Среда разработки Remix - создайте свой первый Smart Contract
Примечания от переводчика
Данная статья является переводом [материала][link-original-article], опубликованного пользователм [@joshorig][joshorig-profile]. Переводчик сохранил авторский стиль изложения от первого лица.
Во избежание неоднозначностей и разночтений, часть устоявшихся терминов оставлена без перевода. Например: JavaScript, Blockchain, Solidity.
Ссылки, приведенные в статье, ведут на страницы с англолязычной документацией, поскольку соответствующая документация на русском отсутствует на момент создания данного перевода.
Среда разработки Remix - создайте свой первый Smart Contract
1. Вступление от автора
Простейшим способом разработать свой smart contract на языке Solidity является использование [онлайн-окружения Remix][remix-ide-open]
Оно не требует никаких действий по установке либо настройке, поскольку сделано как сайт, а не как настольное приложение. Просто откройте [этот сайт][remix-ide-open] - и вы уже готовы приступить к написанию кода своего контракта.
Среда разработки Remix предоставляет обширный набор вспомогательных средств для отладки, статического анализа и публикации кода контракта. Все они доступны как часть единого онлайн-окружения.
[Исходный код][tutorial-source-root], приведенный в данном обучающем материале, можно найти [по этой ссылке][tutorial-source-root].
Прежде, чем продолжить, вспомним: 
"А что, собственно, будет являться 
конечным результатом нашей работы?"
Итак, идея распределенного приложения такова:
Пользователи нашего распределенного приложения (dApp)
смогут создавать задания
и выплачивать за их выполнение гонорар 
в криптовалюте "эфир" (имеющей буквенный код ETH).
- Любой пользователь, имеющий учетную запись в блокчейне Ethereum, может создать задание. Задание содержит описание критериев приемки выполненной работы, а также значение суммы гонорара для исполнителя.
(под учетной записью понимается пара ключей "public key/pivate key"- прим. переводчика)
- Любой пользователь может подать заявку на получение гонорара, предоставив доказательства факта выполнения задания надлежащим образом.
- Создатель задания должен подтвердить приемку работы, дабы получить ее результат. В этом случае вознаграждение будет автоматически выплачено исполнителю созданным нами контрактом.
2. Создание контракта в среде Remix
Откройте [страницу][remix-ide-open] среды Remix. В левом верхнем углу вы увидите кнопку с иконкой "+". Нажмите на нее и введите "Bounties.sol" в качестве имени файла в появившееся диалоговое окно.
![screenshot : creatng a file in remix][screenshot-remix-create-file]
В качестве первой строки нашего контракта на языке Solidity обязательно должен находиться номер версии компилятора.
pragma solidity ^0.5.0;
Благодаря этой инструкции, Solidity будет знать, что нужен компилятор версии 0.5.0 либо новее. Но не более новый, чем 0.6.0, в которой могут быть изменения, ломающие обратную совместимость. Данное ограничение обеспечивается символом ^, стоящим перед номером версии.
(Таким образом, Solidity следует правилам [semantic versioning][link-semantic-versioning] - прим. переводчика)
Теперь создадим сам класс, который будет содежжать код котракта. Для этого напишем
contract Bounties 
{
}
Далее, добавим конструктор, дабы иметь возможность создать экземпляр нашего контракта.
constructor() public {}
3. Компиляция контракта
На этом этапе мы получили базовый каркас электронного контракта (Smart Contract). Далее - скомпилируем его в Remix IDE.
Ваш файл Bounties.sol сейчас должен выглядеть следующим образом:
pragma solidity ^0.5.0;
contract Bounties 
{
    constructor() public {}
}
В окружении Remix выберите вкладку "Compile" в правом верхнем углу экрана и запустите процесс компиляции нажатием кнопки "Start to Compile" как показано на снимке экрана ниже.
![screenshot: compile a smart contract in Remix IDE][screenshot-remix-compile]
В случаче успеха вы должны увидеть название своего контракта "Bounties" на зеленом фоне, как показано на том же снимке экрана.
4. Публикация задания
Пришло время расширить наш каркас контракта некоторой функциональностью. Начнем с публикации задания.
4.1 Объявление переменных для хранения состояния
Что же такое переменные состояния в Solidity? 
Дело в том, что экземпляр электронного контракта имеет возможность запоминать свое состояние,
помещая его в хранилище EVM (Ehtereum Virtual Machine - прим. переводчика).
Это состояние описываеться одной либо несколькими переменными, 
которые могут иметь один из встроенных в язык Solidity типов. 
Значение, хранящееся в этих переменных, может быть изменено 
лишь функциями, вызванными во время транзакции.
Исчерпывающий список типов можно найти в документации языка Solidity. 
А именно, в [секции "Types"][doc-solidity-types].
Первым делом, давайте объявим перечислимый тип, описывающий множество состояний, в которых может находиться задача.
// множество "состояния задачи"
//
enum BountyStatus 
{ 
    CREATED  , // "создано" - новая задача, ожидающая иполнителя
    ACCEPTED , // "принято к исполнению" - исполнитель найден и утвержден
    CANCELLED  // "отменена" - работодатель больше не нуждается в данной услуге
}
Далее объявим структуру, для хранения данных о задаче.
// структура "заказ" (работа за вознаграждение)
//
struct Bounty 
{
    // учетная запись ethereum работодателя
    //
    address issuer;
    // срок выполнения.
    // исполнитель не получит вознаграждение, если нарушит его.
    //
    uint deadline;
    // описание задания
    //
    string data;
    // состояние задачи
    // тип enum был только что объявлен нами выше
    //
    BountyStatus status;
    // сумма вознаграждения в WEI
    // WEI - минимальная неделимая дробная часть валюты "эфир"
    // 10^18 WEI == 1 ETH 
    //
    uint amount;
}
Что такое структура? 
Структура - это конструкция языка, 
позволяющая описывать собственные типы данных. 
По сути, это - набор переменных, 
которые могут принадлежать как типам, встроенным в язык Solidity, так и являться другими структурами.
Набор переменных объединяют в структуру
с целью их группирования и более понятной организации 
при разработке кода контракта.
Давайте-ка добавим в наш контракт переменную-массив, хранящую данные обо всех созданных контрактах.
Bounty[] public bounties;
// создать задание
//
function issueBounty(
    string memory _data,        // описание работ
    uint64        _deadline     // срок исполнения 
) public   // функция может быть вызвана любым пользователем или другим контрактом в сети Ethereum
  payable  // при вызове этой функции контракт может получать эфир
           // полученный эфир будет храниться "на балансе" контракта
    hasValue()  // проверяем, что при вызове контракту был отправлен эфир в ненулевом объеме.
                // реализацию этого modifier запрограммируем позже
    validateDeadline(_deadline) 
                // проверяем, что крайний срок не исчерпан на момент задания
                // реализацию этого modifier запрограммируем позже
returns (uint)  // возвращает целое число - порядковый номер последнего добавленного задания
{
    bounties.push(
        Bounty(
            msg.sender, // адрес отправителя эфира, вызвавшего функцию. 
                        // он же создатель нового задания.
                        //
            _deadline,  // срок исполнения. передан как параметр.
            _data,      // описание работ. передан как параметр.
            BountyStatus.CREATED, // статус "задача создана", исполнитель еще не назначен
            msg.value   // размер награды. 
                        // до выполнения задания сам эфир будет храниться в контракте.
                        // в структуру попадает только его количество
                        // измеряемое в WEI
        )
    );
    // отнимаем единицу, поскольку 
    // нумерация элементов массива начинается с нуля
    return (bounties.length - 1);
}
bounties.push(Bounty(msg.sender, _deadline, _data,
BountyStatus.CREATED, msg.value));
return (bounties.length - 1);
Широко распространена практика 
запуска modifier перед основной функцией
с целью проверки её входных параметров
modifier validateDeadline(uint _newDeadline)
{
    require(_newDeadline > now);
    _;
}
Примечание переводчика: очевидно, что это количество не может быть отрицательным
поскольку это было бы эквивалентно 
"забрать силой эфир у другого пользователя"
что абсолютно недопустипо.
modifier hasValue() 
{
    require(msg.value > 0);
    _;
}
event BountyIssued(
    uint    bounty_id, // индекс задания в массиве
    address issuer   , // создатель задания, который внес в контракт средства для вознаграждения
    uint    amount   , // сумма гонорара
    string  data     ); // описание требований к заданию
bounties.push(
    Bounty(
        msg.sender, 
        _deadline, 
        _data, 
        BountyStatus.CREATED, 
        msg.value));
emit BountyIssued(            // добавленное событие
        bounties.length - 1,  // индекс нового задания в качестве идентификатора
        msg.sender,           // пользователь, финансирующий задание
        msg.value,            // размер гонорара
        _data);               // описание задания
return (bounties.length - 1);
pragma solidity ^0.5.0;
/**
* @title Bounties
* @author Joshua Cassidy- <joshua.cassidy@consensys.net>
* @dev Простой электронный контракт
* который позволяет любому пользователю
* оплатить криптовалютой "эфир"
* задание с четко определенными критериями приёмки
* награду может получить любой пользователь
* предоставивший доказательства исполнения задачи
*/
contract Bounties 
{
/*
* Множества (перечислимые типы)
*/
enum BountyStatus 
{ 
    CREATED  , // "создано" - новая задача, ожидающая иполнителя
    ACCEPTED , // "принято к исполнению" - исполнитель найден и утвержден
    CANCELLED  // "отменена" - работодатель больше не нуждается в данной услуге
}
/*
* Состояние, хранилище
*/
Bounty[] public bounties;
/*
* Структуры
*/
struct Bounty 
{
    // учетная запись ethereum работодателя
    //
    address issuer;
    // срок выполнения.
    // исполнитель не получит вознаграждение, если нарушит его.
    //
    uint deadline;
    // описание задания
    //
    string data;
    // состояние задачи
    // тип enum был только что объявлен нами выше
    //
    BountyStatus status;
    // сумма вознаграждения в WEI
    // WEI - минимальная неделимая дробная часть валюты "эфир"
    // 10^18 WEI == 1 ETH 
    //
    uint amount;
}
/**
* @dev Конструктор
*/
constructor() public 
{
}
/**
* @dev issueBounty(): создает новое задание и выделяет на него средства
* @param _deadline the unix timestamp after which fulfillments will no longer be accepted
* @param _data the requirements of the bounty
*/
function issueBounty(
    string memory _data, // описание работ
    uint64 _deadline     // срок исполнения 
) public   // функция может быть вызвана 
           // любым пользователем 
           // или другим контрактом в сети Ethereum
           // ---
  payable  // при вызове этой функции контракт может получать эфир
           // полученный эфир будет храниться "на балансе" контракта
    hasValue()  // проверяем, что при вызове 
                // контракту был отправлен эфир
                // в ненулевом объеме.
    validateDeadline(_deadline) // проверяем, что крайний срок
                                // не исчерпан на момент создания нового задания
returns (uint)  // возвращает целое число - 
                // порядковый номер последнего добавленного задания
{
 bounties.push(
        Bounty(
            msg.sender, // адрес отправителя эфира, вызвавшего функцию. 
                        // он же создатель нового задания.
                        // ---
            _deadline,  // срок исполнения. передан как параметр.
            _data,      // описание работ. передан как параметр.
            BountyStatus.CREATED, // статус "задача создана", исполнитель еще не назначен
            msg.value   // размер награды. 
                        // до выполнения задания сам эфир будет храниться в контракте.
                        // в структуру попадает только его количество
                        // измеряемое в WEI
        )
    );
    emit BountyIssued(            // добавленное событие
        bounties.length - 1,  // индекс нового задания в качестве идентификатора
        msg.sender,           // пользователь, финансирующий задание
        msg.value,            // размер гонорара
        _data);               // описание задания
    // отнимаем единицу, поскольку 
    // нумерация элементов массива начинается с нуля
    return (bounties.length - 1);
}
/**
* Модификаторы
* Проверка входных параметров
*/
modifier hasValue() 
{
    require(msg.value > 0); // проверяем, что контракт получил достаточно эфира
    _; // исполняем основную функцию
}
modifier validateDeadline(uint _newDeadline) 
{
    require(_newDeadline > now); // проверяем, что введенная дата не в прошлом. 
                                 // чтоб контракт не "забивался" бесполезными данными.
    _; // исполняем основную функцию
}
/**
* События-сообщения
*/
event BountyIssued(
        uint    bounty_id, 
        address issuer   , 
        uint    amount   , 
        string  data     );
}
Теперь во вкладке `"Run"` окружения Remix можно выбрать наш контракт `"Bounties"`, чтоб вызвать функцию `issueBounty()`. Для этого следует найти нужную функцию в секции `"Deployed Contracts"`. На данный момент секция содержит следующие функции: * `issueBounty` - розовый цвет этой кнопки означает, что ее нажатие и вызов соответствующей функции создаст новую транзакцию. * `bounties` - синий цвет этой кнопки означает простой вызов функции без транзакции и изменения состояния контракта. ![screenshot: buttons to invoke contract functions][screenshot-remix-contract-invoke-buttons] Для вызова функции `issueBounty` сперва нужно задать ее аргументы, вписав их в соответствующее поле ввода (расположенное напротив розовой кнопки). Порядок расположения значений при вводе должен совпадать с порядком объявления соответствующих параметров в коде контракта. В качестве параметра `string _data` укажем `"некоторые требования"` (кавычки необходимы для типа `string`). В свою очередь, в качестве `uint64 _deadline` запишем значение `1691452800` (без кавычек, поскольку `uint64` - целочисленный тип), что соответствует дате `8 августа 2023 года` Поскольку функция `issueBounty` помечена ключевым словом `payable`, мы должны убедиться, что параметр `msg.value` задан. Ввести нужное значение количество эфира можно в верхней части вкладки `"Run"` в среде Remix. Там мы увидим следующие настройки : * `Environment:` как уже изложено выше, позволяет выбрать блокчейн, с которым предстоит работать (в котором находится наш опубликованный контракт). * `Account:` позволяет выбрать учетную запись (имеет тип `address`), от имени которого будет произведен вызов функции. С баланса этого `address` будут списаны эфир и газ для исполнения вызова функции. Также тут можно посмотреть список доступных учетных записей и их балансов. * `Gas Limit:` позволяет задать верхний предел количества газа, который можно использовать для выполнения кода в рамках данной транзакции. * `Value:` количество эфира, перечисляемого на счет контракта. Для удобства ввода суммы можно выбрать единицу измерения: `Wei`, `Gwei`, `Finney`, `Ether` Стоит отметить, что внутри контракта это значение доступно из переменной `msg.value` и там оно будет всегда измеряться в Wei и не зависит от настроек Remix. Итак, давайте зададим в поле `"Value"` какое-нибудь значение больше нуля. В рамках даного упражнения введем `1 ETH`. Теперь можно нажать кнопку `"issueBounty"` в секции `"Deployed Contracts"` вкладки `"Run"` и таким образом запустить транзакцию, которая вызовет функцию `issueBounty()` контракта `Bounties`. В консоли будет отображен результат работы этой транзакции. ![screenshot: issueBounty function invoked][screenshot-invoked-issue-bounty] Иконка "зеленый кружок с галочкой" свидетельствует об успехе транзакции. Раскодированный вывод содержит значение, которое вернула вызванная функция. В данном случае это ноль `0`. Мы ожидаем получить индекс нового экземпляра структуры типа `Bounty` в массиве, хранящем состояние контракта. Мы можем проверить это, вызвав функцию `bounties` в секции `Deployed Contracts`. Введем ноль `0` в качестве аргумента и нажмем синюю кнопку с названием `bounties`. ![screenshot: the result of bounties getter invocation][screenshot-array-getter-called] На этом снимке экрана мы можем увидеть, что данные, которые мы ввели ранее при вызове `issueBounty`, успешно записаны в хранилище `smart contract`. Поскольку именно их мы только что извлекли вызовом функции `bounties` из одноименного поля-массива. #### 8. Попробуйте сами! Прочитав эту статью, вы изучили "как реализовать функцию добавления нового задания". Теперь попробуйте самостоятельно добавить следующие функции: * `fulfilBounty(uint _bountyId, string _data)` - эта функция должна сохранить запись о выполнении задачи с заданным идентификатором-индексом. `msg.sender` следует трактовать как учетную запись исполнителя. * `acceptFulfilment(uint _bountyId, uint _fulfilmentId)` - вызвав эту функцию, создатель контракта подтверждает приемку работы и соглашается, что она была выполнена надлежащим образом. При этом вознаграждение выплачивается исполнителю при условии, что существует заявка от исполнителя. * `function cancelBounty(uint _bountyId)` - эта функция должна отменить заказ на работу при условии отсутствия факта приёмки этого заказа. В этом случае средства возвращаются создателю задания. Примечание автора: для реализации функции `acceptFulfilment` нужно будет использовать функцию `address.transfer(uint amount)`, которая производит пересылку эфира из контракта на адрес исполнителя. [Документация][doc-solidity-transfer] по функции `address.transfer` находится [по ссылке][doc-solidity-transfer]. Полную версию контракта `Bounties.sol` можно посмотреть в справочных целях [на github][tutorial-source-full-contract] ### 9. Что дальше? - Прочитайте следующую статью данного цикла: [Understanding smart contract compilation and deployment](https://kauri.io/article/973c5f54c4434bb1b0160cff8c695369/understanding-smart-contract-compilation-and-deployment) - Изучите возможности среды Remix по [документации](https://remix.readthedocs.io/en/latest/) и [github репозиторию](https://github.com/ethereum/remix-ide) > Понравился этот обущающий материал? Появились вопросы или предложения по его содержанию? Тогда откройте [статью-оригинал][link-original-article] и напишите там свои комментарии на английском языке. Автор будет рад пообщаться с вами. > Нашли ошибку в содержании статьи? Тогда откройте [статью-оригинал][link-original-article] и отредактируйте ее, нажав кнопку `Update Article` в меню по правую сторону экрана. > Нашли ошибку перевода или опечатку? Тогда отредактируйте этот перевод, нажав кнопку `Update Article` в меню по правую сторону экрана. > Нашли ошибку в коде? Отправьте pull request [на github](https://github.com/kauri-io/kauri-fullstack-dapp-tutorial-series/tree/master/remix-bounties-smartcontract) [link-original-article]: https://kauri.io/article/124b7db1d0cf4f47b414f8b13c9d66e2/v8/remix-ide-your-first-smart-contract [joshorig-profile]: https://kauri.io/public-profile/7b88584d0e6a608fa3a8716b0ca1620d61834a0c [remix-ide-open]: https://remix.ethereum.org/ [tutorial-source-root]: https://github.com/kauri-io/kauri-fullstack-dapp-tutorial-series/tree/master/remix-bounties-smartcontract [tutorial-source-full-contract]: https://github.com/kauri-io/kauri-fullstack-dapp-tutorial-series/blob/master/remix-bounties-smartcontract/Bounties-complete.sol [screenshot-remix-create-file]: https://ipfs.infura.io/ipfs/QmYMw578VU2z4nUwGbDwcoMBBmDTEsbriSNs7H44smJpYZ [screenshot-remix-compile]: https://ipfs.infura.io/ipfs/QmSxzksHcCp9AibwAGsTxdYntdn6hGiBmjeCZm3bpKf4h6 [screenshot-remix-static-warnings]: https://ipfs.infura.io/ipfs/QmPbH2hJxqjwyCbo7iLMovVQLZyb96V9EbzKkUhJnS4Eem [screenshot-virtual-machine-dropdown]: https://ipfs.infura.io/ipfs/QmdAgBc9WzFmE4GwKBxHkMRCBBdAapHP1Ym3dR8mS2atSF [screenshot-remix-deploy-button]: https://ipfs.infura.io/ipfs/QmerrAduWYrYaxMT5254xE5DjngDid81hgaVT32uqGt1qt [screenshot-remix-constructor-transaction]: https://ipfs.infura.io/ipfs/QmXCiXYPFLbuk8X8eWv16F3PQFSp2ZEi8pstDrsSbYNybw [screenshot-remix-contract-invoke-buttons]: https://ipfs.infura.io/ipfs/QmUzyH4Vugc3vN52hna8r1r5hRzuLKTVXRC3vP4Huejqwt [screenshot-invoked-issue-bounty]: https://ipfs.infura.io/ipfs/QmQ1JYgeqgViBNxcSajU1NMh41v2UD2UFtYcdodDMv63zR [screenshot-array-getter-called]: https://ipfs.infura.io/ipfs/QmS17UXysJzMLibRzDShajzzMsV5Lkvi3jdjJQTYVqrQzu [link-semantic-versioning]: https://semver.org/ [link-unix-timestamp-wiki]: https://ru.wikipedia.org/wiki/UNIX-%D0%B2%D1%80%D0%B5%D0%BC%D1%8F [link-etherscan-example]: https://etherscan.io/address/0x69a70e299367ff4c3ba1fe8c93fbddd9b5b4771a [doc-solidity-types]: http://solidity.readthedocs.io/en/latest/types.html [doc-solidity-0.5.0-breaking]: https://solidity.readthedocs.io/en/v0.5.0/050-breaking-changes.html [doc-solidity-function-visibility]: https://solidity.readthedocs.io/en/v0.4.24/contracts.html#visibility-and-getters [doc-solidity-error-handling]: http://solidity.readthedocs.io/en/v0.4.24/control-structures.html#error-handling-assert-require-revert-and-exceptions [doc-solidity-modifiers]: https://solidity.readthedocs.io/en/v0.4.24/common-patterns.html?highlight=modifier#restricting-access [doc-solidity-events]: https://solidity.readthedocs.io/en/latest/contracts.html#events [doc-remix-analyzer]: https://remix.readthedocs.io/en/latest/analysis_tab.html [doc-remix-javascript-vm]: https://remix.readthedocs.io/en/latest/run_tab.html [doc-solidity-transfer]: https://solidity.readthedocs.io/en/latest/units-and-global-variables.html#address-related --- - **Kauri original title:** Среда разработки Remix - создайте свой первый Smart Contract - **Kauri original link:** https://kauri.io/sreda-razrabotki-remix-sozdajte-svoj-pervyj-smart/ac1bde5c6c5f48b786e1e17909da095a/a - **Kauri original author:** Alex D. (@alex-d) - **Kauri original Publication date:** 2019-05-31 - **Kauri original tags:** ethereum, remix, solidity - **Kauri original hash:** QmarPoh4HFpoFHefLsWn5VkQ1AgxppP9izXgiazf34RV5R - **Kauri original checkpoint:** QmSRv329t5c2hpHHf1Yz4XZomqgeBc8LVh9KNJC9z4PVDS