Модификаторы — modifiers
Как писать modifiers в Solidity, использовать `_` для тела функции и выносить повторяющиеся проверки доступа.
Modifier добавляет проверку к функции
modifier — это переиспользуемый блок кода, который выполняется перед функцией, после функции или вокруг неё. Чаще всего modifiers используют для проверок: только owner, продажа открыта, адрес не нулевой, сумма больше нуля.
Символ _ внутри modifier показывает, куда Solidity подставит тело функции.
Пишем продажу membership с modifiers
Создайте файл contracts/MembershipSale.sol:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
contract MembershipSale {
address public immutable owner;
uint256 public immutable pricePerMembership;
bool public saleOpen;
mapping(address => uint256) public memberships;
event SaleOpened(address indexed owner);
event SaleClosed(address indexed owner);
event MembershipBought(address indexed buyer, uint256 amount, uint256 paid);
event Withdrawn(address indexed owner, address indexed to, uint256 amount);
error OnlyOwner(address caller);
error SaleIsClosed();
error ZeroAmount();
error NotEnoughEth(uint256 required, uint256 received);
error ZeroAddress();
error EthTransferFailed();
modifier onlyOwner() {
if (msg.sender != owner) {
revert OnlyOwner(msg.sender);
}
_;
}
modifier whenSaleOpen() {
if (!saleOpen) {
revert SaleIsClosed();
}
_;
}
modifier validAmount(uint256 amount) {
if (amount == 0) {
revert ZeroAmount();
}
_;
}
constructor(uint256 initialPricePerMembership) {
if (initialPricePerMembership == 0) {
revert ZeroAmount();
}
owner = msg.sender;
pricePerMembership = initialPricePerMembership;
}
function openSale() external onlyOwner {
saleOpen = true;
emit SaleOpened(msg.sender);
}
function closeSale() external onlyOwner {
saleOpen = false;
emit SaleClosed(msg.sender);
}
function buy(uint256 amount) external payable whenSaleOpen validAmount(amount) {
uint256 requiredPayment = amount * pricePerMembership;
if (msg.value < requiredPayment) {
revert NotEnoughEth(requiredPayment, msg.value);
}
memberships[msg.sender] += amount;
uint256 refund = msg.value - requiredPayment;
if (refund > 0) {
(bool refundSuccess, ) = msg.sender.call{value: refund}("");
if (!refundSuccess) {
revert EthTransferFailed();
}
}
emit MembershipBought(msg.sender, amount, requiredPayment);
}
function withdraw(address payable to) external onlyOwner {
if (to == address(0)) {
revert ZeroAddress();
}
uint256 amount = address(this).balance;
(bool success, ) = to.call{value: amount}("");
if (!success) {
revert EthTransferFailed();
}
emit Withdrawn(msg.sender, to, amount);
}
}Этот контракт продаёт membership-доступы. Owner открывает и закрывает продажу, пользователь покупает доступы, а повторяющиеся проверки вынесены в modifiers.
Как это работает
modifier onlyOwner() — объявляет modifier без параметров. Он проверяет, что функцию вызвал владелец контракта.
_ — место, куда подставится тело функции. Если проверка до _ откатит транзакцию, тело функции не выполнится.
function openSale() external onlyOwner — применяет modifier к функции. Перед saleOpen = true Solidity выполнит код из onlyOwner.
modifier whenSaleOpen() — защищает покупку от вызова, когда продажа закрыта.
modifier validAmount(uint256 amount) — modifier с параметром. Функция передаёт в него свой аргумент: validAmount(amount).
function buy(...) external payable whenSaleOpen validAmount(amount) — применяет два modifier подряд. Они выполняются слева направо: сначала whenSaleOpen, потом validAmount.
Порядок выполнения
Modifier может выполнять код до и после тела функции:
modifier example() {
// выполнится до тела функции
_;
// выполнится после тела функции
}В обычных проверках код пишут до _. Код после _ используют реже: например, для финальной проверки инварианта или освобождения lock в reentrancy guard.
Если в modifier нет _, тело функции никогда не выполнится. Почти всегда это ошибка.
Когда использовать modifier
Modifier хорош для коротких повторяющихся проверок: доступ, пауза, whitelist, проверка нулевого адреса, проверка суммы.
Не прячьте в modifier сложную бизнес-логику. Если modifier меняет много state или делает внешние вызовы, функцию сложнее читать и проверять на безопасность.
Правило простое: modifier должен отвечать на вопрос “можно ли выполнить функцию?”, а не выполнять основное действие.
Частые ошибки
Забыть _ → функция с таким modifier не выполнит своё тело. Проверяйте, что _ есть в каждом modifier, который должен пропускать выполнение дальше.
Прятать важные изменения state в modifier → читатель ожидает проверки, а не бизнес-логику. Основные изменения лучше держать внутри функции.
Не учитывать порядок modifiers → onlyOwner whenSaleOpen и whenSaleOpen onlyOwner могут вернуть разные ошибки при одном и том же вызове.
Что дальше
- Кастомные ошибки — Custom Errors — используйте typed errors внутри modifiers
- Events — объявление и emit — записывайте успешные действия после проверок
- Mapping и вложенные маппинги — храните роли, whitelist и лимиты для access modifiers