Mapping нужно уделить особо пристальное внимание, так как он, по моему опыту просмотренных видео о смарт-контрактах, используется довольно часто.
Mapping или сопоставление - это хранение данных в виде ключ-значение, например, "адрес кошелька => сумма пополнения".
При этом в Solidity нельзя создавать mapping, у которого в ключах или значениях смешанные типы данных. Другими словами, если ключом является address, то позже вы не можете сохранить там значение uint.
Также нужно отметить, что в "ключе" можно сохранять только простые переменные, а "значении" может выступать другой mapping или даже массив.
#mapping
Mapping или сопоставление - это хранение данных в виде ключ-значение, например, "адрес кошелька => сумма пополнения".
При этом в Solidity нельзя создавать mapping, у которого в ключах или значениях смешанные типы данных. Другими словами, если ключом является address, то позже вы не можете сохранить там значение uint.
Также нужно отметить, что в "ключе" можно сохранять только простые переменные, а "значении" может выступать другой mapping или даже массив.
#mapping
🔥1
Динамические массивы и мэппинги в storage
И сразу по следам предыдущего видео мы посмотрим другой урок о storage.
Видео урок.
Здесь лектор более наглядно рассказывает о том, как располагаются значения в хранилище. Особое внимание он уделяет динамическим массивам и mapping.
В задачах на взлом контрактов мы встречали несколько упражнений, где требовалось знать работу памяти и уметь доставать значения через getStorageAt(). Поэтому эта тема является достаточно важной для обучения блокчейн разработчика!
#mapping #array #storage
И сразу по следам предыдущего видео мы посмотрим другой урок о storage.
Видео урок.
Здесь лектор более наглядно рассказывает о том, как располагаются значения в хранилище. Особое внимание он уделяет динамическим массивам и mapping.
В задачах на взлом контрактов мы встречали несколько упражнений, где требовалось знать работу памяти и уметь доставать значения через getStorageAt(). Поэтому эта тема является достаточно важной для обучения блокчейн разработчика!
#mapping #array #storage
YouTube
Solidity и смарт-контракты Ethereum, урок #35 | Динамические массивы и мэппинги в storage
ХОТИТЕ СТАТЬ РАЗРАБОТЧИКОМ Solidity, узнать об Ethereum, блокчейне и многом другом ещё больше?!
Мои друзья из GUIDE DAO (бывшая школа MCS) предлагают скидку 0,1 ETH на ВСЕ СВОИ БУТКЕМЫ ПО КРИПТЕ! Материалы этих буткемов подготовлены мной и другими специалистами:…
Мои друзья из GUIDE DAO (бывшая школа MCS) предлагают скидку 0,1 ETH на ВСЕ СВОИ БУТКЕМЫ ПО КРИПТЕ! Материалы этих буткемов подготовлены мной и другими специалистами:…
👍1
Прочитать mapping в Foundry
Просто оставлю тут небольшую заметку про работу с памятью.
Я еще только учусь вместе с вами, и сегодня столкнулся с проблемой прочтения значений из памяти контракта.
Допустим есть такой маппинг:
mapping (uint256 => structInt) internal BlaBlaSt;
Я перерыл весь foundry и гугл, но так и не понял есть ли какие-нибудь команды, чтобы прочитать значения по нужному id.
В общем, если однажды столкнётесь с такой же проблемой, то проще всего будет написать функцию для вывода значений, а уже после использовать ее в своих тестах.
Получили значения из функции => сохранили в переменные => вывели в консоль.
Мне сегодня это стоило пары часов.
Если знаете более простой способ или команду, буду рад, если поделитесь в комментариях.
#foundry #mapping
Просто оставлю тут небольшую заметку про работу с памятью.
Я еще только учусь вместе с вами, и сегодня столкнулся с проблемой прочтения значений из памяти контракта.
Допустим есть такой маппинг:
mapping (uint256 => structInt) internal BlaBlaSt;
Я перерыл весь foundry и гугл, но так и не понял есть ли какие-нибудь команды, чтобы прочитать значения по нужному id.
В общем, если однажды столкнётесь с такой же проблемой, то проще всего будет написать функцию для вывода значений, а уже после использовать ее в своих тестах.
Получили значения из функции => сохранили в переменные => вывели в консоль.
Мне сегодня это стоило пары часов.
Если знаете более простой способ или команду, буду рад, если поделитесь в комментариях.
#foundry #mapping
Solidity полон сюрпризов
А знали ли вы, что mapping могут держать функции в качестве значения? Или, что функции могут принимать функции в качестве параметров?
Лично для меня это было в диковинку. Наткнулся на эту картинку, опять же, в Твиттере и сначала подумал, что это какой-то прикол. Более того, по указанной ссылке, да и в поиске я не смогу найти информации об этом.
Переписав код со скрина в Ремикс, он скомпилировался без проблем. Да и потом исполнился на раз!
Удивительно! Ни разу: ни в аудитах, ни в задачах, ни где, я не встречал таких примеров.
В комментариях оставляю код, чтобы вы могли скопипастить его в Ремикс и поиграться самим.
#mapping #function
А знали ли вы, что mapping могут держать функции в качестве значения? Или, что функции могут принимать функции в качестве параметров?
Лично для меня это было в диковинку. Наткнулся на эту картинку, опять же, в Твиттере и сначала подумал, что это какой-то прикол. Более того, по указанной ссылке, да и в поиске я не смогу найти информации об этом.
Переписав код со скрина в Ремикс, он скомпилировался без проблем. Да и потом исполнился на раз!
Удивительно! Ни разу: ни в аудитах, ни в задачах, ни где, я не встречал таких примеров.
В комментариях оставляю код, чтобы вы могли скопипастить его в Ремикс и поиграться самим.
#mapping #function
👍3
Пример упаковки структур в маппинге
В отчетах можно часто найти информационный пункт о том, что переменные в структурах следует правильно паковать. В этом примере автор решил пойти дальше и еще больше оптимизировать хранение структуру в контракте.
С помощью двух функций, упаковки и распаковки, и побитовых операций переменные в структуре размещаются в uint256, который в свою очередь сохраняется в маппинге с ключом адресата.
Не уверен, что с практической точки зрения это может быть часто применимо, однако вполне вероятно такой способ упаковки можно будет встретить в проектах при аудите.
#mapping #struct
В отчетах можно часто найти информационный пункт о том, что переменные в структурах следует правильно паковать. В этом примере автор решил пойти дальше и еще больше оптимизировать хранение структуру в контракте.
С помощью двух функций, упаковки и распаковки, и побитовых операций переменные в структуре размещаются в uint256, который в свою очередь сохраняется в маппинге с ключом адресата.
Не уверен, что с практической точки зрения это может быть часто применимо, однако вполне вероятно такой способ упаковки можно будет встретить в проектах при аудите.
#mapping #struct
❤2👍1
Организация storage в прокси контракте
В процессе аудита протокола Arcade встретился лицом к лицу с не совсем обычной организацией storage в контракте. Не совсем обычной для меня. Возможно, более опытные разработчики уже сталкивались с подобной реализацией.
Я могу ошибиться в некоторых моментах описания и прошу поправить меня, если вдруг, что не так.
P.S. В посте будут приведены ссылки на контракты из открытого конкурсного репозитория, актуального на время написания поста.
Итак, у нас есть прокси контакт - NFTBoostVault, который наследует функции от двух библиотек, которые и позволяют работать с памятью контракта так, чтобы при обновлениях не было коллизии в данных - Storage и NFTBoostVaultStorage.
Примечательно здесь то, что базовые типы данных, например address и uint, которые не поддерживают определенные места хранения в storage (как mapping), хранятся в структуре с одноименным названием, например:
struct Address {
address data;
}
Так в конструкторе или функции мы можем установить / обновить для них значения с помощью:
Storage.set(Storage.uint256Ptr("locked"), 1);
где Storage.uint256Ptr("locked") - место в памяти, а 1 - значения для установки.
Или также для типов address:
Storage.set(Storage.addressPtr("manager"), manager);
где manager - тип address из аргументов функции.
Доставать значения из такого слота памяти можно при помощи других функций из библиотеки:
function getIsLocked() public view override returns (uint256) {
return Storage.uint256Ptr("locked").data;
}
т.е. мы запрашиваем в storage слот с хешем uint256Ptr("locked") и достаем оттуда необходимые данные.
Но еще интереснее дела обстоят с работой mapping. Посмотрите на следующий код:
NFTBoostVaultStorage.Registration storage registration = _getRegistrations()[msg.sender];
Здесь мы достаем структурные данные из памяти, которые хранятся в виде mapping! Вот его другие функции:
function _getRegistrations() internal pure returns (mapping(address => NFTBoostVaultStorage.Registration) storage) {
return NFTBoostVaultStorage.mappingAddressToRegistrationPtr("registrations");
}
function mappingAddressToRegistrationPtr(
string memory name
) internal pure returns (mapping(address => Registration) storage data) {
bytes32 offset = keccak256(abi.encodePacked(REGISTRATION_TYPEHASH, name));
assembly {
data.slot := offset
}
}
Я и сейчас на 100% не уверен как работает "под капотом" преобразование данные в _getRegistrations()[msg.sender], так, чтобы получился mapping. Но выглядит интересно.
Solidity не перестает меня удивлять!
В общем, если вас это заинтересовало, то советую самим посмотреть контракты и попробовать чуть лучше разобраться. Буду также рад, если дадите свои комментарии по этому вопросу.
P.S. Отдельное спасибо @elawbek, что помог мне чуть лучше понять, как это все работает.
#storage #mapping #proxy
В процессе аудита протокола Arcade встретился лицом к лицу с не совсем обычной организацией storage в контракте. Не совсем обычной для меня. Возможно, более опытные разработчики уже сталкивались с подобной реализацией.
Я могу ошибиться в некоторых моментах описания и прошу поправить меня, если вдруг, что не так.
P.S. В посте будут приведены ссылки на контракты из открытого конкурсного репозитория, актуального на время написания поста.
Итак, у нас есть прокси контакт - NFTBoostVault, который наследует функции от двух библиотек, которые и позволяют работать с памятью контракта так, чтобы при обновлениях не было коллизии в данных - Storage и NFTBoostVaultStorage.
Примечательно здесь то, что базовые типы данных, например address и uint, которые не поддерживают определенные места хранения в storage (как mapping), хранятся в структуре с одноименным названием, например:
struct Address {
address data;
}
Так в конструкторе или функции мы можем установить / обновить для них значения с помощью:
Storage.set(Storage.uint256Ptr("locked"), 1);
где Storage.uint256Ptr("locked") - место в памяти, а 1 - значения для установки.
Или также для типов address:
Storage.set(Storage.addressPtr("manager"), manager);
где manager - тип address из аргументов функции.
Доставать значения из такого слота памяти можно при помощи других функций из библиотеки:
function getIsLocked() public view override returns (uint256) {
return Storage.uint256Ptr("locked").data;
}
т.е. мы запрашиваем в storage слот с хешем uint256Ptr("locked") и достаем оттуда необходимые данные.
Но еще интереснее дела обстоят с работой mapping. Посмотрите на следующий код:
NFTBoostVaultStorage.Registration storage registration = _getRegistrations()[msg.sender];
Здесь мы достаем структурные данные из памяти, которые хранятся в виде mapping! Вот его другие функции:
function _getRegistrations() internal pure returns (mapping(address => NFTBoostVaultStorage.Registration) storage) {
return NFTBoostVaultStorage.mappingAddressToRegistrationPtr("registrations");
}
function mappingAddressToRegistrationPtr(
string memory name
) internal pure returns (mapping(address => Registration) storage data) {
bytes32 offset = keccak256(abi.encodePacked(REGISTRATION_TYPEHASH, name));
assembly {
data.slot := offset
}
}
Я и сейчас на 100% не уверен как работает "под капотом" преобразование данные в _getRegistrations()[msg.sender], так, чтобы получился mapping. Но выглядит интересно.
Solidity не перестает меня удивлять!
В общем, если вас это заинтересовало, то советую самим посмотреть контракты и попробовать чуть лучше разобраться. Буду также рад, если дадите свои комментарии по этому вопросу.
P.S. Отдельное спасибо @elawbek, что помог мне чуть лучше понять, как это все работает.
#storage #mapping #proxy
🔥6👍4
Foundry с 0. Часть 25
На следующей неделе у меня в планах рассмотреть invariant тесты и работу с подписями стандарта EIP-712. Вообще, давайте пройдемся по ближайшим планам с темой Foundry.
На данный момент вышло уже 25 постов, где я постарался объяснить базовые нюансы написания тестов. Осталось несколько более сложных тем:
1. Тесты с использованием контрактов Uniswap и Chainlink;
2. Дебаггинг;
3. Работа с L2, ZKsynk и ZeroLayer;
4. Тестирование мостов;
Информации по этим темам не так много, поэтому времени уйдет на подготовку постов чуть больше. Возможно, дальше они будут выходить не каждый день.
Также хотелось бы разобрать несколько примеров из серии Damn Vulnerable DeFi, Immunefi reports и реальные тесты протоколов из конкурсных аудитов.
В целом, работы нам где-то еще на месяц. Зато после этого мы качественно поднимем свои навыки работы с Foundry и написанием тестов!
А сегодня хочу рассказать вам о некоторых командах Foundry, о которых редко упоминают в обучающих видео.
1. Команда forge fmt
Простая и удобная команда, которая позволяет быстро отформатировать ваш код. Нет, она не исправит ваши ошибки в коде, но сможет убрать не нужные отступы и пробелы, сделает код визуально адекватным.
2. Команда forge doc
Она создает документацию по функциям и контрактам вашего протокола и сохраняет все в отдельные md файлы.
Кстати, для тех, кто не знал, есть прекрасный плаггин Markdown All in One (от Yu Zhang), который может отобразить md файлы в вашем редакторе в удобном читаемом виде.
3. Команда forge config
Она выводит все доступные опции для настройки конфигурационного файла foundry.toml. Можно копировать прямо с терминала, помещать в файл и корректировать необходимые настройки.
4. Кодирование структур
Структуры в Solidity кодируются в виде кортежей данных (tuple), подробнее об этом можно прочитать тут:
https://docs.soliditylang.org/en/latest/abi-spec.html#mapping-solidity-to-abi-types
В тестах Foundry также нужно иметь ввиду, как это все работает "под капотом". Например, возьмем простой тест:
Вот как эта функция, принимающая структуру, будет выглядеть в раскладке:
т.е. функция f принимает один аргумент из двух компонентов типа адрес и uint256.
4. Вывод отрицательных чисел в тестах
Через команду console.log мы можем выводить какую-либо информацию прямо из наших тестов в терминал. Однако с отрицательными числами так не выйдет.
В этом случае необходимо использовать - console.logInt().
5. Использование стандартных ошибок Foundry
В библиотеке StdErrors, которая по умолчанию наследуется в Test, основную библиотеку для проведения тестов, есть свой набор ошибок. Вы можете использовать их для своих тестов, например:
Полный список ошибок можно посмотреть тут:
https://book.getfoundry.sh/reference/forge-std/std-errors
Надеюсь, сегодня вы также узнали чуточку больше о Foundry.
Приятного обучения и легких выходных!
#foundry #lesson25
На следующей неделе у меня в планах рассмотреть invariant тесты и работу с подписями стандарта EIP-712. Вообще, давайте пройдемся по ближайшим планам с темой Foundry.
На данный момент вышло уже 25 постов, где я постарался объяснить базовые нюансы написания тестов. Осталось несколько более сложных тем:
1. Тесты с использованием контрактов Uniswap и Chainlink;
2. Дебаггинг;
3. Работа с L2, ZKsynk и ZeroLayer;
4. Тестирование мостов;
Информации по этим темам не так много, поэтому времени уйдет на подготовку постов чуть больше. Возможно, дальше они будут выходить не каждый день.
Также хотелось бы разобрать несколько примеров из серии Damn Vulnerable DeFi, Immunefi reports и реальные тесты протоколов из конкурсных аудитов.
В целом, работы нам где-то еще на месяц. Зато после этого мы качественно поднимем свои навыки работы с Foundry и написанием тестов!
А сегодня хочу рассказать вам о некоторых командах Foundry, о которых редко упоминают в обучающих видео.
1. Команда forge fmt
Простая и удобная команда, которая позволяет быстро отформатировать ваш код. Нет, она не исправит ваши ошибки в коде, но сможет убрать не нужные отступы и пробелы, сделает код визуально адекватным.
2. Команда forge doc
Она создает документацию по функциям и контрактам вашего протокола и сохраняет все в отдельные md файлы.
Кстати, для тех, кто не знал, есть прекрасный плаггин Markdown All in One (от Yu Zhang), который может отобразить md файлы в вашем редакторе в удобном читаемом виде.
3. Команда forge config
Она выводит все доступные опции для настройки конфигурационного файла foundry.toml. Можно копировать прямо с терминала, помещать в файл и корректировать необходимые настройки.
4. Кодирование структур
Структуры в Solidity кодируются в виде кортежей данных (tuple), подробнее об этом можно прочитать тут:
https://docs.soliditylang.org/en/latest/abi-spec.html#mapping-solidity-to-abi-types
В тестах Foundry также нужно иметь ввиду, как это все работает "под капотом". Например, возьмем простой тест:
contract Test {
struct MyStruct {
address addr;
uint256 amount;
}
function f(MyStruct memory t) public pure {}
}Вот как эта функция, принимающая структуру, будет выглядеть в раскладке:
{
"inputs": [
{
"components": [
{
"internalType": "address",
"name": "addr",
"type": "address"
},
{
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"internalType": "struct Test.MyStruct",
"name": "t",
"type": "tuple"
}
],
"name": "f",
"outputs": [],
"stateMutability": "pure",
"type": "function"
}т.е. функция f принимает один аргумент из двух компонентов типа адрес и uint256.
4. Вывод отрицательных чисел в тестах
Через команду console.log мы можем выводить какую-либо информацию прямо из наших тестов в терминал. Однако с отрицательными числами так не выйдет.
В этом случае необходимо использовать - console.logInt().
5. Использование стандартных ошибок Foundry
В библиотеке StdErrors, которая по умолчанию наследуется в Test, основную библиотеку для проведения тестов, есть свой набор ошибок. Вы можете использовать их для своих тестов, например:
vm.expectRevert(stdError.arithmeticError);
Полный список ошибок можно посмотреть тут:
https://book.getfoundry.sh/reference/forge-std/std-errors
Надеюсь, сегодня вы также узнали чуточку больше о Foundry.
Приятного обучения и легких выходных!
#foundry #lesson25
🔥3
Solidity Tip
Порой встречал это в конкурсных аудитах, но только сейчас осознал это как отдельный инструмент работы с маппингами.
В функциях возможно определить локальный маппинг, но с указанием на storage data location (не уверен, как это точно перевести).
Такой способ работы со вложенными маппингами поможет вам сократить расход газа в операциях.
#solidity #mapping #tip
Порой встречал это в конкурсных аудитах, но только сейчас осознал это как отдельный инструмент работы с маппингами.
В функциях возможно определить локальный маппинг, но с указанием на storage data location (не уверен, как это точно перевести).
Такой способ работы со вложенными маппингами поможет вам сократить расход газа в операциях.
#solidity #mapping #tip
👍9