китаев.tech

Модификаторы — modifiers

Как писать modifiers в Solidity, использовать `_` для тела функции и выносить повторяющиеся проверки доступа.

Modifier добавляет проверку к функции

modifier — это переиспользуемый блок кода, который выполняется перед функцией, после функции или вокруг неё. Чаще всего modifiers используют для проверок: только owner, продажа открыта, адрес не нулевой, сумма больше нуля.

Символ _ внутри modifier показывает, куда Solidity подставит тело функции.


Пишем продажу membership с modifiers

Создайте файл contracts/MembershipSale.sol:

solidity
// 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 может выполнять код до и после тела функции:

solidity
modifier example() {
    // выполнится до тела функции
    _;
    // выполнится после тела функции
}

В обычных проверках код пишут до _. Код после _ используют реже: например, для финальной проверки инварианта или освобождения lock в reentrancy guard.

Если в modifier нет _, тело функции никогда не выполнится. Почти всегда это ошибка.


Когда использовать modifier

Modifier хорош для коротких повторяющихся проверок: доступ, пауза, whitelist, проверка нулевого адреса, проверка суммы.

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

Правило простое: modifier должен отвечать на вопрос “можно ли выполнить функцию?”, а не выполнять основное действие.


Частые ошибки

Забыть _ → функция с таким modifier не выполнит своё тело. Проверяйте, что _ есть в каждом modifier, который должен пропускать выполнение дальше.

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

Не учитывать порядок modifiersonlyOwner whenSaleOpen и whenSaleOpen onlyOwner могут вернуть разные ошибки при одном и том же вызове.


Что дальше

Материалы китаev.tech публикуются в образовательных целях и не являются инвестиционной рекомендацией. Примеры кода и описания протоколов — для обучения; использование в продакшне на ваш собственный риск. Дисклеймер и политика конфиденциальности.