Учебник по Solidity: Все о модификаторах

от автора

Что такое модификатор в Solidity?

В документации Solidity модификаторы определяются следующим образом:

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

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

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

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

Когда использовать модификатор в Solidity?

Основное применение модификаторов — автоматическая проверка условия перед выполнением функции. Если функция не удовлетворяет требованию модификатора, возникает исключение, и выполнение функции прекращается.

Как создавать и использовать модификаторы?

Модификаторы могут быть созданы (объявлены) следующим образом:

modifier MyModifier {    // здесь код модификатора... }

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

modifier ModifierWithArguments(uint256 a) {    // ... }  modifier ModifierWithoutArguments() {    // ... }  modifier ModifierWithoutArguments {    // ... }

Как работают модификаторы?

Давайте начнем с базовых примеров, приведенных ниже.

modifier OnlyOwner {    require(msg.sender == owner);    _; }

Символ _;

Символ _; называется подстановочным знаком. Он объединяет код функции с кодом модификатора.

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

Используя термины документации Solidity, этот символ «возвращает поток выполнения к исходному коду функции».

Модификатор должен содержать символ _; в своем теле. Это обязательно.

Куда поместить _; ?

Вместо _; будет подставлена функция, поэтому выполнение функции зависит от того места, где вы укажите этот символ: до, посредине или после основного кода модификатора.

modifier SomethingBefore {    require(/* сначала проверьте что-нибудь */);    _; // возобновить выполнение функции }  modifier SomethingAfter {    _; // запускаем сперва функцию    require(/* затем проверяем что-нибудь */) }

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

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

function isOkay() public view returns(bool) {    // выполнить проверку истинности    return true; }  function isAuthorised(address _user) public view returns(bool) {    // логика проверки авторизации _user    return true; }  modifier OnlyIfOkAndAuthorised {    require(isOkay());    require(isAuthorised(msg.sender));    _; }

Передача аргументов модификаторам

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

modifier Fee(uint _fee) {    if (msg.value >= _fee) {        _;    } }

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

Давайте проиллюстрируем это на простом примере.

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

Модификатор с аргументами может исполнить такое поведение.

contract Vault {     modifier fee(uint256 _fee) {         if (msg.value != _fee) {             revert("Вы должны заплатить комиссию, чтобы снять свои эфиры");         } else {             _;         }     }      function deposit(address _user, uint256 _amount) external {         // ...     }      function withdraw(uint256 _amount) external payable fee(0.025 ether) {         // ...     } } 

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

modifier notSpecificAddress (address _user) {   if (_user === msg.sender) throw;   _; }  function someFunction(address _user) notSpecificAddress("0x......") {    // ... }

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

Применение нескольких модификаторов к функции.

К одной функции можно применить несколько модификаторов. Это можно сделать следующим образом:

contract OwnerContract {     address public owner = msg.sender;     uint256 public creationTime = now;      modifier onlyBy(address _account) {         require(msg.sender == _account, "Отправитель не авторизован.");         _;     }     modifier onlyAfter(uint256 _time) {         require(now >= _time, "Функция вызвана слишком рано");         _;     }      function disown() public onlyBy(owner) onlyAfter(creationTime + 6 weeks) {         delete owner;     } }

Модификаторы будут выполняться в том порядке, в котором они определены, то есть слева направо. Так, в приведенном выше примере функция будет проверять следующие условия перед запуском:

  1. onlyBy(…) : является ли адрес, вызывающий смарт-контракт, владельцем?

  2. onlyAfter(…) : является ли вызывающий владельцем смарт-контракта более 6 недель?

Переопределение модификаторов

Как и функции, модификаторы, определенные в одном смарт-контракте, могут быть переопределены другими смарт-контрактами, которые являются производными от него. Как следует из документации Solidity:

  • Модификаторы являются наследуемыми свойствами смарт-контрактов и могут быть переопределены производными смарт-контрактами.

Поэтому символы, введенные в модификатор, не видны в функции (поскольку модификатор может измениться, если он будет переопределен в производном смарт-контракте).

Модификаторы с перечислениями

Если ваш смарт-контракт содержит переменную типа enum, вы можете проверить значение, которое она содержит, передав один из доступных вариантов в качестве аргумента модификатору.

enum State { Created, Locked, Inactive }  State state;  modifier isState(State _expectedState) {    require(state == _expectedState);    _; }

Модификаторы на практике

Только владелец или определенные пользователи

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

modifier OnlyBy(address _account) {    require(msg.sender == _account, "отправитель не авторизован");    _; }

Поддержка нескольких администраторов

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

modifier onlyAdmins {     // функция isUserAdmin проверяет вхождение пользователя      // в список админов     if (!isUserAdmin(msg.sender)) throw;     _;   }

Проверка данных

Еще один отличный вариант использования модификаторов — проверка вводимых данных.

Ниже приведены некоторые примеры, основанные на различных типах данных.

modifier throwIfAddressIsInvalid(address _target) {    if (_target == 0x0) throw;    _; }  modifier throwIfIsEmptyString(string _id) {    if (bytes(_id).length == 0) throw;    _; }  modifier throwIfEqualToZero(uint _id) {    if (_id == 0) throw;    _;  }  modifier throwIfIsEmptyBytes32(bytes32 _id) {    if (_id == "") throw;    _; }

Проверка истечения промежутка времени

modifier OnlyAfter(uint _time) {    require(now >= _time, "функция вызвана слишком рано!");    _; }

Возврат случайно отправленного eth

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

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

modifier refundEtherSentByAccident() {    if(msg.value > 0) throw;    _; }

Взимать сборы

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

modifier Fee (uint _fee) {    if (msg.value >= _fee) {        _;    } }

Вернуть сдачу

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

Пример реализации выглядит следующим образом.

modifier GiveChangeBack(uint _amount) {    _;    if (msg.value > _amount) {        msg.sender.transfer(msg.value - _amount);    } }

Переключение между состояниями

Модификатор может быть использован для сложных образцов проектирования. К ним относится образец проектирования — Машина состояний.

Предотвращение повторного вызова

Посмотрите на модификатор ReentrancyGuard из набора OpenZeppelin.

Запретить смарт-контрактам взаимодействовать со смарт-контрактами

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

В результате это не позволяет другим смарт-контрактам вызывать функцию, к которой прикреплен модификатор.

modifier onlyEOA() {    require(msg.sender == tx.origin, "Must use EOA");    _;   }

Проверка типа account’а

Этот модификатор дополняет предыдущий, который проверял, принадлежит ли address человеку или является смарт-контрактом. А этот модификатор проверяет есть ли по адресу код кода, используя опкод extcodesize(…) через assembly. Если есть, то это смарт-контракт.

modifier onlyHuman {    uint size;    address addr = msg.sender;    assembly { size := extcodesize(addr) }    require(size == 0, "разрешено только людям! (присутствует код по адресу)");    _; }

Заключение

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

Какие самые интересные модификаторы вы писали или находили? Поделитесь в ответах.

Серия статей

  1. Учебник по Solidity. Всё о модификаторах

  2. Учебник по Solidity. Все об адресах


ссылка на оригинал статьи https://habr.com/ru/articles/572004/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *