Solidity. Смарт контракты и аудит – Telegram
Solidity. Смарт контракты и аудит
2.62K subscribers
246 photos
7 videos
18 files
547 links
Обучение Solidity. Уроки, аудит, разбор кода и популярных сервисов
Download Telegram
Описание контрактов ERC721 и NFT. Часть 4

Особый интерес в этом контракте представляет функция _checkOnERC721Received().

Она принимает такие аргументы, как адрес отправителя, адрес получателя, id токена и некоторые данные в формате bytes, и нужна для того, чтобы проверить, куда мы отправляем наш NFT. В случае, если отправка идет на обычный адрес пользователя, то все ок, однако с адресами контрактов несколько сложнее.

С помощью условия "to.code.length > 0" мы проверяем, что "to" является все таки адресом контракта.

Далее, с помощью ранее подключенного интерфейса IERC721Receiver, на адрес получателя мы вызываем функцию onERC721Received() и передаем аргументы отправителя, получателя, id токена и данные.

В случае, если приходит правильный ответ, то это говорит о том, что контракт может принимать токен, в противном случае мы получаем ошибку, вариантов коей может быть два.

В первом случае контракт получателя совсем не работает с NFT и нам возвращаются пустые данные. А во втором, контракт работает с NFT, но по каким-то причинам не может принять наш токен.

И вот в первом случае мы просто выводим ошибку и откатываем транзакцию, а во втором - принимаем ошибку с помощью assembly и также возвращаем NFT.

#erc721 #nft
Описание контрактов ERC721 и NFT. Часть 5

Также в нашем контракте есть служебные функции _safeTransfer() и _transfer().

В первой принимаются аргументы отправителя, получателя, номер токена и другие данные. Тут вызывается другая функция _transfer() и используется _checkOnERC721Received, чтобы проверить, можно ли отправлять токен.

А во второй проверяется владелец токена и адрес отправителя. В mapping добавляются и отнимаются балансы, а также присваивается токен новому владельцу. Все просто, как и в ERC20.

Ну и последние две функции это _baseURI() и tokenURI(), которые возвращают url нашего токена.

_baseURI() - просто возвращает адрес хранилища, если он есть, к которому после нужно добавить идентификатор токена в tokenURI().

Более того, в уроке приводится дополнительный контракт ERC721URIStorage.sol, который расширяет работу с url токена.

В нем создается отдельный mapping, где хранятся id токена и сами url.

Функция tokenURI() переопределяется, но по сути ничего не меняется в выдаче результатов.

Добавляется функция _setTokenURI(), которая устанавливает в mapping новый url токена, и функция _burn(), которая удаляет его оттуда. Надо заметить, что _burn() еще вызывает аналогичную функцию в родительском контракте. 

#erc721 #nft
Описание контрактов ERC721 и NFT. Часть 6

Ну, и в завершении нашего сегодняшнего изучения ERC721 стоить сказать пару слов о Enumerable контракте, о котором лектора спрашивают в уроке.

Enumerable, как и URIStorage, расширяют функционал основного контракта. В частности этот добавляет работу с токенами по индексу.

Здесь создаются mapping, которые хранят индексы всех токенов, индексы токенов на разных адресах и владельцы токенов по индексу.

Также добавляются функции, которые возвращают общее количество всех токенов, которые были созданы, поиск токена по индексу и поиск владельца по индексу токена.

Переопределяется функция _beforeTokenTransfer(), и добавляются несколько служебных фукнций для управления mapping.

Добавление Enumerable контракта является более сложной организацией работы с NFT, которую используют крупные компании на мировом рынке. Для выпуска своего NFT будет достаточно базовых функций, которые мы сегодня прошли.

#erc721 #nft
Описание контрактов ERC721 и NFT. Часть 7

Забыл написать, про контракт, где реализуется ERC721.

В файлах он идет под названием MyToken.sol.

Итак, в нем мы подключаем три других контракта для наследования функций:

import "./ERC721.sol";
import "./ERC721Enumerable.sol";
import "./ERC721URIStorage.sol";

Далее создаем переменные для сохранения владельца и id токена.

В конструкторе вызываем контракт ERC721 и передаем туда имя токена и его символ, а внутри устанавливаем владельца.

Дальше, через функцию safeMint(), в которой принимаем адрес получателя и другие данные, создаем NFT через служебные функции _safeMint() и _setTokenURI(). В конце, увеличиваем id токена для последующего минта.

Также, с помощью supportsInterface() говорим, что поддерживаем интерфейсы ERC721 и ERC721Enumerable.

Устанавливаем url адрес в _baseURI() и  tokenURI().

И в конце, добавляем функцию для уничтожения нашего токена _burn().

Если вы все же решили использовать Enumerable контракты, то нужно переопределить функцию _beforeTokenTransfer(), вызывая в нем эту же функцию из контракта выше (используя super).

Вот и все!

ERC721 достаточно сложный контракт для тех, кто впервые столкнулся с ним. Однако, посидев с ним несколько часов, вы сможете лучше ориентироваться в коде и уже самостоятельно использовать новые контракты, которые смогут улучшиться ваш NFT.

#erc721 #nft
Урок 25 - Rinkeby, Etherscan, Alchemy

Всем привет!

И сразу к хорошим новостям! Еще не улеглись полученные знания после вчерашнего разбора ERC721, как лектор будет радовать нас новым крутым стримом по ERC1155, который призван заменить 721, и позволяет создавать уникальные и не уникальные токены!

Стрим будет 08 сентября в 19:00 по московскому времени на канале Ильи. Там вы сможете задать вопросы напрямую! Всем советую подключиться!

Ну, а мы сегодня продолжаем наше обучение и поговорим о деплое контрактов в тестовые сети, типа Rinkeby. Также рассмотрим один из популярных сервисов Alchemy.

Новый видео урок!

В зарубежных вакансиях часто проскальзывает пункт, что разработчику нужно уметь загружать свои контракты в тестовые сети и работать с одним из сервисов, который позволяет делать это.

Именно поэтому данное видео будет интересно еще и со стороны поиска работы!

Всем приятного дня и легкого обучения!

#урок #rinkeby #etherscan #alchemy
Деплой контракта в сеть Hardhat

Вообще, как я понял из уроков, есть два типа деплоя контрактов:

1. Деплой в hardhat для проведения тестов с функциями контрактов;
2. Общий деплой в блокчейн;

Разница лишь в том, что для тестов мы разворачиваем контракт каждый раз для каждого теста, а в общем деплое - один раз. При этом за пределами hardhat, в тестовых сетях типа Rinkeby, контракт загружается по примеру mainnet - один раз и его нельзя уже редактировать или удалять.

Для деплоя контракта используется уже знакомый нам код:

async function main() {
  const [signer] = await hre.ethers.getSigners();
  const Lock = await hre.ethers.getContractFactory("Lock", signer);
  const lock = await Lock.deploy();

  await lock.deployed();

}

main().catch((error) => {
  console.error(error);
  process.exitCode = 1;
});

Нужно сказать, что чаще всего код для деплоя в сеть пишут в файле с таким же названием - deploy.js.

После этого, вам нужно скомпилировать ваш контракт с помощью команды npx hardhat compile, и далее развернуть его в сеть с помощью:

npx harhat run noscripts\deploy.js --network localhost

Все! Теперь можно создать другой файл, подключить в него ethers.js и проводить какие-либо работы с развернутым контрактом уже в сети hardhat.

#deploy #hardhat
Немного о сетях и сервисах

Самое главное, что нужно знать о тестовых сетях Эфира то, что на данный момент информация во многих роликах о деплое контрактов уже устарела.

Ранее существовали популярные тестовые сети:

1. Rinkeby
2. Ropsten
3. Kiln
4. Kovan

Так вот, они будут удалены  из-за слияния сетей Эфира в середине сентября 2022 года. Другими словами, их не нужно использовать вообще!

На их смену придут две новые сети:

1. Gorly
2. Sepolia

Они, вероятнее всего, станут основными тестовыми сетями для Эфира и всех токенов на его основе.

Поэтому в видео уроке необходимо взять общий принцип разворачивания СК, а не пример с Rinkeby.

Также существует несколько сервисов, которые помогают разворачивать контракты в тестовых сетях и взаимодействовать с ними. Самыми популярными являются

1. Alchemy
2. Infura

Оба бесплатные, но с ограничениями. Для обучения подходят оба. Да и в вакансиях чаще всего встречаются только эти два.

В принципе, ничего сложного с регистрацией и использованием этих сервисов я не нашел. Если вам потребуется, я поищу дополнительный материал по этим темам.

Далее перейдем к деплою контракта и env.

#infura #rinkeby #ropsten #alchemy #gorly #sepolia
Настройка и использование .env

ENV (environment variables)
- это особые переменные рабочей среды. Другими словами, это некие глобальные значения, расположенные на уровне операционной системы, доступные программам, например настройки системы.

В нашем примере, их используют для того, чтобы скрыть некоторые, особо важные участки кода, как например приватный ключ в Метамаск.

Устанавливается он при помощи команды в консоли:

npm install dotenv --save

Или как было показано в видео: через файл hardhat.config.json и команды в консоли после - npm install.

Затем в тот же файл hardhat.config добавляете строку подключения:

require('dotenv').config()

Далее вы заходите или создаете папку .env в корневом каталоге проекта и записываете переменные (в нашем случае ключи и доступы), например так:

S3_BUCKET="YOURS3BUCKET"
SECRET_KEY="YOURSECRETKEYGOESHERE"

Также проверьте, что в файле gitingnore есть запись ".env", чтобы случайно ваши пароли и ключи не попали в открытый доступ.

После всех этих действий, вы можете использовать созданные переменные в других файлах. Для этого, в месте, где хотите использовать env переменную, ставите фигурные скобки и записываете по примеру:

{process.env.API_KEY} - где API_KEY - имя вашей переменной.

Так легко и просто вы можете защищать свои данные на видео, в репозиториях, на слайдах и т.д.

#env
Пример деплоя контракта в сеть Goerli

И наконец, давайте попробуем загрузить наш контракт в тестовую сеть goerli.

Хочу уточнить, что после тестов мне больше понравился Alchemy, поэтому я буду использовать в примере его сервис.

Итак, на данный момент, вы должны были уже зарегистрироваться в Alchemy на официальном сайте и создать env файл.

Заходим в Alchemy и кликаем на кнопку Create App. Появляется окошко с полями. Там мы вводим название проекта, его описание, а также в поле Chain должен значиться Ethereum и в Network - Goerly.

С помощью Alchemy можно загружать контракты и в другие блокчейн сети, как Solana или Polygon, но мы работаем именно с Эфиром.

После этого создастся ваш проект. В рамках поля проекта ищите кнопку View key и нажимаете на нее. Копируете API key в свой env файл в новую переменную, например API_KEY.

Теперь заходим на сайт Метамаск и скачиваем приложение для Chrome.

Запишите, сохраните, сфотографируйте секретную фразу восстановления МетаМаск!!! Это очень важно! Если вдруг вы забудете пароль, это будет единственный способ восстановить доступ!

После установки и регистрации, открываете приложение и в самом верху выбираете "Тестовая сеть Goerly".

Рядом с названием вашего аккаунта будет меню (три точки), нажимаете их, выбираете реквизиты счета, экспорт закрытого ключа, вводите еще раз свой пароль, копируете свой закрытый ключ и вставляете его в новую переменную в env файле, например META_KEY.

Теперь идем в папку нашего проекта и открываем файл hardhat.config.json.

В раздел module.exports добавляем код networks, как в примере лектора в уроке или как представлено ниже, чтобы в итоге он выглядел так:

module.exports = {
  solidity: "0.8.9",
  networks: {
    goerli: {
      url:
https://eth-goerli.alchemyapi.io/v2/${process.env.API_KEY},
      accounts: [process.env.META_KEY]
    }
  }
};

И вот после всех этих действий, мы можем загрузить свой проект в goerly, выполнив команду в консоли:

npx harhat run noscripts\deploy.js --network goerly

В вашем проекте на Alchemy должен появиться новый контракт, если все прошло как надо.

#goerly #metamask #alchemy
Как получить эфир для сети Goerli

Кстати, для все операций теперь в тестовой сети вам может потребоваться Эфир. Для этого в google вводите в строку поиска goerli faucet.

Например, у меня в поиске в первой ссылке был этот сайт. Можете зарегистрироваться там или поискать другие сайты, которые позволят получать эфир без регистрации.

Теперь нам нужно скопировать адрес нашего кошелька из МетаМаск. Заходим в браузере туда и нажимаем на название аккаунта, чтобы получить наш адрес.

Возвращаемся на сайт faucet (все они работают по одному и тому же принципу), и в поле вставляем наш адрес кошелька. Через пару минут проверяем его и убеждаемся, что теперь там доступно немного ETH.

Так можно делать на других сайтах и собрать чуть больше эфира на кошельке для тестов с контрактом.

Нужно отметить, что этот ETH не имеет никакой ценности и торговать им на бирже не получится. Он создан специально для работы в тестовых сетях.

#goerly #metamask #faucet
Кратко о работе с Etherscan

Etherscan - это самый популярный сайт, где можно просматривать контракты, их код, abi и транзакции по ним.

И чтобы легче находить его и верифицировать свой контракт, можно использовать hardhat.

Для этого после регистрации на Etherscan в правом верхнем углу кликаем на аватар своего профиля и выбираем Api keys. Создаем свой ключ и копируем его в новую переменную в файле env, например ETH_KEY.

Затем снова открываем файл hardhat.config.json и в раздел module.exports добавляем:

etherscan: {
  apiKey:
${process.env.ETH_KEY}
}

К слову сказать, у вас уже должен быть подключен новый пакет hardhat-etherscan (в toolbox он уже есть). Если нет, то сделать это можно командой в консоли:

npm install --save-dev @nomiclabs/hardhat-etherscan

и добавлением записи в hardhat.config.json:

require("@nomiclabs/hardhat-etherscan");

После этого в консоли вам станет доступна команда:

npx hardhat verify --network netName contractAddress

где netName - имя тестовой сети, куда вы загрузили свой контракт, а contractAddress - адрес вашего контракта.

Данная команда выдаст вам ссылку на etherscan, где вы можете получить всю информацию по вашему контракту в блокчейне.

#etherscan
Урок 26 - Honeypot

Надеюсь у всех вчера получилось разобраться с деплоем контрактов в различные сети, а также с новым пакетом для скрытия важной информации в коде.

Сегодня же у нас достаточно легкий урок. В нем нужно не столько учиться каким-то фишкам или настройкам пакетов и конфигов, сколько понять общий принцип ловушки: как ее разглядеть в коде и как научиться переигрывать мошенников.

Также это будет полезно, если вы на досуге "играетесь" с криптовалютой, биржами и следите за выходом новых токенов.

Новый видео урок.

Также напоминаю, что завтра у нас будет стрим от Ильи в 19:00 по московскому времени на его канале. Можно будет узнать новую тему, которую мы будем разбирать в пятницу, и задать вопросы по Solidity.

Вероятнее всего, я завтра сделаю несколько простых постов по нюансам языка, дам несколько полезных ссылок и рекомендаций.

На этой неделе мы закончим продвинутое обучение и со следующей я расскажу вам о новых планах.

Всем легкого дня и быстрого обучения!

#урок #honeypot
Что такое Honeypot

И по традиции, давайте разберемся, что же такое этот Honeypot.

Honeypot (горшок с медом) - это некая ловушка для пользователей, которая основана на не самых очевидных моментах в коде. Чаще всего им пользуются мошенники, чтобы обманом продать токены, которые нельзя будет потом перепродать.

Допустим, по всем каналам в Телеграм и на популярных ресурсах кто-то делает посты о том, что он запускает крутые токены в оборот, что у него большие планы, и уже несколько крупных компаний поддерживают его инициативу. Конечно же, он предлагает всем пользователям купить его токены по бросовой цене в 0.1 $ и обещает, что через полгода их стоимость будет около 1 $ или даже 5 $. Доверчивые люди бросаются покупать токены в надежде заработать. Но через указанный промежуток времени, никакого роста токена нет, и пользователи решают продать его.

А не тут-то было! Токены продать больше нельзя, и мошенник уходит с деньгами создавать новый скам.

В этом уроке и обращают наше внимание на подобные нюансы в коде, как избежать обмана, и как перехитрить мошенника, если он захочет использовать уязвимость нашего контракта на reentrancy.

#honeypot
👍1
Разбор Honeypot

Для того, чтобы вы понимали лучше разбор данного урока, я скидываю ссылку на комит урока от лектора. Лучше всего будет, если вы прочитаете пост, а затем еще раз сами просмотрите код контрактов.

Как я понял из урока, honeypot основан на подмене контракта для логирования событий в контракте банка. Вроде как, всем пользователям показывается первый обычный контракт Logger, который просто порождает событие для log, а на самом деле, наследование идет от другого контракта, который был как бы спрятан от глаз пользователей.

И если не разобраться, от кого идет наследование в ILogger public logger в контракте банка, то можно попасть на деньги.

Также в данном примере была использована уязвимость reentrancy (по сути, повторный возов функций кода) в контракте банка, чтобы заманить в ловушку потенциального хакера.

Хакер думает, что раз "balances[_initiator] = 0" вызывается после отправки средств на счет пользователя, то можно создать новый контракт для атаки, который будет вызывать функцию _withdraw каждый раз по новому пока на счету банка не останется средств.

И вот для того, чтобы обмануть хакера, который хочет обмануть нас, в контракте банка мы создаем новую переменную "bool resuming", чтобы запутать хакера, и в функции _withdraw принимаем дополнительный аргумент "uint _statusCode".

После этого, уже как бы мы сами создаем контракт honeypot и обновляем withdraw с новой bool переменной.

Т.е. изначально лектор показывал, что honeypot создал мошенник, чтобы обманывать пользователей, а позже honeypot создаем мы, чтобы обмануть мошенника.

Уже в нашем honeypot мы используем "_actionCode", чтобы определить, кто пытается вызвать withdraw.

Если обычный пользователь снимает свои деньги, то все проходит ок.

Если же мошенник пытается использовать уязвимость reentrancy и вызывать повторно withdraw, то через нее мы вызываем служебную _widthdraw с новым передаваемым аргументом "_statusCode", который на втором вызове меняет значение на "2". Теперь сам мошенник попадает в нашу ловушку honeypot, транзакция откачивается и он не может получить даже свои деньги.

Вот как-то так.

В 4 контрактах и 1 интерфейсе из урока легко запутаться, полагаю как и в данном посте при первом прочтении.

Тут главное понять, что в рамках урока мы действуем и как мошенник и как тот, кто хочет обмануть мошенника, поэтому один код работает сразу для двух примеров.

#honeypot
Тесты с урока про Honeypot

Также хочу обратить ваше внимание, что лектор в уроке для проведения тестов с контрактами использует деплой с учетом последних обновлений hardhat, который я описывал ранее.

Однако напомню для повторения.

В начале он импортирует loadFixture, expect и ethers для проведения тестов, а также type для использования typechain.

import { loadFixture } from "@nomicfoundation/hardhat-network-helpers";
import { expect } from "chai";
import { ethers } from "hardhat";
import type { Bank, Attack, Logger, Honeypot } from "../typechain-types";

Затем в деплое, вместо beforeEach, пишет функцию dep(), которая возвращает объекты для тестирования ниже.

И в тестах начинает работы с получения этих объектов через await loadFixture(dep).

На данный момент это стандарт работы с тестами в hardhat и их нужно знать.

#honeypot #deploy
Solidity by Example

Сегодня, в ожидании стрима, на канале не будет уроков. Вместо этого я сделаю несколько полезных постов по работе с Solidity.

Я нашел один интересный сайт, где приводятся примеры кода Solidity, как шпаргалки.

Очень удобно, например, когда вы забыли какой-то урок или функцию, и хотите быстро по коду понять написание или использование.

Да, он на английском, но тем не менее очень понятен интуитивно.

Solidity by Example.

#links #hint
Статья с рекомендациями

Также нашел еще одну интересную статью с рекомендациями по написанию хорошего кода. Она на английском языке, поэтому в течение дня я сделаю несколько постов с переводом основных пунктов.

Советы достаточно интересные, и ничего подобного раньше в уроках или других видео я не встречал.

#link #hint
Используйте assert() и require() правильно

Assert()
следует использовать только в тестах внутренних (internal) ошибок или для проверки инвариантов.

Require() используют для проверки условий.

#assert #require #hint
👍1
Используйте модификаторы правильно

Интересное замечание, которое я не встречал еще в практике.

Не рекомендуется использовать модификаторы, в которых изменяются переменные состояния или реализуются внешние вызовы, например при наследовании, так как сами модификаторы исполняются до кода в функции.

contract Registry {
    address owner;

    function isVoter(address _addr) external returns(bool) {
        // Code
    }
}

contract Election {
    Registry registry;

    modifier isEligible(address _addr) {
        require(registry.isVoter(_addr));
        _;
    }

    function vote() isEligible(msg.sender) public {
        // Code
    }
}

Например, выше вы можете видеть НЕ правильное использование модификатора, так как контракт Registry может делать reentrancy атаку в другом контракте, вызывая Election.vote() внутри isVoter().

Модификаторы чаще всего используются, чтобы заменить дублирующийся код в функциях, по примеру проверки владельца isOwner().

#modifier #hint
👍1
Аккуратнее с делением чисел

Solidity, на данный момент сентября 2022 года, не поддерживает числа с точкой, и при делении 5/2 будет показан результат "2". Т.е. вместе с откидыванием цифр после точки, он еще и округляет результат до меньшего числа.

Это действительно проблема для большинства разработчиков. И многие пытаются преодолеть ее через дополнительные библиотеки на openzeppelin или пишут свои "костыли".

В документации по Solidity пишут, что нужно использовать мультипликатор, как в примере:

uint multiplier = 10;
uint x = (5 * multiplier) / 2;

Из других статей я понял, что можно также выполнять все математические операции на фронтенде, чтобы все было точнее, а уже результат сохранять в Solidity.

Некоторые предлагают использовать decimals эфира, но я не видел хороших примеров.

#division #integer #hint
👍1
Интерфейсы и абстрактные контракты

Интерфейсы и абстрактные контракты призваны помочь с написанием кода нашего контракта и облегчить его.

Однако следует помнить, что интерфейсы не могут выполнять функции, не имеют доступа к storage и не могут наследовать от других интерфейсов.

При этом абстрактные контракты так делать могут, что делает их более универсальными. Но нужно помнить, что при наследовании нашим контрактом абстрактного, необходимо override его функции.

#abstract #interface #hint
👍1