Функции
Внутри контракта мы определяем функции, которые могут быть вызваны транзакцией EOA или другим контрактом. В нашем примере с Faucet у нас есть две функции: withdraw и (безымянная) функция fallback. Синтаксис, который мы используем для объявления функции в Solidity, следующий:
function FunctionName([parameters]) {public|private|internal|external}
[pure|view|payable] [modifiers] [returns (return types)]
Давайте рассмотрим каждый из этих компонентов:
FunctionName
Имя функции, которое используется для вызова функции в транзакции (из EOA), из другого контракта или даже внутри одного контракта. Одна функция в каждом контракте может быть определена как функция fallback с помощью ключевого слова "fallback" или функция эфира получения, определенная с помощью ключевого слова "receive". Если она присутствует, функция получения эфира вызывается всякий раз, когда данные вызова пусты (независимо от того, получен эфир или нет). В противном случае, функция fallback вызывается, когда не названа никакая другая функция. Функция обратного вызова не может иметь аргументов или возвращать что-либо.
параметры
После имени мы указываем аргументы, которые должны быть переданы функции, с их именами и типами. В нашем примере с краном мы определили uint withdraw_amount как единственный аргумент функции withdraw. Следующий набор ключевых слов (public, private, internal, external) определяет видимость функции:
public
Public - это значение по умолчанию; такие функции могут вызываться другими контрактами или транзакциями EOA, или изнутри контракта. В нашем примере с краном обе функции определены как публичные.
external
Внешние функции подобны публичным функциям, за исключением того, что их нельзя вызывать изнутри контракта, если только они явно не снабжены ключевым словом this.
interbal
Внутренние функции доступны только изнутри контракта - они не могут быть вызваны другим контрактом или транзакцией EOA. Они могут быть вызваны производными контрактами (теми, которые наследуют данный контракт).
private
Частные функции подобны внутренним функциям, но не могут быть вызваны производными контрактами.
Следует помнить, что термины "внутренний" и "приватный" несколько вводят в заблуждение. Любая функция или данные внутри контракта всегда видны в публичном блокчейне, то есть любой может увидеть код или данные. Ключевые слова, описанные здесь, влияют только на то, как и когда функция может быть вызвана.
Второй набор ключевых слов (pure, constant, view, payable) влияет на поведение функции:
view
Функция, помеченная как view, обещает не изменять никакое состояние. Термин constant - это псевдоним для view, который будет устаревшим в будущем релизе. В настоящее время компилятор не применяет модификатор view, выдавая только предупреждение, но ожидается, что в версии 0.5 Solidity это ключевое слово станет принудительным.
pure
Чистая функция - это функция, которая не читает и не записывает переменные в хранилище. Она может работать только с аргументами и возвращать данные, не обращаясь ни к каким хранимым данным. Чистые функции предназначены для поощрения программирования в декларативном стиле без побочных эффектов и состояния.
payable
Оплачиваемая функция - это функция, которая может принимать входящие платежи. Функции, не объявленные как оплачиваемые, будут отклонять входящие платежи. Есть два исключения, обусловленные дизайнерскими решениями в EVM: платежи coinbase и наследование SELFDESTRUCT будут оплачены, даже если функция отката не объявлена как оплачиваемая, но это имеет смысл, поскольку выполнение кода в любом случае не является частью этих платежей.
Как вы можете видеть в нашем примере с Faucet, у нас есть одна оплачиваемая функция (функция fallback), которая является единственной функцией, которая может принимать входящие платежи.
Модификаторы функций
Solidity предлагает специальный тип функции, называемый модификатором функции. Вы применяете модификаторы к функциям, добавляя имя модификатора в объявление функции. Модификаторы чаще всего используются для создания условий, которые применяются ко многим функциям в рамках контракта. У нас уже есть условие контроля доступа в нашей функции destroy. Давайте создадим модификатор функции, выражающий это условие:
modifier onlyOwner {
require(msg.sender == owner);
_;
}
Этот модификатор функции, названный onlyOwner, устанавливает условие для любой функции, которую он модифицирует, требуя, чтобы адрес, хранящийся в качестве владельца контракта, совпадал с адресом msg.sender транзакции. Это базовый шаблон проектирования для контроля доступа, позволяющий только владельцу контракта выполнять любую функцию, имеющую модификатор onlyOwner.
Возможно, вы заметили, что в модификаторе нашей функции есть своеобразный синтаксический "заполнитель" - знак подчеркивания, за которым следует точка с запятой (_;). Это место заменяется кодом функции, которая модифицируется. По сути, модификатор "оборачивается" вокруг модифицируемой функции, помещая ее код в место, обозначенное символом подчеркивания.
Чтобы применить модификатор, добавьте его имя к объявлению функции. К функции может быть применено более одного модификатора; они применяются в той последовательности, в которой они объявлены, в виде списка, разделенного пробелами.
Давайте перепишем нашу функцию destroy, чтобы использовать модификатор onlyOwner:
function destroy() public onlyOwner {
selfdestruct(owner);
}
Имя модификатора функции (onlyOwner) находится после ключевого слова public и говорит нам, что функция destroy модифицируется модификатором onlyOwner. По сути, вы можете прочитать это как "Только владелец может уничтожить этот контракт". На практике полученный код эквивалентен "обертыванию" кода из onlyOwner вокруг destroy.
Модификаторы функций - чрезвычайно полезный инструмент, поскольку они позволяют нам писать предварительные условия для функций и применять их последовательно, делая код более легким для чтения и, как следствие, более легким для аудита безопасности. Чаще всего они используются для контроля доступа, но они достаточно универсальны и могут применяться для множества других целей.
Внутри модификатора можно получить доступ ко всем значениям (переменным и аргументам), видимым для модифицируемой функции. В данном случае мы можем получить доступ к переменной-владельцу, которая объявлена внутри контракта. Однако обратное не верно: вы не можете получить доступ ни к одной из переменных модификатора внутри модифицированной функции.