Solidity. Смарт контракты и аудит – Telegram
Solidity. Смарт контракты и аудит
2.62K subscribers
246 photos
7 videos
18 files
547 links
Обучение Solidity. Уроки, аудит, разбор кода и популярных сервисов
Download Telegram
ERC-1363: Payable Token

Стандарт ERC-1363 реализует расширение токена ERC-20 для выполнения произвольного кода сразу после вызова transfer(), transferFrom() или approve() в рамках одной транзакции. Этот стандарт позволяет выполнить две транзакции в одной и избежать излишней оплаты за газ.

Важно! Стандарт ERC-1363 является расширением стандарта ERC-20 и полностью обратно совместимым. То есть, он не переопределяет стандартные функции transfer(), transferFrom() или approve().

Стандарт IERC1363.sol расширяет реализацию токена ERC-20 новыми функциями.

interface IERC1363 is IERC20, IERC165 {
function transferAndCall(address to, uint256 amount) external returns (bool);
function transferAndCall(address to, uint256 amount, bytes calldata data) external returns (bool);
function transferFromAndCall(address from, address to, uint256 amount) external returns (bool);
function transferFromAndCall(address from, address to, uint256 amount, bytes calldata data) external returns (bool);
function approveAndCall(address spender, uint256 amount) external returns (bool);
function approveAndCall(address spender, uint256 amount, bytes calldata data) external returns (bool);
}

Работает это следующим образом: вызов любой из этих функций сначала выполняет вызов соответсвующей функции в ERC-20, а затем делает дополнительный вызов на адресе получателя токенов или кому выдавался approve(). Например, transferAndCall(), под капотом, делает стандартный вызов функции transfer(), а затем делает дополнительный вызов функции на адресе получателя токена.

Для выполнения кода после вызова transfer() или transferFrom() получатель токена должен быть контрактом и реализовывать интерфейс IERC1363Receiver.sol

interface IERC1363Receiver {
function onTransferReceived(address spender, address sender, uint256 amount, bytes ?calldata data) external returns (bytes4);
}

Репозиторий и документация с примерами реализации стандарта от Vittorio Minacori, который является автором стандарта ERC-1363: Payable Token

P.S. Про другие стандарты, такие как ERC-165 и ERC-4337 можно почитать в wiki от студии web3 разработки MetaLamp.

#erc1363 #token
👍2
RACE #22 Of The Secureum Bootcamp Epoch

Не теряйте, у меня очень много событий сейчас происходит, что времени остаётся только на основной курс и немного на конкурсные аудиты. С понедельника уже пойдем по Foundry.

А сейчас хочу предложить вам немного попрактиковаться с кодом и пройти небольшой тест Race. В этот раз его написал Tincho - ментор Secureum, создатель Damn Vulnerable DeFi и The Red Guild.

Мини тест, как всегда прекрасен!

RACE #22 Of The Secureum Bootcamp Epoch

У кого сколько получилось?

#race
👍2
Foundry с нуля: Установка. Часть 0

С сегодняшнего дня мы постепенно начинаем изучать Foundry на канале.

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

https://news.1rj.ru/str/solidityset/898


Как будет строиться прохождение?

Сначала пройдемся по документации Foundry, чтобы вы знали, что там представлено, и где найти информацию в случае необходимости. Поговорим об установке, о chisel и anvil, опишем некоторые cast команды и читкоды.

После этого уже приступим к самим тестам.

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

В целом, хочется чтобы получился детальный цикл постов "от А до Я".


Установка Foundry

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

Полный процесс установки можно найти по этой ссылке:

https://book.getfoundry.sh/getting-started/installation

Обычно установка может быть осуществлена с помощью простой команды:

curl -L https://foundry.paradigm.xyz | bash

Это установит FoundryUp - установщик Foundry, который после сможет обновлять и поддерживать среду с более простыми командами.

Нужно сказать, что FoundryUp пока не поддерживается терминалами CMD или PowerShell, которые идут в Windows, поэтому установка там чуть сложнее.

Для этого предусмотрен немного другой пошаговый гайд:

1. Скачиваем Rust и его пакетный менеджер Cargo:

https://www.rust-lang.org/learn/get-started

2. Скачиваем Visual Studio с опцией "Desktop Development With C++"

https://visualstudio.microsoft.com/downloads/

3. Ну, и Git Bash, если у кого еще нет:

https://gitforwindows.org/

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

cargo install --git https://github.com/foundry-rs/foundry --profile local --locked forge cast chisel anvil

которая и установит Foundry на ваш компьютер.

P.S. Для слабых и средних по мощности компьютеров весь процесс может занять до 1,5 - 2 часов!

Также для пользователей Docker есть возможность установки Foundry с командой:

docker pull ghcr.io/foundry-rs/foundry:latest

Я сам не особо работал с Docker, поэтому более детальные гайды по нему можно прочитать тут:

https://book.getfoundry.sh/tutorials/foundry-docker

Проверить, что все установилось правильно можно попытавшись создать новый проект Foundry:

1. Создайте новую папку и откройте ее в редакторе кода;
2. В терминале редактора выполните команду: forge init
3. Если в папке появились файлы типа src, test, foundry.toml, то все ок. Если же нет - спрашивайте в чате совета или смотрите документацию.


Задание

1. Установить Foundry на свой компьютер.

#foundry #lesson0
🔥7👍41
Foundry с 0. Часть 1

Теперь давайте кратко поговорим о том, что установилось у нас вместе с Foundry.

Во-первых, это Cast - специальный инструмент для исполнения RPC вызовов в сети Эфириум.

RPC, от Remote Procedure Call, это протокол удаленного вызова процедур в Эфире: вы можете отправлять транзакции, делать вызовы в смарт контрактах, "доставать" какую-либо информацию из блокчейна - и все это с помощью командной строки в терминале!

Например, выполнив следующую команду:

cast call 0x6b175474e89094c44da98b954eedeac495271d0f "totalSupply()(uint256)" --rpc-url https://eth-mainnet.alchemyapi.io/v2/Lc7oIGYeL_QvInzI0Wiu_pOZZDEKBrdf
8603853182003814300330472690

мы узнаем значение totalSupply у токена DAI. И нам не потребуется посещать другие сайты или использовать API для выполнения этой задачи.

Cast командам будут посвящены отдельные посты, а сейчас пойдем дальше.

Во-вторых, у нас установился Anvil.

Anvil - это локальный тестовый узел блокчейна, который идет только с Foundry. Служит для проведения тестов для ваших смарт контрактов.

P.S. Некоторые антивирусы, например Касперский, ругаются на Anvil по какой-то причине. Поискав в сети, никаких претензий к Anvil по теме безопасности системы я не нашел, поэтому просто поставил его в "доверие" к антивирусу.

В-третьих, также загрузился Chisel.

Chisel - это продвинутый Solidity REPL (read-eval-print loop: программа, которая работает как командная оболочка).

Для меня самого Chisel остается некоторой загадкой, поэтому в свое время будем разбираться с ним вместе.

Если я правильно понимаю на данный момент, с Chisel вы сможете выполнять код Solidity прямо в терминале.

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

#foundry #lesson1
👍7🔥51
Foundry с 0: Cast команды. Часть 2

Сейчас существует достаточно большое количество Cast команд для самых разных задач: от простых запросов о получении баланса токена до работы с calldata и шифрованием.

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

Итак, мы можем открыть терминал и ввести команду:

cast help 

она покажет нам все доступные cast команды.

Тут стоит сделать отступление и уточнить, что для выполнения некоторых команд, нам потребуется специальная RPC ссылка для доступа к блокчейн сетям. Получить такую бесплатно можно на таких популярных проектах как Alchemy или Infura.

По сути, процесс получения ссылок у них похож: регистрируетесь на портале, ищите кнопку Get API key или похожую со словом API, и потом генерируется ваша индивидуальная ссылка для rpc запросов. Выглядит она примерно так:

https://mainnet.infura.io/v3/apiKey

apiKey - ваш уникальный api ключ, показывать который нежелательно никому.

Там же можно получить ссылку на различные сети: Ethereum, Optimism, Arbitrum и т.д.

Именно эту ссылку и нужно будет добавлять в конце наших cast команд.

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

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


Chain команды

Итак, нам доступны три команды:

cast chain-id
cast chain
cast client

Первая выдаст нам номер блокчейна (например, для Эфириума - это 1, для Оптимизма - 10), вторая - название сети, третья - клиента, через который отправляются запросы (например, для Infura - клиент GETH).

Для того чтобы выполнить команду в консоли потребуется прописать:

cast chain-id --rpc-url link

где вместо link - указать вашу ссылку rpc, которую мы получали выше.

Также, думаю, сегодня можно рассмотреть и команды для блоков.


Block команды

Всего на данный момент их шесть:
https://book.getfoundry.sh/reference/cast/block-commands

1. cast find-block
2. cast gas-price
3. cast block-number
4. cast basefee
5. cast block
6. cast age


Первая ищет блок, который был ближе всего к нужной дате, например

cast find-block 1609459200

покажет блок, который был ближе всего к Новому Году 2021.

Обратите внимание, что время указывается в формате unix (количество секунд прошедшее с момента 1 января 1970 года).

Вторая команда показывает текущую стоимость газа в нужной сети, например:

cast gas-price --rpc-url link

Третья - последний на данный момент блок:

cast block-number --rpc-url link

Четвертая - basefee блока (кто не знает, что это, то почитайте про Лондонское обновление):

cast base-fee blockNum --rpc-url link

blockNum - это номер блока, basefee которого нам нужно получить. Вместо числа - 1, 443, 2343242 - можно указать одно из теговых значений: earliest, finalized, safe, latest или pending. По умолчанию идет latest.

Пятая - получение информации о блоке, например:

cast block finalized --rpc-url link

Тут также можно указывать номер блока числом или одним из тегов:
earliest, finalized, safe, latest или pending.

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

Получить информацию о конкретном поле можно с помощью модификации команды, а именно добавления --field опции:

cast block latest -f hash --rpc-url link  

Ну, и шестая команда служит для получения timestamp блока:

cast age blockNum --rpc-url link

где blockNum это номер блока или его теговое значение.

Дальше мы продолжим говорить о командах cast и разберем другие примеры.


Задание

1. Зарегистрироваться на Alchemy или Infura;
2. Получить rpc ссылку;
3. Попробовать несколько команд из поста;
4. Настроить .env файл для rpc ссылок;

#foundry #cast #block #chain #lesson2
👍5🔥5
Foundry с 0. Часть 3

Продолжаем узнавать разнообразие cast команд и сегодня поговорим о тех, что помогают работать с аккаунтами, кошельками и транзакциями, и, на закуску, узнаем, как получить source код контракта с Etherscan.


Account команды

На данный момент существуют 6 команд cast для работы с аккаунтами:

1. cast balance - позволяет узнать количество нативных токенов сети на аккаунте в wei. Тут, кстати, можно уточнить номер блока, на момент которого был тот или иной баланс. Полная команда выглядит так:

cast balance account --block blockNum --rpc-url RpcUrl

2. cast storage - крутая команда, чтобы получить значения в storage контракта. Можно уточнить слот, в котором лежит значение, и скастовать информацию только из него. Например для WETH контракта:

cast storage 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2 0

или просто

cast storage 0x5Af0D9827E0c53E4799BB226655A1de152A425a5

3. cast proof - получение storage proof аккаунта. Не очень понял, что каких целей это может потребоваться. С этой командой вы получить proof таких полей как: accountProof, account address, account balance, codeHash, nonce, storageHash, storageProof, storageProof.key, storageProof.proof, storageProof.value.

Если при написании тестов нам будут требоваться эти команды, мы обязательно вернемся к ним и разберем подробнее.

4. cast nonce - тут все просто, получаем nonce аккаeнта, команда предельно простая:

cast nonce account

5. cast code - получение байткода контракта. Пример с Weth:

cast code 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2

6. cast codesize - получение runtime bytecode size контракта.

P.S. В большинстве команд, где запросы идут в сеть, нужно добавлять свою rpc-url!

Теперь несколько слов о командах для работы с кошельком.


Wallet команды

1. cast wallet new
- создать новый кошелек: адрес и приватный ключ.

2. cast wallet address - конвертация приватного ключа в адрес кошелька. Интересная команда, которая принимает достаточно большое количество опций для генерации адреса: на основе приватного ключа, mnemonic derivation path, mnemonic passphrase, обычного mnemonic. При этом вы также можете указывать, где хранится ключ, который нужно использовать для генерации адреса.

Самая простая команда звучит так:

cast wallet address --private-key PRIVATE_KEY

весь список опция можно найти тут:

https://book.getfoundry.sh/reference/cast/cast-wallet-address

3. cast wallet sign - с ней вы можете подписать сообщение с вашего кошелька. Также, помимо приватных ключей, можно использовать mnemonic опции. Простая команда выглядит так:

cast wallet sign --private-key PRIV_KEY "hello"

4. cast wallet verify - раз можно подписать сообщение, то есть возможность и проверки этого действия. С этой командой можно проверить адресата подписанного сообщения. Команда:

cast wallet verify --address account --address addressMessageSignature

5. cast wallet vanity - интересная команда, которая позволяет сгенерировать адрес по специальным критериям: с определенным окончанием или началом, или nonce. Выглядит так:

cast wallet vanity --ends-with beef

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

Далее несколько команд для работы с транзакциями.


Transaction команды

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

1. cast receipt - получения "чека" о транзакции. Потребуется указать хэш транзакции:

cast receipt TX_HASH

2. cast tx - похожая на первую и выдает информацию о транзакции.

cast tx TX_HASH

3. cast-estimate - с ней можно оценить стоимость проведения транзакции по газу. Также можно указать, если с ней потребуется переслать нативную валюту, типа Эфира. Команда немного сложнее предыдущих и выглядит так:

cast estimate 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2 --value 0.1ether "deposit()" --rpc-url rpcUrl

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

Вместо deposit - вы можете вставить calldata с селектором вызываемой функции и аргументами для нее.
👍71🔥1
4. cast run - команда, которая позволяет эмулировать транзакцию из сети на своей локальной сети, и расписать все traces (ее пути). Полезна для дебаггинга, поэтому будем обращаться к ней позже.

5. cast call - еще одна команда, которая сильно поможет при дебаггинге транзакций, так как выполняет вызов на адрес без публикации транзакции в сети. Много опций для тонкой настройки. Вернемся к ней в соответствующем уроке - посте.

6. cast publish - команда для публикации в сети предварительно подписанной транзакции. Выглядит так:

cast publish --rpc-url RPC TX  

P.S. не помню, чтобы встречал ее практического применения. Будем разбираться.

7. cast send
- похожая на предыдущую команда, которая уже подписывает и публикует транзакцию в сети. Куча настроек. Вернемся к ней позже.

Ну, и последняя на сегодня, команда для получения кода контракта прямо с Etherscan. Тут вам потребуется предварительная регистрация на ресурсе и получение специального кода API. Команда выглядит так:

cast etherscan-source 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2  --etherscan-api-key ETHERSCAN_API_KEY 

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

Завтра поговорим о cast командах для работы с ABI и другие полезные инструменты.

#foundry #cast #lesson3
👍6🔥1
Foundry с 0. Часть 4

Сегодня мы закончим узнавать новые cast команды и пройдем последние три раздела: ABI, конвертация и полезные инструменты.


ABI команды

1. cast abi-encode / cast abi-decode
- шифрует или расшифровывает данные. Например:

cast abi-encode "someFunc(address,uint256)" 0x... 1 234

cast abi-decode "balanceOf(address)(uint256)" 0x000000000000000000000000000000000000000000000000000000000000000a

2. cast 4byte - можно получить функцию по селектору.

cast 4byte 0x8cc5ce99

3. cast 4byte-decode - расшифровать функцию и ее аргументы из calldata:

cast 4byte-decode 0xa9059cbb000000000000000000000000e78388b4ce79068e89bf8aa7f218ef6b9ab0e9d00000000000000000000000000000000000000000000000000174b37380cea000

4. cast 4byte-event - получить функцию event из calldata

cast 4byte-event 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef

5. cast calldata - создать calldata из функции и аргументов. Например,

cast calldata "someFunc(address,uint256)" 0x... 1

6. cast calldata-decode - расшифровать calldata

cast calldata-decode "transfer(address,uint256)" 0xa9059cbb000000000000000000000000e78388b4ce79068e89bf8aa7f218ef6b9ab0e9d0000000000000000000000000000000000000000000000000008a8e4b1a3d8000

7. cast-pretty-calldata - еще одна команда для расшифровки calldata, например:

cast pretty-calldata 0xa9059cbb000000000000000000000000e78388b4ce79068e89bf8aa7f218ef6b9ab0e9d00000000000000000000000000000000000000000000000000174b37380cea000


8. cast upload-signature / cast sig - получить селектор функции:

cast upload-signature 'function approve(address,uint256)'

можно указывать несколько подряд

9. cast keccak - получить зашифрованные через keccak256 данные.

cast keccak abcsdfg

10. cast compute-address - сгенерировать адрес на основе nonce и адреса деплоера, например:

cast compute-address --nonce 0 --rpc-url yourUrl

11. cast create2 - создать адрес через опкод creat2.

12. cast interface - создать интерфес контракта из ABI:

cast interface -o IWETH.sol 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2

Также можно использовать ABI с Etherscan, но для этого придется получать API и использовать дополнительную опцию --etherscan-api-key.

13. cast max-int / cast min-int / cast max-uint - получение максимальных значений uint256 / int256.


Команды для конвертации данных

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

Итак, вы можете конвертировать:

1. Бинарные данные в hex;
2. Fixed point number в integer;
3. Расшифровывать RLP данные;
4. UTF8 text в hex;
5. Количество wei в Эфир;
6. Получать адрес из bytes32;
7. Получать строку из bytes32;
8. hex в строку ASCII;
9. Из hex в числа;
10. Из hex в bytes32;
11. Из hex в RLP;
12. Совершать побитовые операции со сдвигом влево-вправо;

Полные список команд можно найти по ссылке:
https://book.getfoundry.sh/reference/cast/cast-shl

Крайне рекомендую попробовать поработать со всеми командами в свободное время, чтобы понять, как это все работает и привыкнуть работать с cast.


Задание

1. Попробовать выполнить 10 разны команд cast.

#foundry #cast #lesson4
👍111
Foundry с 0. Chisel. Часть 5

Сегодня, наконец, поговорим о Chisel.

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

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

Итак, chisel появляется у нас в системе вместе с установкой Foundry. Это также опенсорсный продукт, который разрабатывается, как я понял, сообществом, а не какой-либо коммерческой компанией.

Для того, чтобы начать с ним работать, достаточно открыть терминал и написать команду

chisel

Мы войдем в режим работы программы.

Все доступные команды можно посмотреть, как это обычно бывает, с помощью

chisel !help

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

Из списка не особо понятно, что может делать chisel и как с ним обращаться, поэтому дадим несколько примеров.

1. Математические операции

С chisel можно выполнять не только математические операции, но также и побитовые.

Очень удобно получать значения вычислений побитовых сдвигов влево / вправо, или такие как "побитовое И" или "побитовое ИЛИ".

Достаточно в терминале написать запрос операции, типа:

100 ^ 4 
100 << 5

Единственное то, нужно быть аккуратными с делением, так как программа эмулирует работу с Solidity. Например, если попытаться выполнить операцию 3/2, то chisel не покажет результата.

2. Работа с ABI

Вы также можете получать зашифрованные данные через abi.encode и keccak256. Например,

abi.encode(256, bytes32(0), "Chisel!")

или

keccak256(abi.encode(256, bytes32(0), "Chisel!"))

Что классно с chisel, так это то, что он показывает, как это будет храниться в памяти (memory) с указание на поинтеры.

Например, команда выше с abi.encode покажется как:

├ Hex (Tuple Encoded):
├─ Pointer ([0x00:0x20]): 0x0000000000000000000000000000000000000000000000000000000000000020
├─ Length ([0x20:0x40]): 0x00000000000000000000000000000000000000000000000000000000000000a0
└─ Contents ([0x40:..]): 0x000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000743686973656c2100000000000000000000000000000000000000000000000000

3. Переменные в chisel

В chisel можно создавать свои переменные и позже работать с ними. Например, вы можете создать переменную:

uint a = 1;

А потом попробовать выполнить математическую операцию, типа:

uint b = a << 0x08;

4. Функции и контракты

В chisel вы также можете создавать полноценные контракты и функции в них, а позже отслеживать пути работы!

Создадим простейший контракт прямо в терминале:

contract Test {
function get() external view returns (uint) {
return 256;
}
}

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

Test t = new Test()

Теперь мы можем обращаться к контракту, вызывая функции из него:

t.get()

Более того, если мы добавим !traces перед вызовом функции, то сможем получить расшифровку "пути" вызова! Не большой аналог -vvv при тестах в Foundry, кто знает.

5. Получение интерфейсов контрактов с Etherscan

С помощью простой команды мы можем получить интерфейс любого контракта и сохранить его в файле. Однако тут есть ограничения: сохранить можно только интерфейс верифицированных контрактов в сети Ethereum, но вскоре обещают поддержку и других сетей. Команда простая:

!fetch 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2 IWETH

тут мы указываем адрес контракта, интерфейс которого хотим скачать и название для файла, который будет создан для этого.
👍3🔥1
6. Простое взаимодействие с загруженными контрактами

Также вы можете делать форк Ethereum и взаимодействовать с контрактами, которые уже были загружены в блокчейн. Для этого потребуется rpc-ссылка, которую можно получить, например, в сервисе Infura и выполнить команду:

!fork yourRpcLink

можно также указать номер блока для форка:

!fork yourRpcLink 000000000

Далее, к примеру, можно написать так:

interface IERC20 { function balanceOf(address holder) external virtual returns(uint256);}

и присвоить его в переменную с реальным контрактом:

IERC20 usdc = IERC(0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48); 

и теперь вы можете делать вызов на контракт на эту функцию:

usdc.balanceOf(...)

где в скобках указать интересующий адрес, как если бы это было в контракте!

7. Сохранение и загрузка сессий

Если вы хотите сохранить некоторые свои наработки в chisel, то можете выполнить команду !save для сохранения сессии, и позже загрузить ее через команду !load.

!clear поможет очистить память chisel от предыдущих операций.

8. Работа с памятью

Есть также три прекрасные команды !memdump, !stackdump и !rawstack для работы с памятью.

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

Заключение

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

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

А вы, что думаете про chisel?

#foundry #chisel #lesson5
👍9🔥1
Foundry с 0. Часть 6

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

Вся конфигурация Foundry лежит в файле foundry.toml, который появляется в момент инициализации проекта и лежит в корневой папке.

Вообще, формат toml для меня был некоторым открытием при изучении Foundry. Вы знали, что это, по своей сути, отдельный язык?

Toml, или Tom's Obvious Minimal Language, это специальный язык для конфигурационных файлов, который просто читать и писать. Более того, из него достаточно просто парсить информацию!

В общем, если хотите узнать о нем чуть больше, рекомендую посетить его официальную страницу:

https://toml.io/en/

Но вернемся к Foundry.

Итак, все настройки лежат в файле foundry.toml. Если открыть его сразу после старта проекта, то можно увидеть что-то типа такого:

[profile.default]
src = "src"
out = "out"
libs = ["lib"]

P.S. Актуально на момент написания поста. Среда развивается и вскоре могут появиться другие записи.

Настройки можно разделить на три категории: по умолчанию, общие и профильные.

profile.default - как можно догадаться, это профиль по умолчанию в Foundry. Скажем так, он самый главный.

Если вы создадите другие профили, например [profile.local], то тут можно указать какие-то специальные настройки для текущего проекта, которые в свою очередь, будут наследовать настройки из profile.default.

Смотрите, что получается. Где-то на своем компьютере вы можете создать корневой файл foundry.toml с дефолтным профилем и записать самые общие настройки там. А уже в каком-либо конкретном проекте - создавать новый файл foundry.toml с новым профилем для данного протокола и получать дефолтные настройки из главного файла, настроив предварительно пути.

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

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

1. src - путь до контрактов от корня проекта;
2. test - путь до тестов от корня проекта;
3. out - путь до артефактов от корня проекта;
4. libs - массив путей с библиотеками от корня проекта;
5. cache - включить / выключить кэширования.
6. broadcast - путь для транслирования записей транзакций;
7. force - включение / выключение очистки кэша при сборке проекта;
8. solc_version - версия Solidity для компилятора;
9. ignored_error_codes - игнорировать ошибки в коде при сборке проекта;
10. evm_version - версия EVM, например london или byantium;
11. optimizer - вкл/выкл оптимизатора;
12. optimizer_runs - количество "пробегов" оптимизатора;
13. via_ir - вкл/выкл "прохода" компиляции через IR оптимизатор;
14. etherscan - установка параметров для работы с etherscan;

Более подробно об этих и множестве других настроек можно узнать отсюда:

https://book.getfoundry.sh/reference/config/

Для установки настроек в Foundry достаточно добавлять соответствующие записи в файл foundry.toml. Например, просто прописав:

optimizer = true
optimizer_runs = 20_000

Мы включим оптимизатор кода и установим его "пробег" на 20 000.

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

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

#foundry #lesson6
👍7🔥1
Foundry с 0. Часть 7

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

На данный момент в Foundry используется набор стандартных библиотек - Forge Std, которые хранятся в папке lib в корне проекта. Всего их около 17 штук, но чаще всего используют всего 3-4 из них, а другие уже по умолчанию наследуются.

1. Test.sol - основная библиотека, которую мы наследуем в контракте-тесте. Она уже содержит в себе практически все остальные библиотеки.

2. console.sol или console2.sol - использование логирования данных, как в JavaScript через console.log().

3. Vm.sol - библиотеки для возможности использования специальных читкодов для управления локальным состоянием блокчейна.

4. Script.sol - базовые инструменты для написания скриптов.

5. StdAssertions - тестирование и сравнение результатов.

6. StdErrors - вывод ошибок.

7. StdStorage - работа с памятью при тестировании.

Более развернутую информацию по доступным библиотекам можно почитать тут:

https://book.getfoundry.sh/reference/forge-std/

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

#foundry #forgestd #lesson7
👍5🔥1
Foundry с 0. Часть 8

Сегодня мы поговорим о старте нового проекта на Foundry и подготовке рабочей среды.

P.S. Ранее, в нулевом разделе, был описан процесс установки программы на свой компьютер, поэтому дальше текст будет подразумевать, что он уже у вас имеется.

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

forge init

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

.github - для работы с git;
lib - тут находятся контракты для импорта(например, OpenZeppelin);
noscript - вспомогательные скрипты для деплоя;
src - основная папка для наших контрактов;
test - папка для тестовых контрактов;
.foundry.toml - настройка для Foundry в данном проекте;

Свой проект мы можем писать, создавая новые файлы в папке src, а тесты - в test. Я проговариваю это еще раз, так как видел проекты, в которых разработчики создавали отдельные папки все src для контрактов, а потом жаловались, что что-то не работает.

Старайтесь держать структуру файлов в корневой папке.

Если же вы получили готовый проект для написания тестов и скачали его с GitHub, то подготовить его в своей рабочей среде можно выполнив команду:

forge install

Она установит необходимые зависимости и настроит пути.

Да, несколько слов о путях к файлам.

В некоторых проектах можно встретить отдельный файл remappings.txt. Именно в нем вы можете прописывать уникальные пути к файлам. Например, после установки контрактов от Open Zeppelin, они будут находиться в папке lib и иметь полный путь:

lib/openzeppelin-contracts/contracts/token/ERC20.sol 

Вы можете модифицировать его, сократив до

@openzeppelin/token/ERC20.sol

Для этого, в файле remappings.txt следует прописать:

@openzeppelin/ = lib/openzeppelin-contracts/contracts/

И теперь "под капотом" он будет заменяться на более короткий.

Это бывает удобно, когда у вас большая файловая система в проекте.

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

#foundry #lesson8
5🔥1
Foundry с 0. Часть 9

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

1. Unit тест

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

2. Fuzz тест

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

Грубый пример, у нас есть функция перевода, которая допускает значения меньше uint112. И вот мы, как бы говорим, Foundry найди такие значения, чтобы функция откатилась. Он в аргументы подставляет разные значения от uint4 до uint256 и выдает результат.

Если сейчас не понятно, то с практикой вы лучше осознаете всю ее пользу.

3. Invariant тесты

Сравнительно недавнее веяние в мире тестов. Активно его стали развивать только в последние полгода.

Инварианты - это такие значение, которые всегда должны быть "правильными"... Я сам долго пытался понять это, но объяснить лучше на примере.

Вот вы запустили свой токен. И вот его totalSupply никогда не должен быть больше общего количества токенов у пользователей. Или, что количество токенов на вывод не должно превышать количество токенов при депозите.

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

4. Differential тесты

Более сложные по своей основе тесты, которые иногда еще называют differential fuzzing.

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

Я сам не писал еще такие тесты, поэтому будем позже разбираться с ними вместе.

5. Fork тесты

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

6. Дебаггинг

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

В рамках каждого вида тестов мы можем проверять различные условия в коде (if/else, require), математические операции, порождение событий и вообще практически все, что захотим.

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

Со следующей неделе мы начнем разбирать практическую часть написание тестов. А впереди у нас еще много работы!

#foundry #lesson9
👍7
Foundry с 0. Часть 10

Мы закончили с введением в Foundry и скоро начнем изучать написание тестов.

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

Два видео на русском языке:

1. Foundry: альтернатива Hardhat для разработки/тестов

2. Нечёткое тестирование (fuzzy testing) в Foundry

И несколько на английском:

3. Learn Solidity, Blockchain Development, & Smart Contracts

4. Introduction | Testing with Foundry

5. How to Foundry with Brock Elmore

6. Testing with Foundry

7. Fuzz & Invariant Tests

8. How to Foundry 2.0: Brock Elmore

Этих видео будет достаточно для того, чтобы погрузиться в Foundry и самому начать писать тесты.

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

#foundry #lesson10
👍3🔥31
Foundry с 0. Часть 11

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

Что нужно знать перед написанием тестов?

Во-первых, все тесты пишутся в папке test. Можно, конечно, создавать новые папки, но придется переписывать пути для Foundry и производить другие не нужные манипуляции. Поэтому проще все делать уже в одной папке, в которой строить нужную нам архитектуру.

Во-вторых, по правилам Foundry название файлов для тестов должно значится как fileName.t.sol. Другими словами окончание t.sol обязательно для каждого файла теста.

В-третьих, практически в каждом файле тестов нам нужно будет импортировать библиотеку Test от Forge и контракт, для которого будут писаться тесты:

import {Test, console2} from "forge-std/Test.sol";

В четвертых, уже наш тестовый контракт обязательно должен иметь наследование от Test:

contract myContract is Test {}

Возьмем, к примеру, файл контракта и файл теста, которые создаются при инициализации проекта: Counter.sol:

Наш контракт:

contract Counter {
uint256 public number;

function setNumber(uint256 newNumber) public {
number = newNumber;
}

function increment() public {
number++;
}
}


И напишем к нему простые тесты. Для начала импортируем библиотеку Test и наш контракт, а также создаем наследование:

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;

import {Test, console2} from "forge-std/Test.sol";
import {Counter} from "../src/Counter.sol";

contract CounterTest is Test {}

Из Test, как вы видите, мы получаем два объекта: сам Test и еще Console. Второй объект поможет нам логировать данные из тестов и выводить их в консоль, очень похоже на console.log из javanoscript.

Далее нам нужно будет создать объект нашего контракта и поместить его в переменную.

Counter public counter;

Но как поместить объект контракта в переменную при том, что в Foundry нет конструктора?

Для этого есть специальная служебная функция setUp(). Если вы переходите на Foundry с Hardhat, то можете представить себе setUp() как нечто подобное loadFixture или beforeEach. Другими словами setUp() будет исполняться перед каждым отдельным тестом. Это нужно для того, чтобы наш локальный блокчейн, на котором и будут проходить все тесты, не хранил изменения в стейте, а очищал его. Так сами тесты будут более достоверными.

Итак, в setUp() мы будем разворачивать наш контракт Counter:

setUp() public {
counter = new Counter();
}

Теперь можно написать наш первый тест.

Самое главное, что нужно запомнить сейчас так это то, что название тестов:

1. Должны начинаться со слова test;
2. Должны кратко описывать то, что они тестируют;

В нашем случае мы можем создать тест с названием:

function test_Increment() public {}

Также следует запомнить, что все функции для тестов должны иметь область видимости public или external, чтобы программа могла работать с ними.

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

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

В тестах нам и нужно сравнить наши ожидания с тем, что явилось результатом исполнения функции. Например, у нас в контракте есть функция increment(). Что она делает? Она прибавляет 1 к значению в number.

Как написать для этого тест?

function test_Increment() public {
counter.increment();
assertEq(counter.number(), 1);
//console2.log(counter.number());
}

В начале мы вызываем функцию в нашем контракте для изменения переменной number, а затем проверяем, что наше ожидаемое значение будет равно значению, которое сохранилось в переменной. С помощью console2.log мы смотрим значение number.

Теперь нам нужно обратиться к Foundry в терминале и попросить сделать тест. Это делается через команду:
👍1
forge test

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

forge test -vv

Всего 5 уровней уточнений:

1. По умолчанию срабатывает при обычно команде тестов;
2. -vv - для простого отображения ошибок и console.log;
3. -vvv - для отображения путей теста;
4. -vvvv - более подробное отображения действий теста;
5. -vvvvv - также подробное отображение путей;

Последние два часто применяются при дебагинге.

Итак, прописываем команду и видим, что тест прошел, о чем символизирует надпись [PASS] напротив теста в терминале.

P.S. Что интересно, так это то, что, если раскомментировать console2.log, то он будет показывать значение 0, хотя тест будет пройден!

Если же мы подправим наш тест до:

assertEq(counter.number(), 2);

То после команды в терминале, увидим надпись [FAIL] и информацию об ошибке.

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

#foundry #lesson11
👍9
Foundry с 0. Часть 12

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

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

При этом есть еще множество других сравнений. Например, создадим новый тест, в котором установим переменную number = 150.

function test_setNumber() public {
counter.setNumber(150);
}

Теперь с помощью последующих команды мы можем проверить, что:

assertGt(counter.number(), 140); //проверка, если "а" больше "б";
assertGe(counter.number(), 150); // проверка, если "а" больше или равно "б";
assertLt(counter.number(), 170); // проверка, если "а" меньше "б";
assertLe(counter.number(), 150); // проверка, если "а" меньше или равно "б";

Есть еще две более сложные команды:

assertApproxEqAbs();
assertApproxEqRel();

Первая проверяет, что значение "а" примерно равно "б", но в пределах определенной дельты. Другими словами, "а" не должно превышать "б+с", например:

assertApproxEqAbs(counter.number(), 200, 200);

Вторая же команда делает примерно тоже, но в процентном соотношении, где 100% = 1е18, и пример может выглядеть так:

assertApproxEqRel(counter.number(), 150, 0.4e18);

Эти две команды могут быть полезны при работе с DeFi протоколами, где часто идет работа с дельтами значений и ценами на токены.

Кроме того, вы также можете проверять еще несколько параметров и не только числовых:

1. assertTrue(); - проверка, что результат будет равен true
2. assertFalse(); - проверка, что результат будет равен false
3. assertEqDecimal(); - проверка, на равенство decimals
4. assertEq32(); - проверка, что результат будет равен bytes32
5. assertEq0(); - проверка, что результат будет равен bytes32

Хоть они используют реже, чем остальные, все равно их также можно встретить в крупных проектах.

#foundry #lesson12
👍1
Foundry с 0. Часть 13

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

Для начала добавим новую функцию в наш контракт Counter.sol:

function setNumber100(uint256 newNumber) public {
require(newNumber == 100, 'Wrong numer!');
number = newNumber;
}

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

Как это протестировать?

Если мы просто хотим убедиться, что данная функция не сработает, если число будет отличное от 100, то можно сделать так:

function testFail_setNumber100() public {
counter.setNumber100(150);
}

Обратите внимание на testFail. Так мы говорим Foundry, что ожидаем откат транзакции. Другими словами, если сейчас транзакция не пройдет, то результатом будет fail и таким образом тест удастся. Поняли, в чем дело?

Когда мы пишем testFail, необходимо чтобы транзакция упала - тогда тест пройдет. Если же транзакция завершится нормально, то тест будет "завален". Тут, скажем, немного обратная от привычной логика.

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

Как протестировать require и условия с кастомными ошибками?

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

Одним из них является:

vm.expectRevert();

Тут мы как бы обращаемся к vm (virtual machine) и говорим, что ожидаем реверт с некоторым сообщением. Наш тест может выглядеть так:

function test_setNumber_Revert() public {
vm.expectRevert("Wrong numer!");
counter.setNumber100(150);
}

Обратите внимание, что читкод мы пишем до исполнения функции, а не после нее. Это очень важно для хода выполнения тестов! Более того, в этом случае мы пишем название теста просто test_testName, а не testFail_testName, т.е. слово fail тут уже не нужно.

Далее посмотрим на два вида кастомных ошибок.

Для начала добавим их и две новые функции в контракт:

error WrongNum(address caller, uint256 num);
error WrongSet();

function setNumber150(uint256 newNumber) public {
if (newNumber != 150) revert WrongSet();
number = newNumber;
}

function setNumber200(uint256 newNumber) public {
if (newNumber != 200) revert WrongNum(msg.sender, newNumber);
number = newNumber;
}

Тесты для обеих функций могут выглядеть так:

function test_setNumber_Revert150() public {
vm.expectRevert(abi.encodeWithSignature('WrongSet()'));
counter.setNumber150(200);
}

function test_setNumber_Revert200() public {
vm.expectRevert(abi.encodeWithSignature("WrongNum(address,uint256)", address(this),150));
counter.setNumber200(150);
}

Ошибки и их аргументы мы оборачиваем в abi.encodeWithSignature() и это становится нашим ожидаемым результатом тестов.

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

function test_setNumber_Revert150() public {
vm.expectRevert('WrongSet.selector);
counter.setNumber150(200);
}

Оба варианта рабочие. Можете смело использовать их в своих тестах.

Еще интереснее дела обстоят с порождением событий и их тестированием.

Объявим его в нашем контракте Counter:

event NewEvent(uint256 num);

и добавим запись в функцию increment() в виде:

emit NewEvent(number);

Наш тест для событий может выглядеть так:

function test_IncrementEvent() public {
vm.expectEmit(true, true, true, false);
emit NewEvent(counter.number());
counter.increment();
}

Сначала мы используем новый читкод vm.expectEmit(), который принимает четыре аргумента в качестве булевых значение. Первые три устанавливаются для отслеживания indexed параметров в событии, последний - нужна ли проверка входных значений.

Далее нам нужно породить это событие в нашем тесте и уже после вызвать функцию.

Немного странно, но так это работает.
👍2
Обратите внимание, для того чтобы протестировать порождение событий в контракте-тесте также нужно создать такой же event!

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

#foundry #lesson13
👍1🔥1
Foundry с 0. Часть 14

В прошлом посте мы впервые встретились с читкодами, помните vm.expectRevert() и vm.expectEmit()?

Читкоды Foundry это специальные служебные функции в программе, которые помогают нам писать качественные тесты и манипулировать состоянием локального блокчейна! Вообще это очень крутая штука.

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

1. Манипуляции локальным блокчейном

С помощью этой категории вы можете управлять состоянием блокчейна для своих тестов. Например, вы можете "перемотать время" или перескочить некоторое количество блоков, пополнить свои счета токенов и nft, установить block.difficulty или block.basefee, управлять nonce и многое другое.

2. Assertions

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

3. Форк сетей

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

4. Работа с переменными среды

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

5. Функции помощники

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

6. Остальные

Есть также редко используемая категория, которая помогает работать с файлами и rpc ссылками, а также проводить более сложные тесты, типа фаззинга и инвариантов.

Работа с "фиктивными" адресами пользователей

В этом посте мы затронем тему, как писать тесты с использованием подставных адресов.

Для большего понимая объясню, что я имею ввиду под "фиктивными адресами". Это такие адреса, которые создаются только на время проведения теста. Их нельзя использовать больше нигде. Только в ваших тестах, как например, для проверки доступа к функциям.

Для этих целей используется специальный читкод - vm.addr(uint256);

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

Итак, продолжим работать с нашим контрактом Counter и введем концепцию владельца, добавив в конструктор owner = msg.sender, и создав соответствующую переменную. Более того, для простых тестов добавим еще модификатор и простую функцию:

address public owner;

constructor() {
owner = msg.sender;
}

modifier onlyOwner () {
require(msg.sender == owner, "Now an owner!");
_;
}

function setNumberOwner(uint256 newNumber) public onlyOwner{
number = newNumber;
}


А теперь самое интересное, переходим в наш файл для тестов Counter.t.sol и создаем адреса пользователей.

address ADMIN = vm.addr(23432432);
address HACKER = vm.addr(223343212432);


Название переменных для ключевых ролей лучше писать заглавными буквами, как константы. В скобках vm.addr() вы можете написать вообще любое число uint256, а Foundry создаст на его основе рабочий уникальный адрес.

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

Добавить лейбл к адресу очень просто:

vm.label(ADMIN, "ADMIN");
vm.label(HACKER, "HACKER");


P.S. Чаще всего видел, что лейблы помещали в setUp() функцию.

Теперь еще один интересный момент.

Когда мы в setUp() создаем новый контракт Counter и помещаем его в переменную, то владельцем становится адрес контракта нашего теста - CounterTest. Для "подмены" адреса вызывающего можно использовать еще несколько читкодов - vm.prank(), vm.startPrank() и vm.stopPrank().

vm.prank() - работает только на вызов, который идет после него, а vm.startPrank() - работает до тех пор, пока его не остановит vm.stopPrank().
👍1🔥1👏1