Видимость, pure/view/payable, модификаторы доступа, constructor, fallback и receive
Модификатор доступа — это не просто синтаксический сахар, а один из главных инструментов безопасности контракта
Solidity имеет четыре уровня видимости для функций.
public — функция вызывается как извне (через транзакцию), так и внутри контракта и наследниками. Компилятор создаёт и внешний интерфейс, и internal-вызов.
external — функция вызывается только извне. Нельзя вызвать через this.foo() внутри контракта (технически можно, но это внешний вызов с накладными расходами). Эффективнее public для функций, которые не нужны внутри: параметры-массивы читаются из calldata без копирования в memory.
internal — доступна внутри контракта и наследникам, но не снаружи. Аналог protected в Java.
private — только внутри данного контракта. Наследники не видят. Важно: private не означает секретность — данные storage видны в блокчейне, просто другие контракты не могут вызвать функцию напрямую.
view — функция читает state, но не изменяет. Вызов off-chain (через eth_call) бесплатен. При вызове из другой транзакции газ всё равно тратится на накладные расходы.
pure — функция не читает и не изменяет state. Зависит только от аргументов. Полезна для математических утилит, хэш-функций, кодирования.
Функция без этих модификаторов может изменять state — и требует транзакции.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
contract Vault {
address public owner;
uint256 private balance;
constructor() {
owner = msg.sender;
}
// external + payable: принимает ETH
function deposit() external payable {
balance += msg.value;
}
// pure: чистые вычисления, не читает storage
function calculateFee(uint256 amount) public pure returns (uint256) {
return amount / 100; // 1%
}
// view: читает storage, не изменяет
function getBalance() external view returns (uint256) {
return balance;
}
}Функции, помеченные payable, могут принимать ETH вместе с вызовом. Если вызвать обычную функцию с ненулевым msg.value — транзакция отклонится.
msg.value — сумма ETH (в Wei), отправленная с текущим вызовом. Константа в рамках вызова функции.
msg.sender — адрес, который вызвал текущую функцию (может быть EOA или другой контракт).
Модификатор — это многоразовый "декоратор" для функций. Он добавляет логику до и/или после тела функции, не дублируя код.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
contract Ownable {
address public owner;
constructor() {
owner = msg.sender;
}
// Модификатор: проверяет, что вызывает владелец
modifier onlyOwner() {
require(msg.sender == owner, "Not owner");
_; // здесь вставляется тело функции
}
// Модификатор с параметром
modifier costs(uint256 price) {
require(msg.value >= price, "Insufficient payment");
_;
}
function withdraw() external onlyOwner {
payable(owner).transfer(address(this).balance);
}
function premiumAction() external payable costs(0.1 ether) {
// действие доступно только при оплате минимум 0.1 ETH
}
function transferOwnership(address newOwner) external onlyOwner {
require(newOwner != address(0), "Zero address");
owner = newOwner;
}
}Символ _; — место, куда вставляется тело декорируемой функции. Можно поставить до или после проверок. Можно поставить несколько раз — тело выполнится несколько раз (редкий приём). Можно использовать несколько модификаторов на одной функции — они применяются слева направо.
Важно: модификаторы не являются функциями в смысле ABI — компилятор inline вставляет их код в каждую функцию, к которой они применены.
Конструктор выполняется ровно один раз — при деплое контракта. После этого его код не хранится в контракте (экономия газа). Конструктор не имеет имени с версии Solidity 0.4.22 — просто ключевое слово constructor.
Конструктор может быть payable — тогда при деплое можно отправить ETH. Если конструктор не объявлен, создаётся пустой конструктор по умолчанию.
Это специальные функции, вызываемые автоматически в определённых ситуациях.
receive() external payable — вызывается, когда контракт получает ETH без данных (пустой calldata). Обязана быть payable. Используется для простых ETH-кошельков.
fallback() external [payable] — вызывается, если ни одна функция не совпала с selector в calldata, или если calldata пустой, но receive не определён. Может быть помечена payable для приёма ETH.
contract ETHReceiver {
event Received(address sender, uint256 amount);
event FallbackCalled(address sender, bytes data);
// Принимает ETH без данных
receive() external payable {
emit Received(msg.sender, msg.value);
}
// Принимает любые вызовы с неизвестным selector
fallback() external payable {
emit FallbackCalled(msg.sender, msg.data);
}
}Это поведение важно для proxy-контрактов: proxy перехватывает все вызовы через fallback и перенаправляет их через delegatecall к реализации. Этот паттерн разберём в финальной теме.
Вопросы ещё не добавлены
Вопросы для этой подтемы ещё не добавлены.