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

Тут обратите внимание, что нужно обязательно прописывать "_;", иначе функция, к которой прикреплен модификатор, не сработает.


#modifier #модификатор
🔥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
Организация prank в Foundry

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

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

vm.startPrank(user);

и в завершении:

vm.stopPrank();

Порой для тестов это приходится прописывать довольно часто. Так вот, в этом контракте придумали поместить это все в модификатор:

modifier prank(address from) {
  vm.startPrank(from);
  _;
  vm.stopPrank();
}

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

function test_addLiquidity() public prank(user) {}

На мой взгляд достаточно элегантное решение.

#foundry #test #modifier #prank
👍141
Контроль доступа и голосование за предложения

Интересную реализацию задумки голосования встретил в конкурсном протоколе Axelar. Опишу кратко.

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

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

В Axelar голосование происходит в модификаторе!

P.S. Я сделаю более упрощенное описание процедуры для понимания ее сути.

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

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

function execute(
address target,
bytes calldata callData,
uint256 nativeValue
) external payable onlySigners {
...
}

Обратите внимание на модификатор onlySigners, там и происходит все действие!

modifier onlySigners() {
if (!signers.isSigner[msg.sender]) revert NotSigner();

bytes32 topic = keccak256(
msg.data);
Voting storage voting = votingPerTopic[signerEpoch][topic];

if (voting.hasVoted[msg.sender]) revert AlreadyVoted();

voting.hasVoted[msg.sender] = true;

uint256 voteCount = voting.voteCount + 1;

if (voteCount < signers.threshold) {
voting.voteCount = voteCount;
return;
}

voting.voteCount = 0;

uint256 count = signers.accounts.length;

for (uint256 i; i < count; ++i) {
voting.hasVoted[signers.accounts[i]] = false;
}

emit MultisigOperationExecuted(topic);

_;
}

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

Далее фиксируется голос пользователя. Если проходного минимума не хватает, то идет return и до выполнения самой функции execute дело не доходит!

Если же голоса набраны, то модификатор делает свое дело и execute срабатывает!

По моему, сделано прикольно! Такого я еще ни разу не видел.

#modifier
👍101
Solidity hints. Часть 14

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

А мы продолжаем изучать нюансы Solidity и сегодня на очереди следующие пункты из репо Chinmaya:

21. Without a payable keyword in function declaration, it will auto reject all ether sent to it. It will revert.

что в переводе:

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

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

Вы можете сами зайти на сайт с подбором багов из контестов - Solodit - и ввести в поиск missing payable. На данный момент там 67 багов по этой теме...

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

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

22. Modifiers can also be defined in libraries but their use is limited to functions of the same library

что в переводе:

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

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

Библиотеки не могут хранить Эфир, не имеют своего storage, а то как они работают с вашим контрактом при наличии internal / external функций и говорить не стоит...

А тут еще и модификаторы подоспели.

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

#modifier #payable #library
👍3
Solidity hints. Часть 15

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

Было несколько годных статей на Alchemy по этой теме, да и Патрик Коллинс выпустил 4 часовое видео. Для первого касания хватит, а там посмотрим чего нам не будет хватать для понимания темы.

Ну, а пока что, еще пара пунктов:

23. Multiple modifiers are applied to a function by specifying them in a whitespace-separated list and are evaluated in the order presented. Modifier Order Matters

что в переводе:

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

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

contract MyToken is ERC20, ERC20Permit, Ownable {
}


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

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

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

pragma solidity ^0.4.18;

contract modifierTest {
uint public modState1;
uint public modState2;
uint public modState3;

modifier modA() {
modState1 = modState1 + 1;
_;
}

modifier modB() {
modState2 = modState2 + 1;
_;
modState2 = modState2 + 1;
_;
}

function func() public modA modB {
modState3 = modState3 + 1;
}
}


Как думаете, как значения получатся после выполнения функции? И ответ на этот вопрос моно будет найти в следующем пункте:

24. The _ symbol can appear in the modifier multiple times. Each occurrence is replaced with the function body. Symbols introduced in the modifier are not visible in the function

что в переводе:

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

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

#modifier
🔥3👍2