Статьи

Компиляция и интеллектуальные контракты: объяснение ABI

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

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

Виртуальная машина Ethereum (EVM)

Интеллектуальные контракты Ethereum — это набор инструкций по программированию, выполняемых на всех узлах, на которых работает полноценный клиент Ethereum. Часть Ethereum, которая выполняет умные контрактные инструкции, называется EVM. Это виртуальная машина, не похожая на Java JVM. EVM читает низкоуровневое представление умных контрактов, называемое байт-кодом Ethereum .

Байт-код Ethereum — это язык ассемблера, состоящий из нескольких кодов операций . Каждый код операции выполняет определенное действие в блокчейне Ethereum.

Вопрос в том, как мы пойдем из этого:

pragma solidity 0.4.24; contract Greeter { function greet() public constant returns (string) { return "Hello"; } } 

к этому:

 PUSH1 0x80 PUSH1 0x40 MSTORE PUSH1 0x4 CALLDATASIZE LT PUSH2 0x41 JUMPI PUSH1 0x0 CALLDATALOAD PUSH29 0x100000000000000000000000000000000000000000000000000000000 SWAP1 DIV PUSH4 0xFFFFFFFF AND DUP1 PUSH4 0xCFAE3217 EQ PUSH2 0x46 JUMPI JUMPDEST PUSH1 0x0 DUP1 REVERT JUMPDEST CALLVALUE DUP1 ISZERO PUSH2 0x52 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH2 0x5B PUSH2 0xD6 JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 DUP1 PUSH1 0x20 ADD DUP3 DUP2 SUB DUP3 MSTORE DUP4 DUP2 DUP2 MLOAD DUP2 MSTORE PUSH1 0x20 ADD SWAP2 POP DUP1 MLOAD SWAP1 PUSH1 0x20 ADD SWAP1 DUP1 DUP4 DUP4 PUSH1 0x0 JUMPDEST DUP4 DUP2 LT ISZERO PUSH2 0x9B JUMPI DUP1 DUP3 ADD MLOAD DUP2 DUP5 ADD MSTORE PUSH1 0x20 DUP2 ADD SWAP1 POP PUSH2 0x80 JUMP JUMPDEST POP POP POP POP SWAP1 POP SWAP1 DUP2 ADD SWAP1 PUSH1 0x1F AND DUP1 ISZERO PUSH2 0xC8 JUMPI DUP1 DUP3 SUB DUP1 MLOAD PUSH1 0x1 DUP4 PUSH1 0x20 SUB PUSH2 0x100 EXP SUB NOT AND DUP2 MSTORE PUSH1 0x20 ADD SWAP2 POP JUMPDEST POP SWAP3 POP POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 RETURN JUMPDEST PUSH1 0x60 PUSH1 0x40 DUP1 MLOAD SWAP1 DUP2 ADD PUSH1 0x40 MSTORE DUP1 PUSH1 0x5 DUP2 MSTORE PUSH1 0x20 ADD PUSH32 0x48656C6C6F000000000000000000000000000000000000000000000000000000 DUP2 MSTORE POP SWAP1 POP SWAP1 JUMP STOP LOG1 PUSH6 0x627A7A723058 KECCAK256 SLT 0xec 0xe 0xf5 0xf8 SLT 0xc7 0x2d STATICCALL ADDRESS SHR 0xdb COINBASE 0xb1 BALANCE 0xe8 0xf8 DUP14 0xda 0xad DUP13 LOG1 0x4c 0xb4 0x26 0xc2 DELEGATECALL PUSH7 0x8994D3E002900 

Солидность Компилятор

Сейчас мы сосредоточимся на компиляторе Solidity, но те же принципы применимы к Vyper или любому другому высокоуровневому языку для EVM.

Перво-наперво: установите Node.js.

После того, как вы это сделали, перейдите в свой терминал и запустите это:

 npm install -g solc 

Это установит solc — компилятор Solidity. Теперь создайте пустой каталог. В этом каталоге создайте файл с именем SimpleToken.sol и поместите следующий код:

 pragma solidity ^0.4.24; contract SimpleToken { mapping(address => uint) private _balances; constructor() public { _balances[msg.sender] = 1000000; } function getBalance(address account) public constant returns (uint) { return _balances[account]; } function transfer(address to, uint amount) public { require(_balances[msg.sender] >= amount); _balances[msg.sender] -= amount; _balances[to] += amount; } } 

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

  • публичные функции
  • частные функции
  • свойства

После того, как вы это сделаете, запустите только что установленный solc в вашем файле. Вы делаете это, выполнив следующее:

 solcjs SimpleToken.sol 

Вы должны получить вывод, похожий на этот:

 Invalid option selected, must specify either --bin or --abi 

И ваша компиляция должна провалиться.

Что сейчас произошло? Что такое bin и что такое abi ?

bin — это просто компактное двоичное представление скомпилированного байт-кода. На коды операций ссылаются не PUSH , PULL или DELEGATECALL , а их двоичные представления, которые выглядят как случайные числа при чтении в текстовом редакторе.

ABI — Бинарный интерфейс приложения

Как только наш вывод bin будет развернут в блокчейне, контракт получит свой адрес, а байт-код будет помещен в хранилище Ethereum. Но остается большая проблема: как мы интерпретируем код?

Только из байт-кода невозможно узнать, что в контракте есть функции transfer(:) и getBalance(:) . Еще менее ясно, являются ли эти функции public , private или constant . Контракт развернут без контекста .

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

ABI — это файл .json который описывает развернутый контракт и его функции. Это позволяет нам контекстуализировать договор и вызывать его функции.

Давайте попробуем запустить наши solcjs еще раз. Запустите следующие команды:

 solcjs SimpleToken.sol --abi solcjs SimpleToken.sol --bin 

Ваш каталог должен теперь иметь такую ​​структуру:

 . ├── SimpleToken.sol ├── SimpleToken_sol_SimpleToken.abi └── SimpleToken_sol_SimpleToken.bin 

Файл SimpleToken_sol_SimpleToken.abi должен выглядеть следующим образом:

 [{ "constant": false, "inputs": [{ "name": "to", "type": "address" }, { "name": "amount", "type": "uint256" }], "name": "transfer", "outputs": [], "payable": false, "stateMutability": "nonpayable", "type": "function" }, { "constant": true, "inputs": [{ "name": "account", "type": "address" }], "name": "getBalance", "outputs": [{ "name": "", "type": "uint256" }], "payable": false, "stateMutability": "view", "type": "function" }, { "inputs": [], "payable": false, "stateMutability": "nonpayable", "type": "constructor" }] 

Мы видим, что файл описывает функции контракта. Он определяет:

  • их имя: название функции
  • их платежеспособность: можете ли вы отправить им эфир
  • выходы: возвращаемое значение (я) функции
  • изменчивость их состояния: доступна ли функция только для чтения или имеет доступ для записи.

Все это достаточно легко понять, прочитав его. Но ранее я упоминал, что ABI также определяет, как пользователь может вызывать функции, то есть расположение функции по отношению к адресу смарт-контракта.

Знание названия функции недостаточно; нам также нужно знать, как (где) это назвать.

Это делается путем запуска детерминированного алгоритма для свойств функций, которые мы упоминали ранее (название, платежеспособность, выходные данные и т. Д.). Подробности этой функции можно найти здесь .

пример

ABI — это описание интерфейса контракта. Он не содержит кода и не может быть запущен сам по себе. Байт-код является исполняемым кодом EVM, но сам по себе он не имеет контекста.

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

Пример, использующий web3.js будет выглядеть так:

 var simpletokenContract = web3.eth.contract([{"constant":false,"inputs":[{"name":"to","type":"address"},{"name":"amount","type":"uint256"}],"name":"transfer","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"account","type":"address"}],"name":"getBalance","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"inputs":[],"payable":false,"stateMutability":"nonpayable","type":"constructor"}]); var simpletoken = simpletokenContract.new( { from: web3.eth.accounts[0], data: "", gas: '4700000' }, function (e, contract){ console.log(e, contract); if (typeof contract.address !== 'undefined') { console.log('Contract mined! address: ' + contract.address + ' transactionHash: ' + contract.transactionHash); } }) К var simpletokenContract = web3.eth.contract([{"constant":false,"inputs":[{"name":"to","type":"address"},{"name":"amount","type":"uint256"}],"name":"transfer","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"account","type":"address"}],"name":"getBalance","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"inputs":[],"payable":false,"stateMutability":"nonpayable","type":"constructor"}]); var simpletoken = simpletokenContract.new( { from: web3.eth.accounts[0], data: "", gas: '4700000' }, function (e, contract){ console.log(e, contract); if (typeof contract.address !== 'undefined') { console.log('Contract mined! address: ' + contract.address + ' transactionHash: ' + contract.transactionHash); } }) К var simpletokenContract = web3.eth.contract([{"constant":false,"inputs":[{"name":"to","type":"address"},{"name":"amount","type":"uint256"}],"name":"transfer","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"account","type":"address"}],"name":"getBalance","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"inputs":[],"payable":false,"stateMutability":"nonpayable","type":"constructor"}]); var simpletoken = simpletokenContract.new( { from: web3.eth.accounts[0], data: "", gas: '4700000' }, function (e, contract){ console.log(e, contract); if (typeof contract.address !== 'undefined') { console.log('Contract mined! address: ' + contract.address + ' transactionHash: ' + contract.transactionHash); } }) 

Сначала мы определили simpelTokenContract , который представляет собой описание внешнего вида контракта. Мы сделали это, передав ему ABI SimpleToken.sol .

Затем мы создали instance договора simpletoken , вызвав simpletokenContract.new(...) и передав в него данные договора (исполняемый код).

web3.js объединил их в фоновом режиме и теперь имеет всю необходимую информацию для вызова функций нашего умного контракта.

Вывод

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