Подробнее о смарт-контрактах
Последнее обновление страницы: 23 февраля 2026 г.
Смарт-контракт - это программа, которая запускается на определенном адресе на Ethereum. Они состоят из данных и функций, которые могут выполняться при получении транзакции. Вот обзор того, что представляет из себя смарт-контракт.
Предварительные условия
Сначала убедитесь, что вы прочитали о смарт-контрактах. Эта статья предполагает, что вы уже знакомы с языками программирования, такими как JavaScript или Python.
Данные
Любые данные контракта должны быть присвоены местоположению: либо storage, либо memory. Модифицировать хранилище в смарт контракте - дорогостоящая операция, поэтому вам необходимо подумать заранее, где ваши данные должны храниться.
Хранилища
Постоянные данные называются хранилищем и представлены переменными состояния. Эти значения постоянно хранятся в блокчейне. Вам необходимо объявить тип, чтобы контракт мог отслеживать, сколько пространства в блокчейне ему потребуется при компиляции.
1// Пример Solidity2contract SimpleStorage {3 uint storedData; // Переменная состояния4 // ...5}1Пример на VyperЕсли вы уже программировали объектно-ориентированные языки, то вы, скорее всего, будете знакомы с большинством типов. Однако, address может быть для вас в новинку, если вы новичок в разработке на Ethereum.
Тип address может содержать адрес Ethereum, который равен 20 байтам или 160 битам. Он возвращается в шестнадцатеричной нотации с лидирующим значением 0x.
Другие типы включают:
- логический
- целое число
- числа с фиксированной точкой
- байтовые массивы фиксированного размера
- массивы байтов динамического размера
- рациональные и целочисленные литералы
- строковые литералы
- шестнадцатеричные литералы
- перечисления
Для более подробного объяснения, ознакомьтесь с документацией:
Память
Значения, которые хранятся только на время выполнения контрактной функции, называются переменными памяти. Поскольку они не хранятся постоянно в блокчейне, их использование намного дешевле.
Узнайте больше о том, как EVM хранит данные (хранилище, память и стек) в документации Solidity (opens in a new tab).
Переменные среды
В дополнение к переменным, которые вы определяете по вашему контракту, есть некоторые специальные глобальные переменные. Они в основном используются для предоставления информации о блокчейне или текущей транзакции.
Примеры:
| Свойство | Переменная состояния | Описание |
|---|---|---|
block.timestamp | uint256 | Текущая метка времени начала блоков |
msg.sender | адрес | Отправитель сообщения (текущий вызов) |
Функции
В наиболее упрощенных терминах функции могут получать информацию или устанавливать информацию в ответ на входящие транзакции.
Существует два типа вызовов функций:
internal– не создают вызов EVM- К внутренним функциям и переменным состояния можно получить доступ только изнутри (т. е. из текущего контракта или контрактов, производных от него)
external– создают вызов EVM- Внешние функции являются частью контрактного интерфейса, что означает, что они могут быть вызваны из других контрактов и через сделки. Внешняя функция
fне может быть вызвана внутренне (т. е.f()не работает, ноthis.f()работает).
- Внешние функции являются частью контрактного интерфейса, что означает, что они могут быть вызваны из других контрактов и через сделки. Внешняя функция
Они также могут быть public или private
- Функции
publicможно вызывать внутренне из контракта или внешне с помощью сообщений - Функции
privateвидны только для контракта, в котором они определены, и не видны в производных контрактах
Функции и переменные состояний могут быть общедоступными или частными
Вот функция обновления переменной состояния по контракту:
1// Пример солидарности2function update_name(string value) public {3 dapp_name = value;4}- Параметр
valueтипаstringпередается в функцию:update_name - Она объявлена как
public, что означает, что любой может получить к ней доступ - Она не объявлена как
view, поэтому может изменять состояние контракта
Функции просмотра
Эти функции обещают не изменять состояние данных контракта. Типичными примерами являются функции "получения" - вы можете использовать их, например, для получения баланса пользователя.
1// Пример твердости2функция balanceOf (address _owner) в публичном представлении возвращает (uint256 _balance) {3 вернуть ownerPizzaCount [_owner];4}1dappName: public(string)23@view4@public5def readName() -> string:6 return dappNameЧто считается измененным состоянием:
- Запись переменных состояний.
- Генерирование событий (opens in a new tab).
- Создание других контрактов (opens in a new tab).
- Использование
selfdestruct. - Отправка эфира через вызовы.
- Вызов любой функции, не отмеченной как
viewилиpure. - Использование низкоуровневых вызовов.
- Используя встроенную сборку, которая содержит некоторые опкоды.
Функции конструктора
Функции constructor выполняются только один раз при первом развертывании контракта. Подобно constructor во многих объектно-ориентированных языках программирования, эти функции часто инициализируют переменные состояния их заданными значениями.
1// Пример на Solidity2// Инициализирует данные контракта, устанавливая `owner`3// в качестве адреса создателя контракта.4constructor() public {5 // Все смарт-контракты полагаются на внешние транзакции для запуска своих функций.6 // `msg` — это глобальная переменная, которая содержит актуальные данные о данной транзакции,7 // такие как адрес отправителя и значение ETH, включенное в транзакцию.8 // Узнать больше: https://solidity.readthedocs.io/en/v0.5.10/units-and-global-variables.html#block-and-transaction-properties9 owner = msg.sender;10}Показать все1# Vyper example23@external4def __init__(_beneficiary: address, _bidding_time: uint256):5 self.beneficiary = _beneficiary6 self.auctionStart = block.timestamp7 self.auctionEnd = self.auctionStart + _bidding_timeВстроенные функции
В дополнение к переменным и функциям, которые вы определяете в своем контракте, есть несколько специальных встроенных функций. Самый очевидный пример:
address.send()– Soliditysend(address)– Vyper
Это позволяет контрактам отправлять ETH на другие учетные записи.
Написание функций
Ваша функция требует:
- переменная параметра и тип (если он принимает параметры)
- декларация внутреннего / внешнего
- декларация о чистом / просмотре / к оплате
- возвращает тип (если возвращает значение)
1pragma solidity >=0.4.0 <=0.6.0;23contract ExampleDapp {4 string dapp_name; // переменная состояния56 // Вызывается при развертывании контракта и инициализирует значение7 constructor() public {8 dapp_name = "My Example dapp";9 }1011 // Функция получения12 function read_name() public view returns(string) {13 return dapp_name;14 }1516 // Функция установки17 function update_name(string value) public {18 dapp_name = value;19 }20}Показать всеПолный контракт может выглядеть примерно так. Здесь функция constructor задает начальное значение для переменной dapp_name.
События и журналы
События позволяют вашему смарт-контракту взаимодействовать с вашим внешним интерфейсом или другими подписанными приложениями. После того, как транзакция проверена и добавлена в блок, смарт-контракты могут генерировать события и регистрировать информацию, которую внешний интерфейс затем может обрабатывать и использовать.
Примеры с комментариями
Это несколько примеров, написанных на Solidity. Если вы хотите поэкспериментировать с кодом, вы можете взаимодействовать с ним в Remix (opens in a new tab).
Hello world
1// Указывает версию Solidity, используя семантическое версионирование.2// Узнать больше: https://solidity.readthedocs.io/en/v0.5.10/layout-of-source-files.html#pragma3pragma solidity ^0.5.10;45// Определяет контракт с именем `HelloWorld`.6// Контракт — это набор функций и данных (его состояние).7// После развертывания контракт находится по определенному адресу в блокчейне Ethereum.8// Узнать больше: https://solidity.readthedocs.io/en/v0.5.10/structure-of-a-contract.html9contract HelloWorld {1011 // Объявляет переменную состояния `message` типа `string`.12 // Переменные состояния — это переменные, значения которых постоянно хранятся в хранилище контракта.13 // Ключевое слово `public` делает переменные доступными извне контракта14 // и создает функцию, которую могут вызывать другие контракты или клиенты для доступа к значению.15 string public message;1617 // Подобно многим объектно-ориентированным языкам, конструктор — это18 // специальная функция, которая выполняется только при создании контракта.19 // Конструкторы используются для инициализации данных контракта.20 // Узнать больше: https://solidity.readthedocs.io/en/v0.5.10/contracts.html#constructors21 constructor(string memory initMessage) public {22 // Принимает строковый аргумент `initMessage` и устанавливает его значение23 // в переменную хранилища контракта `message`).24 message = initMessage;25 }2627 // Публичная функция, которая принимает строковый аргумент28 // и обновляет переменную хранилища `message`.29 function update(string memory newMessage) public {30 message = newMessage;31 }32}Показать всеТокен
1pragma solidity ^0.5.10;23contract Token {4 // `address` можно сравнить с адресом электронной почты — он используется для идентификации аккаунта в Ethereum.5 // Адреса могут представлять смарт-контракт или внешние (пользовательские) аккаунты.6 // Узнать больше: https://solidity.readthedocs.io/en/v0.5.10/types.html#address7 address public owner;89 // `mapping` — это, по сути, структура данных хеш-таблицы.10 // Этот `mapping` присваивает беззнаковое целое число (баланс токенов) адресу (владельцу токенов).11 // Узнать больше: https://solidity.readthedocs.io/en/v0.5.10/types.html#mapping-types12 mapping (address => uint) public balances;1314 // События позволяют регистрировать активность в блокчейне.15 // Клиенты Ethereum могут прослушивать события, чтобы реагировать на изменения состояния контракта.16 // Узнать больше: https://solidity.readthedocs.io/en/v0.5.10/contracts.html#events17 event Transfer(address from, address to, uint amount);1819 // Инициализирует данные контракта, устанавливая `owner`20 // в качестве адреса создателя контракта.21 constructor() public {22 // Все смарт-контракты полагаются на внешние транзакции для запуска своих функций.23 // `msg` — это глобальная переменная, которая содержит актуальные данные о данной транзакции,24 // такие как адрес отправителя и значение ETH, включенное в транзакцию.25 // Узнать больше: https://solidity.readthedocs.io/en/v0.5.10/units-and-global-variables.html#block-and-transaction-properties26 owner = msg.sender;27 }2829 // Создает определенное количество новых токенов и отправляет их на адрес.30 function mint(address receiver, uint amount) public {31 // `require` — это управляющая структура, используемая для обеспечения выполнения определенных условий.32 // Если выражение `require` оценивается как `false`, вызывается исключение,33 // которое отменяет все изменения, внесенные в состояние во время текущего вызова.34 // Узнать больше: https://solidity.readthedocs.io/en/v0.5.10/control-structures.html#error-handling-assert-require-revert-and-exceptions3536 // Только владелец контракта может вызывать эту функцию37 require(msg.sender == owner, "You are not the owner.");3839 // Применяет максимальное количество токенов40 require(amount < 1e60, "Maximum issuance exceeded");4142 // Увеличивает баланс `receiver` на `amount`43 balances[receiver] += amount;44 }4546 // Отправляет определенное количество существующих токенов от любого вызывающего на адрес.47 function transfer(address receiver, uint amount) public {48 // У отправителя должно быть достаточно токенов для отправки49 require(amount <= balances[msg.sender], "Insufficient balance.");5051 // Корректирует балансы токенов двух адресов52 balances[msg.sender] -= amount;53 balances[receiver] += amount;5455 // Генерирует событие, определенное ранее56 emit Transfer(msg.sender, receiver, amount);57 }58}Показать всеУникальный цифровой актив
1pragma solidity ^0.5.10;23// Импортирует символы из других файлов в текущий контракт.4// В данном случае — серия вспомогательных контрактов из OpenZeppelin.5// Узнать больше: https://solidity.readthedocs.io/en/v0.5.10/layout-of-source-files.html#importing-other-source-files67import "../node_modules/@openzeppelin/contracts/token/ERC721/IERC721.sol";8import "../node_modules/@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol";9import "../node_modules/@openzeppelin/contracts/introspection/ERC165.sol";10import "../node_modules/@openzeppelin/contracts/math/SafeMath.sol";1112// Ключевое слово `is` используется для наследования функций и ключевых слов из внешних контрактов.13// В этом случае `CryptoPizza` наследует от контрактов `IERC721` и `ERC165`.14// Узнать больше: https://solidity.readthedocs.io/en/v0.5.10/contracts.html#inheritance15contract CryptoPizza is IERC721, ERC165 {16 // Использует библиотеку SafeMath из OpenZeppelin для безопасного выполнения арифметических операций.17 // Узнать больше: https://docs.openzeppelin.com/contracts/2.x/api/math#SafeMath18 using SafeMath for uint256;1920 // Константные переменные состояния в Solidity похожи на другие языки,21 // но вы должны присваивать их из выражения, которое является константой во время компиляции.22 // Узнать больше: https://solidity.readthedocs.io/en/v0.5.10/contracts.html#constant-state-variables23 uint256 constant dnaDigits = 10;24 uint256 constant dnaModulus = 10 ** dnaDigits;25 bytes4 private constant _ERC721_RECEIVED = 0x150b7a02;2627 // Типы Struct позволяют определять собственный тип28 // Узнать больше: https://solidity.readthedocs.io/en/v0.5.10/types.html#structs29 struct Pizza {30 string name;31 uint256 dna;32 }3334 // Создает пустой массив структур Pizza35 Pizza[] public pizzas;3637 // Сопоставление идентификатора пиццы с адресом ее владельца38 mapping(uint256 => address) public pizzaToOwner;3940 // Сопоставление адреса владельца с количеством принадлежащих ему токенов41 mapping(address => uint256) public ownerPizzaCount;4243 // Сопоставление идентификатора токена с одобренным адресом44 mapping(uint256 => address) pizzaApprovals;4546 // Вы можете вкладывать сопоставления, этот пример сопоставляет владельца с одобрениями оператора47 mapping(address => mapping(address => bool)) private operatorApprovals;4849 // Внутренняя функция для создания случайной пиццы из строки (имени) и ДНК50 function _createPizza(string memory _name, uint256 _dna)51 // Ключевое слово `internal` означает, что эта функция видна только52 // в этом контракте и в контрактах, которые его наследуют53 // Узнать больше: https://solidity.readthedocs.io/en/v0.5.10/contracts.html#visibility-and-getters54 internal55 // `isUnique` — это модификатор функции, который проверяет, существует ли пицца56 // Узнать больше: https://solidity.readthedocs.io/en/v0.5.10/structure-of-a-contract.html#function-modifiers57 isUnique(_name, _dna)58 {59 // Добавляет пиццу в массив пицц и получает идентификатор60 uint256 id = SafeMath.sub(pizzas.push(Pizza(_name, _dna)), 1);6162 // Проверяет, что владелец пиццы совпадает с текущим пользователем63 // Узнать больше: https://solidity.readthedocs.io/en/v0.5.10/control-structures.html#error-handling-assert-require-revert-and-exceptions6465 // обратите внимание, что address(0) — это нулевой адрес,66 // указывающий, что pizza[id] еще не назначена конкретному пользователю.6768 assert(pizzaToOwner[id] == address(0));6970 // Сопоставляет пиццу с владельцем71 pizzaToOwner[id] = msg.sender;72 ownerPizzaCount[msg.sender] = SafeMath.add(73 ownerPizzaCount[msg.sender],74 175 );76 }7778 // Создает случайную пиццу из строки (имени)79 function createRandomPizza(string memory _name) public {80 uint256 randDna = generateRandomDna(_name, msg.sender);81 _createPizza(_name, randDna);82 }8384 // Генерирует случайную ДНК из строки (имени) и адреса владельца (создателя)85 function generateRandomDna(string memory _str, address _owner)86 public87 // Функции, помеченные как `pure`, обещают не читать и не изменять состояние88 // Узнать больше: https://solidity.readthedocs.io/en/v0.5.10/contracts.html#pure-functions89 pure90 returns (uint256)91 {92 // Генерирует случайный uint из строки (имени) + адреса (владельца)93 uint256 rand = uint256(keccak256(abi.encodePacked(_str))) +94 uint256(_owner);95 rand = rand % dnaModulus;96 return rand;97 }9899 // Возвращает массив пицц, найденных по владельцу100 function getPizzasByOwner(address _owner)101 public102 // Функции, помеченные как `view`, обещают не изменять состояние103 // Узнать больше: https://solidity.readthedocs.io/en/v0.5.10/contracts.html#view-functions104 view105 returns (uint256[] memory)106 {107 // Использует местоположение в хранилище `memory` для хранения значений только на время108 // жизненного цикла этого вызова функции.109 // Узнать больше: https://solidity.readthedocs.io/en/v0.5.10/introduction-to-smart-contracts.html#storage-memory-and-the-stack110 uint256[] memory result = new uint256[](ownerPizzaCount[_owner]);111 uint256 counter = 0;112 for (uint256 i = 0; i < pizzas.length; i++) {113 if (pizzaToOwner[i] == _owner) {114 result[counter] = i;115 counter++;116 }117 }118 return result;119 }120121 // Передает пиццу и право собственности на другой адрес122 function transferFrom(address _from, address _to, uint256 _pizzaId) public {123 require(_from != address(0) && _to != address(0), "Invalid address.");124 require(_exists(_pizzaId), "Pizza does not exist.");125 require(_from != _to, "Cannot transfer to the same address.");126 require(_isApprovedOrOwner(msg.sender, _pizzaId), "Address is not approved.");127128 ownerPizzaCount[_to] = SafeMath.add(ownerPizzaCount[_to], 1);129 ownerPizzaCount[_from] = SafeMath.sub(ownerPizzaCount[_from], 1);130 pizzaToOwner[_pizzaId] = _to;131132 // Генерирует событие, определенное в импортированном контракте IERC721133 emit Transfer(_from, _to, _pizzaId);134 _clearApproval(_to, _pizzaId);135 }136137 /**138 * Безопасно передает право собственности на данный идентификатор токена другому адресу139 * Если целевой адрес является контрактом, он должен реализовывать `onERC721Received`,140 * который вызывается при безопасной передаче и возвращает магическое значение141 * `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`;142 * в противном случае передача отменяется.143 */144 function safeTransferFrom(address from, address to, uint256 pizzaId)145 public146 {147 // solium-disable-next-line arg-overflow148 this.safeTransferFrom(from, to, pizzaId, "");149 }150151 /**152 * Безопасно передает право собственности на данный идентификатор токена другому адресу153 * Если целевой адрес является контрактом, он должен реализовывать `onERC721Received`,154 * который вызывается при безопасной передаче и возвращает магическое значение155 * `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`;156 * в противном случае передача отменяется.157 */158 function safeTransferFrom(159 address from,160 address to,161 uint256 pizzaId,162 bytes memory _data163 ) public {164 this.transferFrom(from, to, pizzaId);165 require(_checkOnERC721Received(from, to, pizzaId, _data), "Must implement onERC721Received.");166 }167168 /**169 * Внутренняя функция для вызова `onERC721Received` на целевом адресе170 * Вызов не выполняется, если целевой адрес не является контрактом171 */172 function _checkOnERC721Received(173 address from,174 address to,175 uint256 pizzaId,176 bytes memory _data177 ) internal returns (bool) {178 if (!isContract(to)) {179 return true;180 }181182 bytes4 retval = IERC721Receiver(to).onERC721Received(183 msg.sender,184 from,185 pizzaId,186 _data187 );188 return (retval == _ERC721_RECEIVED);189 }190191 // Сжигает пиццу — полностью уничтожает токен192 // Модификатор функции `external` означает, что эта функция193 // является частью интерфейса контракта, и другие контракты могут ее вызывать194 function burn(uint256 _pizzaId) external {195 require(msg.sender != address(0), "Invalid address.");196 require(_exists(_pizzaId), "Pizza does not exist.");197 require(_isApprovedOrOwner(msg.sender, _pizzaId), "Address is not approved.");198199 ownerPizzaCount[msg.sender] = SafeMath.sub(200 ownerPizzaCount[msg.sender],201 1202 );203 pizzaToOwner[_pizzaId] = address(0);204 }205206 // Возвращает количество пицц по адресу207 function balanceOf(address _owner) public view returns (uint256 _balance) {208 return ownerPizzaCount[_owner];209 }210211 // Возвращает владельца пиццы по идентификатору212 function ownerOf(uint256 _pizzaId) public view returns (address _owner) {213 address owner = pizzaToOwner[_pizzaId];214 require(owner != address(0), "Invalid Pizza ID.");215 return owner;216 }217218 // Одобряет другой адрес для передачи права собственности на пиццу219 function approve(address _to, uint256 _pizzaId) public {220 require(msg.sender == pizzaToOwner[_pizzaId], "Must be the Pizza owner.");221 pizzaApprovals[_pizzaId] = _to;222 emit Approval(msg.sender, _to, _pizzaId);223 }224225 // Возвращает одобренный адрес для конкретной пиццы226 function getApproved(uint256 _pizzaId)227 public228 view229 returns (address operator)230 {231 require(_exists(_pizzaId), "Pizza does not exist.");232 return pizzaApprovals[_pizzaId];233 }234235 /**236 * Приватная функция для отмены текущего одобрения для данного идентификатора токена237 * Отменяется, если данный адрес действительно не является владельцем токена238 */239 function _clearApproval(address owner, uint256 _pizzaId) private {240 require(pizzaToOwner[_pizzaId] == owner, "Must be pizza owner.");241 require(_exists(_pizzaId), "Pizza does not exist.");242 if (pizzaApprovals[_pizzaId] != address(0)) {243 pizzaApprovals[_pizzaId] = address(0);244 }245 }246247 /*248 * Устанавливает или отменяет одобрение для данного оператора249 * Оператору разрешено передавать все токены отправителя от его имени250 */251 function setApprovalForAll(address to, bool approved) public {252 require(to != msg.sender, "Cannot approve own address");253 operatorApprovals[msg.sender][to] = approved;254 emit ApprovalForAll(msg.sender, to, approved);255 }256257 // Сообщает, одобрен ли оператор данным владельцем258 function isApprovedForAll(address owner, address operator)259 public260 view261 returns (bool)262 {263 return operatorApprovals[owner][operator];264 }265266 // Принимает право собственности на пиццу — только для одобренных пользователей267 function takeOwnership(uint256 _pizzaId) public {268 require(_isApprovedOrOwner(msg.sender, _pizzaId), "Address is not approved.");269 address owner = this.ownerOf(_pizzaId);270 this.transferFrom(owner, msg.sender, _pizzaId);271 }272273 // Проверяет, существует ли пицца274 function _exists(uint256 pizzaId) internal view returns (bool) {275 address owner = pizzaToOwner[pizzaId];276 return owner != address(0);277 }278279 // Проверяет, является ли адрес владельцем или ему разрешена передача пиццы280 function _isApprovedOrOwner(address spender, uint256 pizzaId)281 internal282 view283 returns (bool)284 {285 address owner = pizzaToOwner[pizzaId];286 // Отключить проверку solium из-за287 // https://github.com/duaraghav8/Solium/issues/175288 // solium-disable-next-line operator-whitespace289 return (spender == owner ||290 this.getApproved(pizzaId) == spender ||291 this.isApprovedForAll(owner, spender));292 }293294 // Проверить, является ли пицца уникальной и еще не существует295 modifier isUnique(string memory _name, uint256 _dna) {296 bool result = true;297 for (uint256 i = 0; i < pizzas.length; i++) {298 if (299 keccak256(abi.encodePacked(pizzas[i].name)) ==300 keccak256(abi.encodePacked(_name)) &&301 pizzas[i].dna == _dna302 ) {303 result = false;304 }305 }306 require(result, "Pizza with such name already exists.");307 _;308 }309310 // Возвращает, является ли целевой адрес контрактом311 function isContract(address account) internal view returns (bool) {312 uint256 size;313 // В настоящее время нет лучшего способа проверить, есть ли контракт по адресу,314 // чем проверить размер кода по этому адресу.315 // См. https://ethereum.stackexchange.com/a/14016/36603316 // для получения более подробной информации о том, как это работает.317 // TODO Проверить это снова перед выпуском Serenity, потому что тогда все адреса будут318 // контрактами.319 // solium-disable-next-line security/no-inline-assembly320 assembly {321 size := extcodesize(account)322 }323 return size > 0;324 }325}Показать всеДополнительные материалы
Ознакомьтесь с документацией Solidity и Vyper для более полного обзора смарт-контрактов:
Смежные темы
Связанные руководства
- Сокращение контрактов для борьбы с ограничением размера контракта – несколько практических советов по уменьшению размера вашего смарт-контракта.
- Регистрация данных из смарт-контрактов с помощью событий – введение в события смарт-контрактов и способы их использования для регистрации данных.
- Взаимодействие с другими контрактами из Solidity – как развернуть смарт-контракт из существующего контракта и взаимодействовать с ним.