Структура смарт-контрактов на Solidity
Тезисно
- Контракты в solidity похожи на концепцию классов в объектно-ориентированных языках, они могут наследоваться и могут наследовать другие контракты.
- Мы можем иметь объявления переменных состояния, функций, событий, ошибок и т.д.
- Существуют также некоторые специальные типы контрактов, такие как библиотеки и интерфейсы, о которых мы поговорим позже.
- Контракты сами по себе являются большой темой, и мы углубимся в нее в следующих частях.
В этой статье мы не будем усложнять и просто изучим различные компоненты контракта.
Переменные состояния
Переменные состояния — это переменные, значение которых постоянно хранится в памяти. Ниже приведен пример такой переменной
uint storedData;
- Переменные состояния могут быть объявлены как
public
,internal
,private
. - Когда переменная состояния объявляется публичной, компилятор автоматически генерирует функцию getter для этой переменной.
- Если мы попытаемся получить доступ к публичной переменной изнутри того же контракта, используя ключевое слово thethis, то будет вызвана функция
getter.
Если мы попытаемся обратиться к переменной состояния без кейворда this, то получим значение напрямую из хранилища. - Объявление переменной
internal
работает аналогичноprotected
модификатору доступа в c/cpp. Внешние контракты не смогут получить доступ к этой переменной, но производные контракты смогут получить к ней доступ. - Если мы пометим переменную как
private
, то внешние контракты и производные контракты не смогут получить к ней доступ.
Примечание
Придание чему-либо приватного или внутреннего характера предотвращает только чтение или изменение инфы другими контрактами, но она все равно будет видна всем за пределами блокчейна.
Функции
Они представляют собой многократно используемые и исполняемые части кода, которые можно вызывать снова и снова. Функции могут быть определены как внутри, так и вне контракта. Ниже приведен пример функции
function helper(uint x) pure returns (uint)
Давайте разберемся, что означает приведенный выше фрагмент кода.
- Слово function - это ключевое слово, которое используется для объявления функции.
- helper — это имя функции, под которым она будет вызываться в дальнейшем.
- в скобках uint x означает, что функция принимает параметр типа uint.
- слово pure также является кейвордом, что означает, что эта функция не изменяет и не читает переменную какого-либо состояния.
- `returns (uint) означает, что эта функция возвращает значение типа uint.
- вызовы функций могут происходить внутри или снаружи, в зависимости от видимости функции.
Модификаторы функций
Модификаторы можно использовать для изменения поведения функции с описанным результатом. Например, вы можете использовать модификатор для автоматической проверки условия перед выполнением функции.
Модификаторы являются производными свойствами контракта, только если они помечены как virtual
. Модификаторы можно переопределять, но НЕ перегружать. Ниже приведен пример модификатора функции.
contract Mutex { bool locked; modifier noReentrancy() { require( !locked, "Reentrant call." ); locked = true; _; locked = false; }
/// This function is protected by a mutex, which means that /// reentrant calls from within `msg.sender.call` cannot call `f` again. /// The `return 7` statement assigns 7 to the return value but still /// executes the statement `locked = false` in the modifier. function f() public noReentrancy returns (uint) { (bool success,) = msg.sender.call(""); require(success); return 7; } }
Давайте разберемся, о чем эта часть кода.
- Это обычный контракт с именем Mutex, который содержит одну булевую переменную, один модификатор и одну функцию.
Давайте разберемся с модификатором noReetrancy
- Этот модификатор следит за тем, чтобы использовал повторные входы, и применяется к функции
f()
. - Символ _ означает, что после него будет выполнено тело функции, причем в функции может быть _, каждая из которых будет заменена телом функции.
- После возврата функции значение будет возвращено, но флоу управления продолжится, т.е. после этого locked будет помечено как false.
- Таким образом, когда функция вернет 7, значение locked будет повторно инициализировано в false.
- К функции можно применить несколько модификаторов, и они будут оцениваться в указанном порядке.
Ивенты
Ивенты представляют собой абстракции поверх функциональности протоколирования EVM. Приложения (обычно фронтенд) могут подписываться на различные ивенты, слушать и действовать соответствующим образом при наступлении определенного события. Рассмотрим пример.
event HighestBidIncreased(address bidder, uint amount); // Event function bid() public payable { // ... emit HighestBidIncreased(msg.sender, msg.value); // Triggering event }
Ошибки
Класс error помогает заранее определить имя ошибки и ее данные, которые могут возникнуть позже в коде. Они используются с операторами возврата. По сравнению со string descriptions
ошибки намного дешевле и позволяют кодировать дополнительную дату. Рассмотрим пример.
error NotEnoughFunds(uint requested, uint available); if (balance < amount) revert NotEnoughFunds(amount, balance);
- Если мы внимательно посмотрим на пример, то увидим, что мы определили
error
, которая будет использоваться, когда запрашиваемая сумма превышает доступную. - При проверке условия недостаточного баланса мы мгновенно возвращаемся к ошибке NotEnoughFunds с текущей суммой и балансом в качестве данных ошибки.
Типы структур
Если вы знакомы с c/cpp, то, возможно, вам уже знакома концепция структур. Структура — это кастомный тип данных, который может группировать различные переменные. Пример типа struct
приведен ниже.
struct Voter { // Struct uint weight; bool voted; address delegate; uint vote; }
Типы перечисления
Enums используются для определения пользовательских типов данных, но с конечным набором возможных значений. Их представление схоже с программирование на C. Давайте увидим это на примере.
enum State { Created, Locked, Inactive }
- Перечисления можно использовать и для управления состояниями.
- Как в примере выше, мы видим, что перечисление
State
может иметь только три значения/состоянияCreated
,Locked
иInactive
. - Мы можем реагировать соответствующим образом в зависимости от
State
. - Возможно, вы уже знакомы с подходом к управлению состояниями с помощью классов перечислений, если вы знакомы с языком программирования
Kotlin
.