Статьи

Отладка с помощью Truffle CLI

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

Современный отладчик позволяет нам:

  • запустить код построчно
  • установить точки останова в коде
  • ставить условия на контрольные точки
  • оценивать выражения во время выполнения.

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

Так в каком состоянии находятся Солидные умные контракты для отладки и отладки?

Отладчик солидности

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

В этой статье мы будем исследовать отладчик Solidity, входящий в комплект Truffle Suite .

Начиная

Сначала нам нужно установить все необходимые инструменты. К счастью для нас, среда Truffle очень хорошо интегрирована, поэтому нам просто нужно ее установить.

Сначала установите Node.js и NPM . После того, как вы установили Node, вы можете убедиться, что он установлен, проверив версию инструмента следующим образом:

➜  ~ node -v
v10.2.1
➜  ~ npm -v
5.6.0

Если ваш Node запущен и работает, давайте установим инфраструктуру Truffle. Это делается достаточно просто с помощью npm

 npm install -g truffle

Вы можете проверить, была ли установка успешной, проверив версию:

 truffle version
Truffle v4.1.11 (core: 4.1.11)
Solidity v0.4.24 (solc-js)

Настройка проекта

Теперь, когда вы настроили Truffle, давайте создадим новый (пустой) проект Truffle. Откройте свой терминал, поместите себя в желаемый каталог и запустите truffle init Вывод должен быть похож на это:

 truffle init
Downloading...
Unpacking...
Setting up...
Unbox successful. Sweet!

Commands:

  Compile:        truffle compile
  Migrate:        truffle migrate
  Test contracts: truffle test

После того, как вы это сделаете, у вас должна быть структура контракта, подобная этой:

 .
├── contracts
│   └── Migrations.sol
├── migrations
│   └── 1_initial_migration.js
├── test
├── truffle-config.js
└── truffle.js

Теперь откройте файл truffle.js

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

Сохраните файл и запустите truffle develop Вы должны получить вывод, похожий на этот:

 truffle develop
Truffle Develop started at http://127.0.0.1:9545/

Accounts:
(0) 0x627306090abab3a6e1400e9345bc60c78a8bef57
(1) 0xf17f52151ebef6c7334fad080c5704d77216b732
(2) 0xc5fdf4076b8f3a5357c5e395ab970b5b54098fef
(3) 0x821aea9a577a9b44299b9c15c88cf3087f3b5544
(4) 0x0d1d4e623d10f9fba5db95830f7d3839406c6af2
(5) 0x2932b7a2355d6fecc4b5c0b6bd44cc31df247a2e
(6) 0x2191ef87e392377ec08e7c08eb105ef5448eced5
(7) 0x0f4f2ac550a1b4e2280d04c21cea7ebd822934b5
(8) 0x6330a553fc93768f612722bb8c2ec78ac90b3bbc
(9) 0x5aeda56215b167893e80b4fe645ba6d5bab767de

Private Keys:
(0) c87509a1c067bbde78beb793e6fa76530b6382a4c0241e5e4a9ec0a0f44dc0d3
(1) ae6ae8e5ccbfb04590405997ee2d52d2b330726137b875053c36d94e974d162f
(2) 0dbbe8e4ae425a6d2687f1a7e3ba17bc98c673636790f1b8ad91193c05875ef1
(3) c88b703fb08cbea894b6aeff5a544fb92e78a18e19814cd85da83b71f772aa6c
(4) 388c684f0ba1ef5017716adb5d21a053ea8e90277d0868337519f97bede61418
(5) 659cbb0e2411a44db63778987b1e22153c086a95eb6b18bdf89de078917abc63
(6) 82d052c865f5763aad42add438569276c00d3d88a2d062d36b2bae914d58b8c8
(7) aa3680d5d48a8283413f7a108367c7299ca73f553735860a87b08f39395618b7
(8) 0f62d96d6675f32685bbdb8ac13cda7c23436f63efbb9d07700d8669ff12b7c4
(9) 8d5366123cb560bb606379f90a0bfd4769eecc0557f1b362dcae9012b548b1e5

Mnemonic: candy maple cake sugar pudding cream honey rich smooth crumble sweet treat

⚠️  Important ⚠️  : This mnemonic was created for you by Truffle. It is not secure.
Ensure you do not use it on production blockchains, or else you risk losing funds.

Это начало экземпляра блокчейна разработки Truffle при поддержке ganache-cliTestRPC

Написание и развертывание контракта

В каталоге контрактов создайте файл с именем Storage.sol В этот файл поместите следующий код:

 pragma solidity ^0.4.23;


contract Storage {

    uint[] private _numberStorage;

    event AddedNewNumber(uint position);

    function addNumber(uint newNumber) public returns (uint) {
        _numberStorage.push(newNumber);

        uint numberPosition = _numberStorage.length;

        emit AddedNewNumber(numberPosition);
        return numberPosition;
    }

    function getNumber(uint position) public constant returns (uint) {
        return _numberStorage[position];
    }

}

После того, как вы закончите с этим, ваша файловая структура должна выглядеть следующим образом:

 ├── contracts
│   ├── Migrations.sol
│   └── Storage.sol
├── migrations
│   └── 1_initial_migration.js
├── test
├── truffle-config.js
└── truffle.js

В каталоге migrations2_deploy_migrations.js

 var Storage = artifacts.require("./Storage.sol");

module.exports = function(deployer) {

    deployer.deploy(Storage);

}

Этот код определяет, как Truffle перенесет наш проект в блокчейн.

Теперь откройте новую вкладку в терминале (оставив запуск truffle developtruffle migrate Это скомпилирует и перенесет ваши контракты в блокчейн разработки. Вы должны получить такой вывод:

 Using network 'development'.

Running migration: 1_initial_migration.js
  Deploying Migrations...
  ... 0x819678a9812313714a27b52c30f065544a331ec5c79ec6c251bc97cd09398d08
  Migrations: 0x8cdaf0cd259887258bc13a92c0a6da92698644c0
Saving successful migration to network...
  ... 0xd7bc86d31bee32fa3988f1c1eabce403a1b5d570340a3a9cdba53a472ee8c956
Saving artifacts...

Теперь напишите truffle console Это откроет вам интерактивную консоль для проверки ваших контрактов. В консоли сделайте следующее:

 ~> Storage.deployed().then((i) => { iStorage = i }) // Store the contract instance into the iStorage variable

~> iStorage.AddedNewNumber({}).watch((err, res) => { console.log("NUMBER POSITION: " + res.args.position.toNumber()) }) // Subscribe to the added new number event

Filter {
  requestManager:
   RequestManager {
     provider: Provider { provider: [HttpProvider] },
     polls: {},
     timeout: null },
  options:
   { topics:
      [ '0x197006a61de03a2f3b4de7f4c4fab6e30ebedef7c1a42d716b2140f184c718b7' ],
     from: undefined,
     to: undefined,
     address: '0xdda6327139485221633a1fcd65f4ac932e60a2e1',
     fromBlock: undefined,
     toBlock: undefined },
  implementation:
   { newFilter:
      { [Function: send] request: [Function: bound ], call: [Function: newFilterCall] },
     uninstallFilter:
      { [Function: send] request: [Function: bound ], call: 'eth_uninstallFilter' },
     getLogs:
      { [Function: send] request: [Function: bound ], call: 'eth_getFilterLogs' },
     poll:
      { [Function: send] request: [Function: bound ], call: 'eth_getFilterChanges' } },
  filterId: null,
  callbacks: [ [Function] ],
  getLogsCallbacks: [],
  pollFilters: [],
  formatter: [Function: bound ] }

~> iStorage.addNumber(13) // Add a new number

{ tx:
   '0xad3f82a6a6cec39dff802f2f16e73bbbc8eff3b68c2ac4da4c371a4c84345a4f',
  receipt:
   { transactionHash:
      '0xad3f82a6a6cec39dff802f2f16e73bbbc8eff3b68c2ac4da4c371a4c84345a4f',
     transactionIndex: 0,
     blockHash:
      '0x464bc0075036cf95484dec165f0248fb0a7db929d14068a312076be14d43d1fe',
     blockNumber: 5,
     gasUsed: 63362,
     cumulativeGasUsed: 63362,
     contractAddress: null,
     logs: [ [Object] ],
     status: '0x01',
     logsBloom:
      '0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000080000000004000000000000000000000000000000000000000000000000000000000000000000010000000000000' },
  logs:
   [ { logIndex: 0,
       transactionIndex: 0,
       transactionHash:
        '0xad3f82a6a6cec39dff802f2f16e73bbbc8eff3b68c2ac4da4c371a4c84345a4f',
       blockHash:
        '0x464bc0075036cf95484dec165f0248fb0a7db929d14068a312076be14d43d1fe',
       blockNumber: 5,
       address: '0x345ca3e014aaf5dca488057592ee47305d9b3e10',
       type: 'mined',
       event: 'AddedNewNumber',
       args: [Object] } ] }

После запуска функции iStorage.addNumber(...) Если это так, выходные данные должны содержать что-то вроде этого:

 truffle(development)> NUMBER POSITION: 1

Теперь давайте попробуем извлечь число из той позиции, в которой мы его сохранили:

 ~> iStorage.getNumber(1)
iStorage.getNumber(1)
Error: VM Exception while processing transaction: invalid opcode
    at XMLHttpRequest._onHttpResponseEnd (/usr/local/lib/node_modules/truffle/build/webpack:/~/xhr2/lib/xhr2.js:509:1)
    at XMLHttpRequest._setReadyState (/usr/local/lib/node_modules/truffle/build/webpack:/~/xhr2/lib/xhr2.js:354:1)
    at XMLHttpRequestEventTarget.dispatchEvent (/usr/local/lib/node_modules/truffle/build/webpack:/~/xhr2/lib/xhr2.js:64:1)
    at XMLHttpRequest.request.onreadystatechange (/usr/local/lib/node_modules/truffle/build/webpack:/~/web3/lib/web3/httpprovider.js:128:1)
    at /usr/local/lib/node_modules/truffle/build/webpack:/~/truffle-provider/wrapper.js:134:1
    at /usr/local/lib/node_modules/truffle/build/webpack:/~/web3/lib/web3/requestmanager.js:86:1
    at Object.InvalidResponse (/usr/local/lib/node_modules/truffle/build/webpack:/~/web3/lib/web3/errors.js:38:1)

Мы получаем ошибку! Как удобно: кажется, теперь мы можем использовать отладчик. Это почти как если бы это было запланировано!

Использование отладчика

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

 truffle debug tx_hash

Мы будем отлаживать функцию, которая добавляет номер в хранилище. Хеш tx будет другим для вас, но общая форма будет похожа на это. И вывод должен быть таким же:

 truffle debug 0x34d26b8bcf01f23dfbef0de28384623ca3ed3e3e7fe28f1a0968d363cf38765f
Compiling ./contracts/Migrations.sol...
Compiling ./contracts/Storage.sol...

Gathering transaction data...

Addresses affected:
 0x345ca3e014aaf5dca488057592ee47305d9b3e10 - Storage

Commands:
(enter) last command entered (step next)
(o) step over, (i) step into, (u) step out, (n) step next
(;) step instruction, (p) print instruction, (h) print this help, (q) quit
(b) toggle breakpoint, (c) continue until breakpoint
(+) add watch expression (`+:<expr>`), (-) remove watch expression (-:<expr>)
(?) list existing watch expressions
(v) print variables and values, (:) evaluate expression - see `v`


Storage.sol:

2:
3:
4: contract Storage {
   ^^^^^^^^^^^^^^^^^^

debug(development:0x34d26b8b...)>

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

В консоли выполните следующие серии команд:

 debug(development:0x34d26b8b...)> o

Storage.sol:

 8:     event AddedNewNumber(uint position);
 9:
10:     function addNumber(uint newNumber) public returns (uint) {
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Набрав oперешагнули строку функции — это означает, что мы не вошли в тело функции, которую мы вызвали.

Теперь мы находимся в строке 10 — или в заголовке функции для добавления числа. Пойдем дальше. Введите o

 Storage.sol:

 9:
10:     function addNumber(uint newNumber) public returns (uint) {
11:         _numberStorage.push(newNumber);
            ^^^^^^^^^^^^^^

Теперь мы помещаем число в массив. Введите o

 Storage.sol:

11:         _numberStorage.push(newNumber);
12:
13:         uint numberPosition = _numberStorage.length;

Мы выдвинули число и получаем позицию числа в массиве. Нажмите o

 Storage.sol:

13:         uint numberPosition = _numberStorage.length;
14:
15:         emit AddedNewNumber(numberPosition);
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Мы на линии, излучающей позицию. Теперь вместо нажатия o Мы можем использовать один из двух способов. Первый, более общий — просто наберите v

Сделайте это, набрав v Вывод должен быть таким:

 numberPosition: 1
     newNumber: 12
              : 189
_numberStorage: [ 12 ]

Другой способ сделать это более конкретный, и мы можем сделать это, набрав :<variable_name> Сделайте это, набрав :numberPosition Вы должны получить 1

Теперь давайте попробуем оценить выражение. Вы можете сделать это также с помощью конструкции :

Давайте попробуем вернуть наш номер с помощью :_numberStorage[numberPosition]

 debug(development:0x34d26b8b...)> :_numberStorage[numberPosition]
undefined

Запустив его, мы получим вывод undefined Это означает, что там нет номера.

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

Нажмите q В вашем Storage.sol

 uint numberPosition = _numberStorage.length;

к этому:

 uint numberPosition = _numberStorage.length - 1;

Теперь снова запустите truffle migratetruffle console В консоли снова запустите команды:

 ~> Storage.deployed().then((i) => { iStorage = i})
~> iStorage.AddedNewNumber({}).watch((err, res) => { console.log("NUMBER POSITION: " + res.args.position.toNumber()) });
~> iStorage.addNumber(12);
~> output: "NUMBER POSITION: 0"
~> iStorage.getNumber(0)
~> iStorage.getNumber(0).then((res) => { console.log(res.toNumber()) } )
~> output: 12

Вы успешно развернули и отладили умный контракт!