Найти в Дзене
#Дружининъ

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

Что такое модификатор в Solidity? В документации Solidity модификаторы определяются следующим образом: Модификаторы можно использовать для изменения поведения функций декларативным способом. Из этого определения можно понять, что модификатор направлен на изменение поведения функции, к которой он присоединен. Например, автоматическая проверка условий перед выполнением функции (для этого модификаторы в основном и используются). Модификаторы уменьшают избыточность кода. Вы можете повторно использовать один и тот же модификатор в нескольких функциях, если вы проверяете одно и то же условие в смарт-контракте. Когда использовать модификатор в Solidity? Основное применение модификаторов - автоматическая проверка условия перед выполнением функции. Если функция не удовлетворяет требованию модификатора, возникает исключение, и выполнение функции прекращается. Как создавать и использовать модификаторы? Модификаторы могут быть созданы (объявлены) следующим образом: modifier MyModifier {
// зд
Оглавление

Что такое модификатор в 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, "разрешено только людям! (присутствует код по адресу)");
_;
}

Заключение

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

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