Погружение в разработку на Ethereum. Часть 4: деплой и дебаг в truffle, ganache, infura

В прошлой статье мы рассмотрели разработанное приложение на Ethereum. Но обошли стороной вопрос как происходит процесс разработки. Ясно, что это не просто написание кода, который сразу работает. Большую часть времени придется потратить на то, чтобы доводить код, который “почти готов”, до рабочего состояния. Деплой, тестирование, отладка — все это в той или иной мере уже затрагивалось здесь например в этих неплохих статьях: раз, два, три (список не полный). В этой статье мы дадим небольшой обзор и возможно в чем-то повторимся, но постараемся сфокусироваться на тех моментах, которые нам показались важными или недосказанными. Плюс за последнее время некоторые штуки изменились, и огромное количество инструкций оказалось устаревшим. Постараемся немного подправить ситуацию.

Проект в truffle

Для тестирования и деплоя смарт контрактов мы пользуемся Truffle, он скрывает часть низкоуровневой работы за абстракциями, что очень удобно. Описанная версия — 4.0.6. Это не единственный фреймворк, есть еще Embark и Dapple, но по ним ничего сказать не можем, не приходилось работать. Для инициализации проекта надо выполнить команду (выполнится в текущей папке, поэтому предварительно создайте папку проекта и перейдите в нее):

$ truffle init

Создается только базовая структура из папок contracts, migrations и tests. В contracts и migrations вы можете увидеть смарт контракт Migrations, который отвечает за логику деплоя (в терминах трюфеля — миграции). А логика примерно такая: в папке migrations вы складываете скрипты и называете их по шаблону 1_description.js, 2_another_one.jsn_etc.js. Самое важное в названии — это индекс, который идет в начале, после этого можно добавлять любое описание, которое нужно только для читабельности. Индекс же используется для выполнения миграций в порядке нумерации. Смарт контракт Migrations используется для того, чтобы сохранять какие из скриптов миграций уже выполнились. Так что если в процессе разработки добавлять новые контракты и новую логику деплоя, то предыдущий успешный прогресс передеплоивать не надо. Лично мы этим не пользуемся, вместо этого имеем фиксированное количество миграций, редактируем и запускаем их каждый раз заново с помощью truffle migration --reset.
Вы можете вручную создавать файлы контрактов, миграций и тестов, но в truffle есть и специальные команды:

$ truffle create contract ExampleContract $ truffle create migration ExampleMigration $ truffle create test ExampleContract

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

Работа с проектом

В более ранних версиях инициализация truffle init создавала сразу небольшой пример (Metacoin и ConvertLib). Чтобы в последних версиях увидеть этот и другие примеры, можно воспользоваться фичей, называемой Truffle Boxes. Боксы созданы для того, чтобы получить полноценный пример проекта Truffle и его взаимодействия с разными web-средствами, например ReactJS. Здесь перечислен список боксов, как официальных, так и созданных сообществом. Создайте директорию для нового проекта и перейдите в нее. Затем выполним команду:

$ truffle unbox metacoin

получите ту структуру проекта, которая в старых версиях создавалась по умолчанию после truffle init. Это пример с базовым токеном Metacoin, который можно пересылать от пользователя к пользователю и с помощью библиотеки ConvertLib смотреть баланс в эфире при фиксированном курсе обмена. Кроме того, что тут показано как создавать и использовать смарт контракт и библиотеку, здесь есть еще и пример тестов на JavaScript и Solidity (подробнее о написании тестов можете почитать здесь, обратите внимание, что там как раз рассматривается более старая версия Truffle). Давайте бегло рассмотрим как можно в тестовом режиме собрать и проверить этот проект. Для начала запустим development консоль:

$ truffle develop

Увидите подобный текст:

Truffle Develop started at http://localhost:9545/  Accounts: (0) 0x627306090abab3a6e1400e9345bc60c78a8bef57 (1) 0xf17f52151ebef6c7334fad080c5704d77216b732 (2) 0xc5fdf4076b8f3a5357c5e395ab970b5b54098fef (3) 0x821aea9a577a9b44299b9c15c88cf3087f3b5544 (4) 0x0d1d4e623d10f9fba5db95830f7d3839406c6af2 (5) 0x2932b7a2355d6fecc4b5c0b6bd44cc31df247a2e (6) 0x2191ef87e392377ec08e7c08eb105ef5448eced5 (7) 0x0f4f2ac550a1b4e2280d04c21cea7ebd822934b5 (8) 0x6330a553fc93768f612722bb8c2ec78ac90b3bbc (9) 0x5aeda56215b167893e80b4fe645ba6d5bab767de  Mnemonic: candy maple cake sugar pudding cream honey rich smooth crumble sweet treat  truffle(develop)>

Что делает эта команда? Она поднимает тестовое окружение и дает доступ к нему через консоль. Тестовое окружение — это то, что вы могли видеть в более старых руководствах под названием TestRPC. На самом деле это оно и есть, просто команда Truffle взяла его под свое управление и переименовала в Ganache. Но об этом напишем далее, а пока перейдем к консоли. Выполним полный цикл команд для компиляции, миграции и тестирования:

truffle(develop)> compile

Результат

Compiling ./contracts/ConvertLib.sol... Compiling ./contracts/MetaCoin.sol... Compiling ./contracts/Migrations.sol... Writing artifacts to ./build/contracts

truffle(develop)> migrate

Результат

Using network 'develop'.  Running migration: 1_initial_migration.js   Deploying Migrations...   ... 0x29619f8ba9b9e001bef885c8ca2fbee45beab738adc41c7f9e2e8273fbc67e9f   Migrations: 0x8cdaf0cd259887258bc13a92c0a6da92698644c0 Saving successful migration to network...   ... 0xd7bc86d31bee32fa3988f1c1eabce403a1b5d570340a3a9cdba53a472ee8c956 Saving artifacts... Running migration: 2_deploy_contracts.js   Deploying ConvertLib...   ... 0x02318651545ac96670af626ef7795cb928d7504afc3f856258058ce579d47fe6   ConvertLib: 0x345ca3e014aaf5dca488057592ee47305d9b3e10   Linking ConvertLib to MetaCoin   Deploying MetaCoin...   ... 0x486c572bbb2df30bb166f5507423d394807b5b92041860968a7d5eb162e42e48   MetaCoin: 0xf25186b5081ff5ce73482ad761db0eb0d25abfbf Saving successful migration to network...   ... 0x059cf1bbc372b9348ce487de910358801bbbd1c89182853439bec0afaee6c7db Saving artifacts...

truffle(develop)> test

Результат

Using network 'develop'.  Compiling ./contracts/ConvertLib.sol... Compiling ./contracts/MetaCoin.sol... Compiling ./test/TestMetacoin.sol... Compiling truffle/Assert.sol... Compiling truffle/DeployedAddresses.sol...     TestMetacoin     ✓ testInitialBalanceUsingDeployedContract (62ms)     ✓ testInitialBalanceWithNewMetaCoin (47ms)    Contract: MetaCoin     ✓ should put 10000 MetaCoin in the first account     ✓ should call a function that depends on a linked library (54ms)     ✓ should send coin correctly (117ms)     5 passing (796ms)

Можно повызывать методы задеплоенных контрактов вручную, например так:

truffle(develop)> var metaCoin truffle(develop)> MetaCoin.deployed().then( function(instance) { metaCoin = instance } ); truffle(develop)> metaCoin.getBalance(web3.eth.coinbase) BigNumber { s: 1, e: 4, c: [ 10000 ] } truffle(develop)> _.toNumber() 10000 truffle(develop)> metaCoin.sendCoin( web3.eth.accounts[2], 3000 ) { tx: '0x9f59085a9f22c0bd691b890370bcffd7eedce1327a3bb525a2de3edf9db0d279',   receipt:    { transactionHash: '0x9f59085a9f22c0bd691b890370bcffd7eedce1327a3bb525a2de3edf9db0d279',      transactionIndex: 0,      blockHash: '0x24e8913b6f707bb5e5acbaa054fef9dabd548a561dc988763209f0aeed9a57b5',      blockNumber: 12,      gasUsed: 51024,      cumulativeGasUsed: 51024,      contractAddress: null,      logs: [ [Object] ] },   logs:    [ { logIndex: 0,        transactionIndex: 0,        transactionHash: '0x9f59085a9f22c0bd691b890370bcffd7eedce1327a3bb525a2de3edf9db0d279',        blockHash: '0x24e8913b6f707bb5e5acbaa054fef9dabd548a561dc988763209f0aeed9a57b5',        blockNumber: 12,        address: '0xf25186b5081ff5ce73482ad761db0eb0d25abfbf',        type: 'mined',        event: 'Transfer',        args: [Object] } ] } truffle(develop)> metaCoin.getBalance(web3.eth.coinbase) BigNumber { s: 1, e: 3, c: [ 7000 ] } truffle(develop)> _.toNumber() 7000

Как видите все выполняется мгновенно и можно отследить изменения: отправили 3000 с основного адреса на другой, видим, что баланс уменьшился. Выходить из консоли командой:

truffle(develop)> .exit

Подключение к ноде

Truffle development это режим с собственной тестовой нодой. Для подключения к реальной сети или тестнету используется команда truffle console, которая полностью аналогична develop, но не поднимает тестовое окружение. Чтобы продемонстрировать это, не обязательно запускать geth, можно воспользоваться например уже упомянутой Ganache, заменившей собой TestRPC. Запустим командой:

$ ganache-cli

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

Но если вы просто сделали truffle unbox metacoin и еще никак не меняли конфигурационные файлы, то подключиться не получится

$ truffle console No network available. Use `truffle develop` or add network to truffle.js config. 

Поэтому добавим в truffle.js следующее:

module.exports = {   networks: {     development: {       host: "localhost",       port: 8545,       network_id: "*"     }   } };

Вы можете увидеть еще и truffle-config.js. Это то же самое, но для Windows. Лишний для вашей системы файл можно удалить.
Этот конфиг даст возможность трюфелю подключаться к любой сети, доступной на localhost:8545. Это значения по умолчанию для geth и ganache-cli. Если вы используете GUI-версию Ganache, зайдите в настройки и при необходимости измените порт и перезапустите (кнопка “save and restart”).

Если застряли на окне с логотипом, то скорее всего у вас на этом же порту уже что-то запущено, geth, ganache-cli/testrpc или что-то еще

Теперь можно подключиться, выполняем команду:

$ truffle console

И можно попробовать например сделать:

truffle(development)> migrate --reset

В GUI-версии вы сразу сможете увидеть изменение баланса аккаунта, с которого прошел деплой, если зайдете во вкладку blocks — то увидите смайненные блоки и сколько газа расходовалось на транзакции в них. Щелкнув по каждому из них можно получить еще более подробную информацию. Вкладка transactions таким же образом покажет вам все прошедшие транзакции.

Много скриншотов






В консольной версии будет та же информация, но в виде стены логов:

Развернуть

net_version eth_accounts eth_accounts net_version net_version eth_sendTransaction    Transaction: 0x29619f8ba9b9e001bef885c8ca2fbee45beab738adc41c7f9e2e8273fbc67e9f   Contract created: 0x922194d35a507e5905fa4f2c9e7172ee8535272a   Gas usage: 269607   Block Number: 1   Block Time: Mon Feb 05 2018 10:28:17 GMT+0300 (MSK)  eth_newBlockFilter eth_getFilterChanges eth_getTransactionReceipt eth_getCode eth_uninstallFilter eth_sendTransaction    Transaction: 0xfafc4352cc1dde57e46954d7ebd3a59232599081a253dd8705847a380ae5b06b   Gas usage: 41981   Block Number: 2   Block Time: Mon Feb 05 2018 10:28:17 GMT+0300 (MSK)  eth_getTransactionReceipt eth_accounts net_version net_version eth_sendTransaction

Используйте что считаете удобнее.
Кстати если вы читали другие инструкции по этой же теме, то наверное уже знаете, что не обязательно вызывать консоль, чтобы компилировать и деплоить, можно просто вызвать например

truffle migrate --reset

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

Пакеты

Ни в каком языке вы не обойдетесь лишь core-функциональностью. Изобретать велосипеды непродуктивно и опасно, а со смарт контрактами, где вы рискуете чужими деньгами, это особенно критично. Поэтому возникает вопрос где взять эту уже проверенную дополнительную функциональность. В Truffle для этого есть система пакетов, которые доступны в двух вариантах установки (не считая обычного копипаста): с помощью npm и с помощью ethpm.

До этого на примере Metacoin мы видели простейший токен. Токены используются довольно часто, даже Crypto Kitties — это по сути токены, хоть и оригинального стандарта. Основной стандарт токенов сейчас — это ERC20. Чтобы соответствовать стандарту токен должен реализовывать набор функций, которые могут обеспечивать универсальное и безопасное использование в кошельках, биржах и т.д. Тут очень полезен оказывается пакет zeppelin-solidity от OpenZeppelin — набор библиотек для часто используемых паттернов в смарт контрактах. В этой статье например уже было описано использование этого пакета. Рассмотрим не использование, а способы установки и подключения. Для начала тот, который описан в инструкции на гитхабе проекта. В корне проекта truffle выполните:

$ npm init -y $ npm install -E zeppelin-solidity

После чего в папке node_modules у вас появится zeppelin-solidity, из которого в смарт контракте можно подключать требуемые файлы, например ownable, строчкой

import 'zeppelin-solidity/contracts/ownership/Ownable.sol';

Но существует еще один способ установки пакетов, который разработан специально для пакетов в Ethereum: EthPM. Пакеты хранятся на IPFS. Список пакетов доступен по ссылке. Он интегрирован в трюфель и чтобы установить тот же самый zeppelin-solidity можно выполнить:

$ truffle install zeppelin

Добавится папка installed_contracts, подключать ее содержимое так же, как и из папки node_modules:

import ‘zeppelin/contracts/ownership/Ownable.sol’;

Но если вы сравните версии, которые установлены тем и другим способом, то обнаружите, что они разные. И по крайней в данный момент версия npm новее (на момент написания 1.6.0 против 1.3.0 в EthPm). Так что хоть идеологически EthPM и интереснее, но пока наверное лучше устанавливать пакеты с помощью npm

Отладка

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

$ mkdir FaultyContract && cd FaultyContract $ truffle init $ truffle create contract FaultyContract $ truffle create migration deploy

В файле contracts/FaultyContract.sol добавьте недостающий код:

pragma solidity ^0.4.4;  contract FaultyContract {    int public result;    function divideXbyY( int x, int y ) public {     y -= y;     result = x/y;   } }

Как видите здесь неизбежно деление на 0.

В файле migration/xxxx_deploy.js (xxxx — сгенерированный id, может быть разный) добавьте недостающий код для деплоя:

var FaultyContract = artifacts.require("./FaultyContract.sol");  module.exports = function( deployer ) {   deployer.deploy( FaultyContract ); }

Откроем develop консоль, скомплируем и задеплоем наш контракт

$ truffle develop truffle(develop)> migrate

Получим контракт и вызовем проблемную функцию:

truffle(develop)> var faultyContract; truffle(develop)> FaultyContract.deployed().then( function(instance) { faultyContract = instance } ); truffle(develop)> faultyContract.divideXbyY(16, 4);  Error: VM Exception while processing transaction: invalid opcode     at Object.InvalidResponse (/usr/lib/node_modules/truffle/build/cli.bundled.js:43303:16)     at /usr/lib/node_modules/truffle/build/cli.bundled.js:331156:36     at /usr/lib/node_modules/truffle/build/cli.bundled.js:314196:9     at XMLHttpRequest.request.onreadystatechange (/usr/lib/node_modules/truffle/build/cli.bundled.js:329855:7)     at XMLHttpRequestEventTarget.dispatchEvent (/usr/lib/node_modules/truffle/build/cli.bundled.js:70159:18)     at XMLHttpRequest._setReadyState (/usr/lib/node_modules/truffle/build/cli.bundled.js:70449:12)     at XMLHttpRequest._onHttpResponseEnd (/usr/lib/node_modules/truffle/build/cli.bundled.js:70604:12)

Как видите не, очень информативно, неизвестно в каком месте ошибка и что значит invalid opcode. В Truffle с версии 4 доступна команда debug (пока бета), позволяющая заново перевыполнить транзакцию построчно. Но для этого нужно получить хеш транзакции, а в ошибке даже его нет. Чтобы увидеть хеш, запустите еще один экземпляр truffle develop с флагом --log:

$ truffle develop --log

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

truffle(develop)> faultyContract.divideXbyY(16, 4);

В окне с логом будет что-то похожее на

   develop:testrpc eth_sendTransaction +0ms   develop:testrpc  +27ms   develop:testrpc   Transaction: 0x21073e12e7c8fb785347d7bd5d974d4954379dcace7b53d452c03b39ca007b9e +1ms   develop:testrpc   Gas usage: 6721975 +0ms   develop:testrpc   Block Number: 6 +0ms   develop:testrpc   Block Time: Tue Feb 06 2018 10:32:27 GMT+0300 (MSK) +0ms   develop:testrpc   Runtime Error: invalid opcode +0ms   develop:testrpc  +0ms

Возьмем хеш транзакции и передадим его в команду debug:

truffle(develop)> debug 0x21073e12e7c8fb785347d7bd5d974d4954379dcace7b53d452c03b39ca007b9e

Вам должны вывестись подсказки дальнейших команд. Чтобы понять хотя бы в какой строчке ошибка, можно использовать команду n (step next):

debug(develop:0x21073e12...)> n  FaultyContract.sol | 0x345ca3e014aaf5dca488057592ee47305d9b3e10:  8:   int public result; 9: 10:   function divideXbyY( int x, int y ) public {       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^  debug(develop:0x21073e12...)>

Каждый шаг будет отображаться несколько строк из кода и подчеркиваться часть, которая в данный момент исполняется. Есть возможность перейти на совсем низкий уровень и по-очереди выполнять каждый opcode (команды виртуальной машины Ethereum) и смотреть состояние стека. Сделаем еще один шаг и попробуем вывести состояние стека:

debug(develop:0x21073e12...)>  FaultyContract.sol | 0x345ca3e014aaf5dca488057592ee47305d9b3e10:  9: 10:   function divideXbyY( int x, int y ) public { 11:     y -= y;              ^  debug(develop:0x21073e12...)> p  FaultyContract.sol | 0x345ca3e014aaf5dca488057592ee47305d9b3e10:  (55) DUP1   00000000000000000000000000000000000000000000000000000000c6329782   000000000000000000000000000000000000000000000000000000000000009b   0000000000000000000000000000000000000000000000000000000000000010   0000000000000000000000000000000000000000000000000000000000000004 (top)

Видим, что последними в стеке лежат наши x и y, 16 и 4. Это конечно не сильно удобный способ и нужно разбираться с опкодами и как они выполняются в виртуальной машине Ethereum. Если интересно — можете глянуть например yellow paper (H.2. Instruction Set). Но у нас задача просто найти строчку с ошибкой. Продолжим выполнять next step, пока не получим что-то подобное:

10:   function divideXbyY( int x, int y ) public { 11:     y -= y; 12:     result = x/y;                  ^^^  debug(develop:0x21073e12...)>  Transaction halted with a RUNTIME ERROR.  This is likely due to an intentional halting expression, like assert(), require() or revert(). It can also be due to out-of-gas exceptions. Please inspect your transaction parameters and contract code to determine the meaning of this error. truffle(develop)>

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

В этом плане для одиночных контрактов и несложных связей можем посоветовать Remix IDEэтой статье например автор пользуется ей для деплоя), там есть практически полноценный дебаг с возможностью видеть значения переменных на каждом шаге. Интерфейс интуитивный, смотрите как уже рассмотренный пример можно будет отладить там:

В консоли внизу видим ошибку, нажимаем дебаг:


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

Удаленные ноды Infura

В прошлой статье мы рассматривали плагин Metamask, который позволяет подключаться к блокчейну без использования локального синхронизированного Ethereum-клиента. Это возможно благодаря сервису Infura. Вы тоже можете получить доступ к нодам Infura и подключаться к ним через truffle. Для этого во-первых нужно зарегистрироваться на их сайте, в письме вам придут ссылки с персональными токенами для доступа. Давайте попробуем задеплоить пример Metacoin на Ropsten без локальной ноды.
Создаем тестовый проект как обычно:

$ mkdir metacoin && cd metacoin $ truffle unbox metacoin

Далее нам понадобится дополнительный пакет HDWalletProvider, с помощью которого Truffle может подписывать транзакции

$ npm init $ npm install $ npm install truffle-hdwallet-provider

Добавим провайдер в настройки трюфеля таким кодом:

var HDWalletProvider = require("truffle-hdwallet-provider"); var mnemonic = "correct horse battery staple correct horse battery staple correct horse battery staple" module.exports = {   networks: {     development: {       host: "localhost",       port: 8545,       network_id: "*"     },     ropsten: {       provider: function() {         return new HDWalletProvider(mnemonic, "https://ropsten.infura.io/<ваш токен из письма>")       },       network_id: 3,       gas: 4000000,       gasPrice: 21000000000     }   } };

Пришлось задать gas и gasPrice, потому что по крайней мере у нас значения по умолчанию не подошли. Не забудьте вставить токен из письма, а еще придумайте свою мнемонику из 12 разных (а не как в примере) слов — она используется для генерации аккаунтов и если кто-то ей завладеет, то сможет сгенерировать те же самые аккаунты и воспользоваться ими без вашего ведома. Например если вы попробуете использовать мнемонику в этой статье, то сможете воспользоваться тем эфиром (0.3), который мы туда переслали (если его не израсходует кто-то еще). В этом конфиге также оставлен development, выбирать между этими двумя сетями можно запуская truffle с соответствующим названием после флага --network:

 $ truffle console --network ropsten

Перед тем как вызывать migrate, требуется пополнить баланс сгенерированного аккаунта. Узнаем адрес и баланс командами:

 truffle(ropsten)> web3.eth.getAccounts( function(e,r) { console.log(r[0]); } ); undefined 0x0bb542704819b5e6a28deb2b73245be57ce0e78b truffle(ropsten)> web3.eth.getBalance('0x0bb542704819b5e6a28deb2b73245be57ce0e78b', function(e, r) { console.log( web3.fromWei( r.toNumber() ) ); }) undefined 0

Перешлите на ваш аккаунт немного Ropsten-эфира для того, чтобы можно было заплатить за деплой. После того, как он дошел (можно проверять предыдущей командой), можно попробовать выполнить migrate:

truffle(ropsten)> migrate

Результат должен быть таким:

Compiling ./contracts/ConvertLib.sol... Compiling ./contracts/MetaCoin.sol... Compiling ./contracts/Migrations.sol... Writing artifacts to ./build/contracts  Using network 'ropsten'.  Running migration: 1_initial_migration.js   Deploying Migrations...   ... 0x93cf7dbde8c362534dc912926fc4d7df54c9c1f5e0a7dcfd964a0177b42bc7be   Migrations: 0x02519d13f61bdcad838d938611e6722c3d1f8034 Saving successful migration to network...   ... 0xec501a78cc11c723ab60186167765aa7c422177153cd72a976e66441db2b5b95 Saving artifacts... Running migration: 2_deploy_contracts.js   Deploying ConvertLib...   ... 0xf172d9a9ff9f1fdfdfabc816d89f5a5e710ba26e3a2ad9e1661c9dea56564f04   ConvertLib: 0xd04bffb73bf546985938a596565141d3a3bf7f0d   Linking ConvertLib to MetaCoin   Deploying MetaCoin...   ... 0x4d9814f1d9a959e83828bf26319dd91d73be977395d88e9e8239bb4c4ed5b0eb   MetaCoin: 0x20fd16643d857ce544a91ae4c80385af99dad196 Saving successful migration to network...   ... 0x0dff866460d24d56d94dcf5f833aa4fa8ae289cb708ff5c9012ce21447575ce8 Saving artifacts... truffle(ropsten)> 

Видим хеш транзакций, можно проверить, что они действительно попали на Ropsten через etherscan.io (например ropsten.etherscan.io/tx/0x93cf7dbde8c362534dc912926fc4d7df54c9c1f5e0a7dcfd964a0177b42bc7be)

Что дальше?

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

Погружение в разработку на Ethereum:
Часть 1: введение
Часть 2: Web3.js и газ
Часть 3: приложение для пользователя

FavoriteLoadingДобавить в избранное

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *