Организация 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
Post #832
1.03K
- 🔥 6
- 👍 4