Как написать смарт контракт ethereum

Смарт контракты Ethereum: пишем простой контракт для ICO

В последнее время ко мне поступает огромное количество запросов за помощью в разработке смартконтракта для проведения ICO, при этом у меня не хватает времени, чтобы помочь каждому. Поэтому я решил написать этот небольшой пост (ссылка на видео в конце поста), в котором описываю очень простой смартконтракт для проведения crowdsale, который вы можете использовать в своих проектах.

Для экономии времени я написал контракт заранее. Давайте разберем его по шагам.

Смартконтракт является программой написанной на языке программирования. В нашем случае на языке Solidity. Для разработки простых контрактов я использую онлайн редактор и компилятор Remix.

Признаком хорошего тона считается начинать любую программу, в т.ч. и смартконтракт, с указания лицензии, на основе которой она распространяется. В нашем случае это GPL. Также можно указать себя в качестве автора контракта, конечно, если вы не пишете контракт для какого-нибудь скамового проекта, где стесняетесь указать себя в качестве автора.

Сразу после идет строка, которая указывает, какую версию компиллятора следует использовать. Если этой строки не будет, то смартконтракт не скомпилируется.

Далее идет непосредственно исходный код самого смартконтракта, который я структурировал в виде иерархии контрактов, каждый из которых реализует законченную функциональность. Это упрощает понимание и последующее использование кода в ваших контрактах.

Прежде всего следует понимать, что после загрузки смартконтракта в виртуальную машину Ethereum вы будете взаимодействовать с ним на общих основаниях, как и все остальные пользователи. Логично, что мы, как команда проекта, хотели бы находиться в привилегированных условиях, которые должны как минимум выражаться в том, что контракт должен сформировать именно на командные токены и конечно же отдал именно нам собранный эфир. Для этого контракт должен знать своего владельца и именно за это отвечает контракт «owned».

Контракт «owned» содержит лишь одно публичное поле «owner», значение которого инициализируется в конструкторе значением поля «sender» глобальной структуры «msg», таким образом, изначально владельцем контракта становится тот, кто осуществил его деплой.

Логично предусмотреть возможность смены владельца на случай, если наш private key будет скомпрометирован, для этого предусмотрена функция «changeOwner», которая получает в качестве параметра адрес нового владельца. Следует обратить на модификатор «onlyOwner», который определен внутри этого же смартконтракта. Модификаторы представляют собой очень удобную конструкцию, позволяющую сгруппировать и присвоить название условиям вызова функций смартконтракта. Модификатор «onlyOwner» проверяет, что вызов функции осуществляется с адреса, который сохранен в поле «owner».

Контракт «owned» полностью работоспособен и предельно прост, однако несет в себе одну скрытую угрозу, ведь при вызове функции «changeOwner» мы можем по ошибке указать несуществующий адрес, а значит, потеряем контроль над контрактом. Чтобы исправить этот недостаток, достаточно ввести еще поле, назовем его «candidate», а при вызове функции «changeOwner» будем сохранять новое значение сначала в «candidate», а перемещать его в «owner» будем, как только кандидат подтвердит свое вступление в права, вызвав со своего адресу функцию «confirmOwner».

Следующий в иерархии контракт — «Crowdsale», отвечает непосредственно за сбор средств и выдачу токенов и наследует рассмотренный ранее контракт «owned».

Особое внимание следует обратить на следующие элементы контракта:

  • Публичное поле «totalSupply», которое должно содержать общее количество токенов, выпущенных смартконтрактом;
  • Публичная карта «balanceOf», которое содержит информацию о балансах всех держателей токенов;
  • Событие Transfer, которое должно испускаться смартконтрактом при каждой операции перемещения токенов между держателями токенов.

Все эти три элемента объединяет одно, они являются обязательной частью стандарта ERC20, который необходимо соблюдать, чтобы информация о наших токенах корректно отображалась в кошельках пользователей и etherscan.io.

Конструктор смартконтракта «Crowdsale» предельно прост. Прежде всего инициализируется значение поля «totalSupply». Наш контракт выпускает 21 миллион токенов, из которых 20 миллионов сразу будут перемещены на баланс смартконтракта. Будем считать, что токены с адреса смартконтракта как раз и доступны для продажи. Оставшиеся токены, в нашем случае 1 миллион, будут записаны на адрес владельца контракта. Ну и в конце конструктора испускается событие «Transfer», которое помещается в блокчейн и информирует пользователей контракта о том, что с баланса контракта на баланс владельца контракта переведено соответствующее количество токенов. Именно испускание этого события позволит etherscan.io корректно отобразить держателей токенов и их балансы.

Ну и самая главная функция смартконтракта «Crowdsale», так называемая fallback функция, которая вызывается каждый раз, когда эфир поступает на адрес нашего смартконтракта. В самом начале осуществляется проверка, что на балансе смартконтракта есть хоть какое-то количество токенов для продажи. Далее устанавливаем фиксированную цену токенов — 5000 штук за 1 эфир. Затем вычисляем, сколько токенов необходимо отправить отправителю эфира. Количество переданных в транзакции средств записано в поле «value» глобальной структуры «msg» и указано оно в «wei», поэтому при определении количества токенов осуществляем перевод «wei» в «ether».

Читайте также:  Ферма майнинг криптовалют что это такое

Затем осуществляется проверка того, что на балансе смартконтракта есть достаточное количество токенов для продажи. Если запрошено больше токенов, чем есть у смартконтракта, то будем переводить все оставшиеся токены. Определяем стоимость в «wei» оставшихся токенов и возвращаем отправителю излишне переведенный эфир. Убеждаемся, что количество покупаемых токенов ненулевое, после чего записываем это количество токенов на баланс покупателя и списываем их с баланса смартконтракта. В конце не забываем испустить событие Transfer.

На этом собственно реализация функциональности сбора средств закончена, но теперь нужно сделать наш токен операбельным и реализовать еще некоторые функции стандарта ERC20. Это сделано в контракте «EasyToken», который наследуется от рассмотренного ранее контракта «Crowdsale».

Прежде всего определим 4 публичных поля, которые сейчас практически не используются никакими кошельками, но для порядка все же определим их. Здесь укажем полное и сокращенное наименование токена, а также количество дробных знаков. В нашем случае токен является неделимым, т.к. значение поля «decimals» установлено равным 0.

И наконец, единственной функцией смартконтракта «EasyToken», ради которой мы создавали этот контракт, является «transfer», которая также является частью стандарта ERC20 и позволит кошелькам пользователей осуществлять передачу токенов друг другу, переводить их на биржу и выводить их с нее. Реализация функции крайне проста, проверяется достаточность количества токенов на балансе отправителя, после чего баланс отправителя уменьшается, а баланс получателя увеличивается на запрошенное количество токенов. В конце испускается событие «Transfer». Теперь наш токен является операбельным и осталось сделать самое главное — предоставить владельцу контракта возможность вывести собранные эфиры. Это мы сделаем в контракте «EasyCrowdsale».

Функция «withdraw» имеет модификатор «onlyOwner», т.е. может быть вызвана только владельцем смартконтракта. Единственное, что она делает — переводит весь баланс смартконтракта на адрес владельца смартконтракта.

Несмотря на то, что рассмотренный нами смартконтракт является полностью функционально законченным и работоспособным, использовать его в реальном проекте я бы не рекомендовал. Чрезмерное упрощение логики контракта привело к тому, что такой контракт не обеспечивает защиту интересов инвесторов в должной мере, а именно, не устанавливает срок проведения crowdsale, не устанавливает минимальной границы сбора, не возвращает средства инвесторам в случае недостижения минимальной границы, а также содержит ряд известных уязвимостей на уровне компилятора языка Solidity, например, подвержен так называемой short address attack.

Многие из этих недостатков устранены в смартконтракте нашего проекта PROVER. Контракт PROOF загружен в Ethereum по этому адресу вместе с исходным кодом, с которым можно познакомиться. Можно даже проверить, как работает контракт, отправив на него реальный эфир, у нас как раз сейчас идет Pre-ICO:). На самом деле, мы будем рады, если вы присоединитесь к presale нашего проекта PROVER, который продлится до конца сентября. PROVER — это уникальная технология подтверждения подлинности видеоматериалов на базе блокчейн и видеоаналитики.

Также приглашаю пройти пререгистрацию в еще одном моем проекте — OpenLongevity — проект, выступающий инициатором, организатором и гарантом открытости клинических исследований терапий старения. Мы включим самих пациентов в глобальное движение по поиску и тестированию потенциальных терапий старения, которые, доказав свою эффективность, сразу же станут частью их собственной жизни.

Источник

Работа со смарт-контрактами через Ethereum RPC API

Всем привет. В этой статье мы рассмотрим основные приемы по публикации смарт-контрактов и взаимодействию с ними с использованием Ethereum RPC API. Обсуждаемые методы API позволяют решать такие задачи:

  1. Создание счёта.
  2. Создание корректного смарт-контракта.
  3. Получение информации со смарт-контракта.
  4. Изменение состояния смарт-контракта.

Содержание:

  • Некоторые общие замечания
  • Упаковка параметров и возвращаемых данных
  • Создание счёта и работа с ним
  • Создание смарт-контракта
    • Компиляция исходного кода смарт-контракта
    • Извлечение кода из транзакции
    • Рассчёт стоимости опубликования контракта
    • Выполнение транзакции на публикацию контракта
  • Взаимодействие со смарт-контрактом
    • Создание контракта с параметрами
    • Идентификация методов контракта
    • Вызов методов запроса информации
    • Вызов методов, изменяющих состояние контракта

Описание используемого API:

Некоторые общие замечания

  1. Все предлагаемые действия иллюстрируются реальными данными из тестовой сети Rinkeby (на момент написания статьи).
  2. Состояние транзакций, счетов и смарт-контрактов в Rinkeby можно отслеживать по сайту https://rinkeby.etherscan.io/ (для подсети Ropsten будет, соответственно, https://ropsten.etherscan.io/).

Упаковка параметров и возвращаемых данных

Эта часть также носит общий характер и поэтому вынесена в отдельную главку, но её изучение можно отложить до момента, когда непосредственно понадобится использовать параметры или разобрать ответ смарт-контракта.

Входящий (параметры) или исходящий пакет данных для контракта формируется по следующему принципу:

  • Фиксированные по длине типы данных (address, uint32, bytes32) передаются с выравниванием до 32-байтного слова (64 hex-цифры).
  • Переменные по длине типы данных (строковые, массивы) передаются по следующей схеме:
    • В позиции объекта в списке передается смещение блока с его данными относительно начала пакета (с выравниванием до 32-байтного слова).
    • В первом 32-байтном слове блока передается число единиц данных.
    • В последующих 32-байтных словах передаются сами данные.

Рассмотрим, например, блок, в котором передается следующий набор данных: address , string , uint32 , address[] (в начале каждой 32-байтовой строки для удобства дан ее шестнадцатеричный адрес относительно начала блока).

Читайте также:  Мантэк чиа мэниван чиа нейгун искусство омоложения организма

В строке 000 передается адрес 0х570f5d143ee469d12dc29bf8b3345fa5536476d9 .
В строке 020 передается ссылка на блок, описывающий переменную типа string — 0x80 байт от начала блока.
В строке 040 передается целое число 0х1234 .
В строке 060 передается ссылка на блок, описывающий массив address[] — 0xc0 байт от начала блока.
В строке 080 передается счетчик символов переменной типа string — 3 .
В строке 0a0 передаются сами символы переменной типа string — слово New .
В строке 0c0 передается счетчик элементов массива address[] — 2 .
В строке 0e0 передается первый элемент массива address[] — 0хaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa .
В строке 100 передается второй элемент массива address[] — 0хbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb .

Внимание! Весь блок передается одним слитным массивом:

Создание счёта и работа с ним

Для создания нового счёта используется метод personal_newAccount , в качестве параметра которому передаётся пароль, который в дальнейшем будет использоваться для разблокировки счёта:

В ответ приходит идентификатор счёта, в данном случае — 0xfbeda9914b78b58a0f0e810298f9d545f8379f8d .

Все дальнейшие манипуляции мы будем производить с этого счета — 0xfbeda9914b78b58a0f0e810298f9d545f8379f8d .

Теперь нам необходимо положить на него некоторую сумму для оплаты исполнения транзакций. Так как в тестовой сети Rinkeby поучаствовать в майнинге человеку со стороны невозможно, то для пополнения счетов предусмотрен специальный «кран», процедура использования которого описана здесь — https://faucet.rinkeby.io/.

Для пополнения счета необходимо:

    Зарегистрироваться на github.com и создать новый gist:

Вписать в него идентификатор счета:

и создать публичный gist («Create public gist»)

Далее нам понадобится URL gist-а, в нашем случае — https://gist.github.com/oldmadjackal/6e246d3f199a80bfb82bfe626faad604:

Переходим на https://faucet.rinkeby.io/, вводим URL gist-а в поле и выбираем сумму пополнения счёта:

Если всё хорошо, то появится зелёное сообщение с подтверждением. Транзакция может пройти не очень быстро — за 5-10 минут.
Смотрим счёт на EtherScan:

  • Для повторных пополнений можно использовать тот же gist.
  • Для того, чтобы использовать средства со счета — выполнять транзакции любого вида — счёт необходимо разблокировать. Для разблокировки счёта используется метод personal_unlockAccount. В качестве параметров указываются идентификатор счёта, пароль и продолжительность разблокировки в секундах:

    Создание смарт-контракта

    Технически смарт-контракт создаётся транзакцией, содержащей в поле данных байт-код контракта и блок параметров инициализации, если такие предусмотрены смарт-контрактом.

    Байт-код контракта для формирования транзакции создания может быть получен следующими способами:

    • Компиляцией из исходного текста смарт-контракта.
    • Извлечением из другой транзакции создания такого же смарт-контракта.

    Второй способ можно использовать, чтобы хранить шаблон создания контракта непосредственно в блокчейне — тогда достаточно знать идентификатор транзакции, чтобы его извлечь и «клонировать».

    Технически можно извлечь код транзакции непосредственно по идентификатору уже существующего смарт-контракта (метод eth_getCode), но этого делать НЕЛЬЗЯ, так как он выдаёт байт-код, подвергшийся специальной обработке при создании смарт-контракта (в частности, из него удаляются блоки инициализации).

    Компиляция исходного кода смарт-контракта

    Для компиляции исходного кода на Solidity я использую Remix — https://remix.ethereum.org/.

    Вводим текст контракта, если ошибок нет — в поле Bytecode будет находиться собственно байткод, в нашем случае:

    Извлечение кода из транзакции

    Для извлечения кода из транзакции используется метод eth_getTransactionByHash . В нашем примере «клонируемый» смарт-контракт был создан транзакцией 0xc4d20bb8f9eede45968fc6bc850397e92b9f263eeb11200615cc08562d46c2e7 .

    В тэге input ответа содержится байткод контракта.

    Обратите внимание, что если конструктор смарт-контракта предусматривает использование параметров, то кроме байт-кода в тэге input будут указаны значения параметров, следующих сразу следом за байт-кодом. При создании шаблона смарт-контракта для последующего клонирования можно использовать нулевые значения параметров.

    Расчет стоимости опубликования контракта

    Для расчета стоимости опубликования контракта используется метод eth_estimateGas , в параметрах которого следует указать номер счета (тэг from ), с которого будет создан контракт, а также байт-код контракта (тэг data ). В ответе будет указано необходимое количество Gas.

    Если конструктор смарт-контракта предполагает использование параметров, то они должны быть включены в запрос в тэге data сразу после байт-кода. В противном случае расчет Gas будет некорректным.

    Обратите внимание, что в некоторых случаях смарт-контракты содержат семантические (не синтаксические) ошибки, которые пропускаются компилятором, но приводят к неработоспособности контракта. Одним из признаков появления такой ошибки будет резкое — в разы, возрастание стоимости создания контракта.

    Выполнение транзакции на публикацию контракта

    Для публикации контракта используется метод eth_sendTransaction . В качестве параметров методу передаются:

    • номер счета, с которого создаётся контракт (тэг from );
    • стоимость публикации в Gas (тэг gas , берется из предыдущего пункта);
    • байт-код контракта с пристыкованным блоком параметров конструктора (тэг data , должен полностью совпадать с использованным в предыдущем пункте).

    В ответ мы получим номер транзакции:

    или сообщение об ошибке:

    Теперь необходимо дождаться завершения транзакции и получить результат её исполнения — создан контракт или нет. Для этого используется метод eth_getTransactionReceipt :

    Пока транзакция находится в «листе ожидания» (Pending Txn), будет выдаваться следующий ответ:

    После исполнения транзакции мы получим полную «квитанцию», в теге contractAddress которой будет содержаться адрес созданного смарт-контракта:

    Если в течении 5 минут квитанция не получена, то либо в сети проблемы, либо ваш узел не разослал проводку по сети. Чтобы понять истинную причину, следует просмотреть очередь Pending Txn на etherscan.io (https://rinkeby.etherscan.io/txsPending). Если транзакции там нет — значит, надо перезапустить клиент Ethereum и повторить публикацию заново.

    Читайте также:  Вексель виды векселей доходность векселя

    Теперь следует проверить, корректно ли создался смарт-контракт. Для этого можно использовать метод eth_getCode — получение кода контракта по его адресу:

    Если контракт создался некорректно, то будет получен ответ:

    Если в тэге result выдаются некоторые данные, отличные от 0x , то контракт создан успешно.

    Взаимодействие со смарт-контрактом

    Для демонстрации взаимодействия со смарт-контрактом будем использовать следующий образцовый смарт-контракт:

    При создании контракта (конструктор — функция Test ) ему передаются адрес Продавца ( seller_ ) и адрес Банка ( bank_ ). Метод GetStatus возвращает адресa Продавца, Банка и текущий статус контракта. Метод SetBankCert предназначен для сохранения в контракте некоторого цифрового идентификатора с переводом в статус «Confirmed».

    Создание контракта с параметрами

    Если конструктор смарт-контракта использует параметры (как в нашем демонстрационном примере), то они должны быть «упакованы» в соответствии с описанием «Упаковка параметров и возвращаемых данных» и присоединены в хвост к байт-коду контракта.

    В нашем случае, передача адреса Продавца 0x794ce6de39fa2d274438cc1692db04dfb5bea836 и адреса Банка 0xfbeda9914 b78b58a0f0e810298f9d545f8379f8d при создании смарт-контракта будет выглядеть следующим образом (байт-код контракта кончается на 0029 ):

    Еще раз обращаю внимание, что расчет Gas должен выполняться для всего блока Байт-код + Параметры, иначе контракт или не будет создан, или будет неработоспособен.

    Наш тестовый демо-контракт был создан по адресу 0x3d20e579f5befdc7d3f589adb6155f684d9a751c .

    Идентификация методов контракта

    Для идентификации метода смарт-контракта, к которому мы обращаемся, используются первые 4 байта (8 шестнадцатеричных цифр) от хэша описания метода.

    Например, для метода GetStatus демо-контракта описанием будет GetStatus() , а для метода SetBankCert — SetBankCert(address) . Особое внимание следует обратить на отсутствие пробелов в описании — были печальные прецеденты :(.

    Для определения хэша используется метод web3_sha3 , при этом строчное значение следует давать в шестнадцатеричном представлении (для GetStatus() это будет 0x4765745374617475732829 ):

    Соответственно, идентификатором метода GetStatus будет 0xabd95b95 , для метода SetBankCert идентификатор — 0x1bb71149 .

    Вызов методов запроса информации

    Для вызова методов, не связанных с изменением состояния контракта (например, для получения информации о его текущем статусе), может быть использован метод API eth_call .

    Запрос метода имеет такую структуру:

    Блок формируется следующим образом:

    где формируются, как указано в пункте «Упаковка параметров и возвращаемых данных».

    Если метод не предполагает наличия параметров, то блок состоит только из идентификатора метода.

    Например, для вызова метода GetStatus демо-контракта Test используется запрос:

    на который будет дан ответ:

    Разберем полученный ответ в соответствии с правилами пункта «Упаковка параметров и возвращаемых данных» и с учетом описания метода GetStatus — function GetStatus() constant returns (address, address, string retVal).

    Для удобства анализа разложим ответ на 32-байтные слова:

    Исходя из описания мы ожидаем получение следующего набора переменных: address, string. Таким образом:

    • в строке 000 находится адрес Продавца (тип address ) — 0x794ce6de39fa2d274438cc1692db04dfb5bea836
    • в строке 020 находится адрес Продавца (тип address ) — 0xfbeda9914b78b58a0f0e810298f9d545f8379f8d
    • в строке 040 находится ссылка на блок описания статуса контракта (тип string ) — блок начинается с адреса 060
    • в строке 060 находится счетчик символов в строке статуса контракта — 3 символа
    • в строке 080 находятся собственно символы статуса контракта в шестнадцатеричной кодировке — New

    Вызов методов, изменяющих состояние контракта

    Для вызова методов, изменяющих состояние контракта, должен быть использован метод API eth_sendTransaction .

    Запрос метода имеет такую структуру:

    должен иметь баланс, достаточный для выплаты . Кроме того, следуют учитывать, что контракт может содержать внутренние условия по контролю , как, например, в методе SetBankCert нашего демо-контракта.
    Блок формируется следующим образом:

    где формируются, как указано в параграфе «Упаковка параметров и возвращаемых данных».

    Если метод не предполагает наличия параметров, то блок состоит только из идентификатора метода.

    Например, для вызова метода SetBankCert(«0хf7b0f8870a5596a7b57dd3e035550aeb5af16607») демо-контракта, будут иметь следующий вид:

    Для определения стоимости исполнения, как и при создании смарт-контракта, используется метод eth_estimateGas , в который передаются всё те же параметры, которые затем будут переданы в методе eth_sendTransaction .

    Как показывает опыт, если в вызываемом методе содержится транзакционный вызов других смарт-контрактов, то сумма Gas может быть рассчитана неверно и транзакция не исполнится. Поэтому рекомендую указывать заведомо большее количество Gas, так как, по идее, излишек использован не будет. В таких случаях я указываю количество Gas, близкое к максимальному — 0х200000 .

    Далее вызываем метод eth_sendTransaction :

    и получаем в ответ идентификатор транзакции:

    Как и в случае с созданием смарт-контракта, ожидаем исполнения транзакции, запрашивая квитанцию (метод eth_getTran sactionReceipt):

    Как только квитанция пришла — транзакция исполнилась:

    Однако, само по себе исполнение транзакции вовсе не означает, что смарт-контракт переключился в нужное состояние, кроме того, могут не исполниться внутренние транзакционные вызовы других смарт-контрактов.

    На мой взгляд, самый надежный способ проверить, что всё отработало — снова запросить состояние смарт-контракта и убедиться, что всё изменилось «как надо».

    Разобрав ответ, мы увидим, что статус изменился на «Confirmed».

    Резюме

    В этом руководстве мы рассмотрели выполнение ряда задач, связанных с публикацией смарт-контрактов и взаимодействием с ними с помощью RPC API блокчейн-платформы Ethereum. Разобрались, как создавать счёт, как создавать корректный смарт-контракт, получать по нему информацию и изменять его состояние, а также выяснили, как с этим смарт-контрактом взаимодействовать.

    Если у вас возникнут дополнительные вопросы, рад буду ответить/подсказать.

    Источник

    Оцените статью