July 15, 2022

Структура смарт-контрактов на 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.

Оригинал статьи на GitHub.