Solidity. Смарт контракты и аудит
2.63K subscribers
246 photos
7 videos
18 files
549 links
Обучение Solidity. Уроки, аудит, разбор кода и популярных сервисов
Download Telegram
Struct используется довольно часто в сложных контрактах, поэтому знать этот тип данных не только нужно, но и важно!

В struct мы можем объявлять специальные поля, как в примере на скрине. При этом struct может хранить другие сложные типы данных, типа массивов и mapping.

#struct #типыданных
🔥1
Обратите внимание на урок с 21 минуты! Здесь рассказывают, как обращаться в функциях к mapping, который содержит struct и т.д. Сложный момент, особенно для новичков!

#struct #типыданных
2🔥1
Уязвимость в struct

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

Рассмотрим просто контракт:

contract NameRegistrar {

    bool public unlocked = false;  // registrar locked, no name updates

   
struct NameRecord {
        bytes32 name;
        address mappedAddress;
    }

    mapping(address => NameRecord) public registeredNameRecord;
    mapping(bytes32 => address) public resolve;

    function register(bytes32 _name, address _mappedAddress) public {
        NameRecord newRecord;
       
newRecord.name = _name;
        newRecord.mappedAddress = _mappedAddress;

        resolve[_name] = _mappedAddress;
        registeredNameRecord[msg.sender] = newRecord;

        require(unlocked);
    }
}

Ничего странного не замечаете? Ошибок каких? Вроде бы их нет. Но есть один недочет.

Тут в контракте newRecord не инициализируется. Поэтому, когда в последующих строках мы устанавливаем значения:

newRecord.name = _name;
newRecord.mappedAddress = _mappedAddress;

они указывают на 0 и 1 слот в памяти, что приводит к установке значений в unlocked. И если мы передадим нужное значение в _name, то можно установить его, как true.

Так можно взломать этот контракт.

Вернее было бы оформить запись в struct так:

NameRecord memory newRecord = NameRecord({name: _name, mappedAddress: _mappedAddress});

Лично я не знал, что в этом случае структура может указывать на слоты памяти переменных. Поэтому важно инициализировать правильно.

Компилятор может указать вам на этот недочет, а в некоторых случая и остановить компиляцию контракта. Будьте внимательны со struct.

#struct #security
🤯2
Передача параметров в struct

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

Итак, у нас есть контракт и struct, например:

interface IExternalContract{

struct one {
address user;
unit amount;
}

function callOne(one[] calldata data) external;

}

и в него из другого контракта нужно передать информацию.

Сделать можно так:

В нужном контракте дублирует struct и передаем его аргументы с помощью abi.encodeWithSignature:

contract YourContract {

  address public contractAddr;

  constructor(address _contractAddr) {

    contractAddr = _contractAddr;

  }

  function callOne(
IExternalContract.one[] calldata data) public {
    IExternalContract(contractAddr).callOne(data);
}
}

Кто-то уже имел дело с передачей инфы в struct? Как справлялись?

#struct
Хэширование в EIP712

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

Вообще EIP712 был создан для того, чтобы можно было подписывать сообщения, которые состоят не только из строк, но и более сложных параметров: например, struct.

И сегодня разберем, как шифруются структуры. Возьмем такой код:

struct Parent {
    uint s;
    Child[] children;
}

Child {
   uint a;
   uint b;
}

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

В самом конце, берется S и хеш структуры и высчитывается уже конечный хеш Parent.

#erc712 #struct
Storage Structs

Еще одна потрясающая статья от автора The File Pattern, в которой он разбирает вопрос использования структур для хранения данных при использовании прокси контрактов.

Статья мне приглянулась еще тем, что я сам участвовал в конкурсных аудитах Astaria и Drips. Тогда я хоть и понял, что данные берутся из определённого слота, но не понимал зачем это сделано.

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

Этот паттерн предлагает создавать структуру данных (struct) и помещать ее в слот "далеко" в памяти при помощи формулы EIP-1967, о которой писалось выше.

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

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

#storage #eip1967 #struct
2👍1
Пример упаковки структур в маппинге

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

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

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

#mapping #struct
2👍1
Как я понял такие способы работы с библиотеками позволяют также сократить размер самого контракта, а также разделить служебные функции от логики остальных.

Интересная реализация, не так ли?

#library #mapping #struct
👍3