Функции: видимость и мутабельность
У каждой функции в Solidity два независимых ярлыка: кто её может вызвать и что она делает с состоянием.
Забыли пометить функцию какview— переплатили газ. Сделали внутреннюю функциюpublic— открыли дыру. Сигнатура функции в Solidity — это уже половина безопасности.
Объявление функции несёт несколько модификаторов. Первая ось — видимость: public (вызывается отовсюду), external (только снаружи, дешевле для больших аргументов), internal (этот контракт и наследники), private (только этот контракт). Вторая ось — мутабельность: view (только читает состояние), pure (не трогает состояние вообще), payable (может принимать ETH), либо без пометки (меняет состояние).
ВИДИМОСТЬ МУТАБЕЛЬНОСТЬ
========= ============
external -> извне pure -> не читает/не пишет состояние
public -> везде view -> читает, но не пишет
internal -> +наслед. payable -> принимает ETH
private -> только (без) -> пишет состояние (нужна транзакция)
здесь
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
contract Wallet {
uint256 public total;
// меняет состояние — нужна транзакция и газ
function deposit() external payable {
total += msg.value; // msg.value — сколько ETH прислали
}
// только читает
function getTotal() external view returns (uint256) {
return total;
}
// чистая функция — ничего из состояния не трогает
function double(uint256 x) external pure returns (uint256) {
return x * 2;
}
}
Как работает под капотом (EVM/газ)
Когда транзакция приходит в контракт, EVM по первым 4 байтам calldata (селектору функции) выбирает нужную функцию. external-функции читают аргументы прямо из calldata, не копируя их в память — это дешевле для больших массивов. view и pure при внешнем вызове исполняются через eth_call без газа, но если их вызвать из меняющей функции — газ за чтение/вычисление всё равно платится в рамках транзакции. payable разрешает прикреплять ETH; без неё транзакция с ненулевым value откатится.
# Та же логика на Python: «движок» вызова по селектору
class Wallet:
def __init__(self):
self.total = 0
def deposit(self, value): # payable
self.total += value
def get_total(self): # view
return self.total
@staticmethod
def double(x): # pure
return x * 2
# имитация диспетчеризации по имени функции (селектору)
w = Wallet()
calls = [("deposit", 5), ("deposit", 3), ("get_total", None)]
for name, arg in calls:
fn = getattr(w, name)
print(name, "->", fn(arg) if arg is not None else fn())
print("double(21) =", Wallet.double(21))
«Та же логика на Python ▶». EVM так же выбирает функцию по идентификатору и исполняет её; deposit меняет состояние, get_total и double — нет.
Частые ошибки
- Сделать вспомогательную функцию
public, хотя её должны вызывать только внутри — это лишняя точка входа и риск. - Забыть
payableна функции, которая должна принимать ETH — пользователи не смогут отправить эфир. - Не пометить читающую функцию как
view— теряется и оптимизация, и читаемость.
Best practices
- Давайте минимально необходимую видимость: по умолчанию
private/internal, повышайте только осознанно. - Для функций, вызываемых только снаружи и с большими аргументами, предпочитайте
externalвместоpublic. - Помечайте
view/pureвезде, где можно: компилятор проверит, что вы случайно не пишете состояние.
Конструктор, receive и fallback
Помимо обычных функций есть три особые. Конструктор (constructor) исполняется ровно один раз — в момент деплоя — и задаёт начальное состояние (например, владельца). После деплоя его вызвать нельзя. Две специальные функции отвечают за приём «голого» ETH без данных: receive() external payable срабатывает, когда контракту присылают эфир пустой транзакцией, а fallback() — когда вызвали несуществующую функцию или прислали данные, под которые нет совпадения. Если ни одной из них нет и контракт не payable, попытка отправить ему ETH откатится. Понимание этих функций важно для безопасности: именно receive/fallback атакующего срабатывают во время reentrancy, когда жертва переводит ему эфир, — об этом мы подробно поговорим в разделе безопасности.
Итоги
Функция описывается двумя осями: видимость (кто вызывает) и мутабельность (что делает с состоянием). Правильные ярлыки — это и газ, и безопасность, и читаемость. Дальше — модификаторы доступа и require.