Create smart contract on TRON / 在 TRON (波場) 部署智能合約
本篇主要分享在 TRON
(波場) 部署智能合約 (Smart Contract) 並與之交互的經驗分享。
為讓初學者快速瞭解與跟著操作,所有的環境都在本機 (自建私鏈) 完成,操作完成後,也可以方便快速地刪除,回到本機乾淨的環境。
1. 快速搭建 TRON 私鏈
本機需要先行建置好 Docker
環境,相關安裝教學請上網查詢。
安裝完成後,按下列步驟,即可快速啟動 TRON
私鏈在本機上。容器名稱將設定為 tron,你可以按需求在下列指令中更換。若自行更換,之後所有的操作請自行對應修改,不會再多述。
$ docker pull trontools/quickstart
$ docker run -it -p 9090:9090 --name tron trontools/quickstart
or
$ docker run -it -p 9090:9090 --name tron -e "preapprove=multiSignFee:1,allowMultiSign:1" trontools/quickstart
需要注意的是,預設會自動建立 10 個錢包,並持有 10000 TRX 可供使用,這 10 個錢包會在執行完成時,顯示在畫面上,如圖:
之後若忘了這些錢包地址也沒關係,只要知道此容器的 IP 地址。我們隨時都可以在本機依下列指令再次顯示,範例的 IP 是 172.17.0.2。
$ curl "http://172.17.0.2:9090/admin/accounts?format=all"
於是 TRON
私鏈就成功在本機啟動。
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
18006b7cff41 trontools/quickstart "./quickstart v2.1.1" 9 hours ago Up 9 hours 0.0.0.0:9090->9090/tcp tron
我們也可以查詢容器內 TRON 的 Log。
$ docker exec -it tron tail -f /tron/FullNode/logs/tron.log
2. 快速搭建開發 TRON 智能合約的環境
為了把重點放在如何與智能合約交互,本篇僅會以簡單的兩個函式為範例 (set, get) 交互,而非比較複雜的 Token。其中的差異僅在於,之後只要在網路上或自行編寫 Token 合約,按此篇的操作亦可完成。
後續所有開發皆採用與本機環境隔開的 Ubuntu
容器。
$ docker pull ubuntu:18.04
$ docker run -d -it --name ubuntu ubuntu:18.04
$ docker exec -it ubuntu bash
智能合約 (Smart Contract) 的開發方式有很多種,這裡將介紹兩種。因為都需要 node
程式語言,所以請按下列操作安裝,或使用自有習慣的方式。
注意,接下來的操作步驟是在上述 Ubuntu 的容器內。之後若見到以 “docker$” 起頭為標示的指令操作,即代表為在 Ubuntu 容器內之操作。
docker$ apt update
docker$ apt install nodejs npm git-core vim-tiny
docker$ node -v
v8.10.0
docker$ npm -v
3.5.2
docker$ git --version
git version 2.17.1
Tronbox
Tronbox 是 TRON 官方提出的開發框架。以下為安裝步驟。
docker$ npm install -g tronbox
接著初始化智能合約。
docker$ mkdir -p $HOME/tron-contract-tronbox
docker$ cd $HOME/tron-contract-tronbox
docker$ tronbox init
3. 開始撰寫 TRON 智能合約
之後可能因為版本更新的因素,請各位再自行調整。
我們需要建立、更新下述兩個指定的檔案。
docker$ cd $HOME/tron-contract-tronbox
docker$ cat contracts/MyContract.sol
pragma solidity >=0.4.23 <0.6.0;
contract MyContract {
string message = "hello";
function postMessage(string memory value) public returns (string memory) {
message = value;
return message;
}
function getMessage() public view returns (string memory) {
return message;
}
}
docker$ cd $HOME/tron-contract-tronbox
docker$ cat migrations/2_deploy_contracts.js
var MyContract = artifacts.require("./MyContract.sol");
var Migrations = artifacts.require("./Migrations.sol");
module.exports = function(deployer) {
deployer.deploy(MyContract);
deployer.deploy(Migrations);
};
接著將 tronbox.js 檔案中 development 區段的 fullHost,指到我們的私鏈位置。
至於如何得知之前運行的 TRON 私鏈的 IP 位址,可以再自行上網查詢。範例是 172.17.0.2。
docker$ cat tronbox.js
...
development: {
// For trontools/quickstart docker image
privateKey: 'da146374a75310b9666e834ee4ad0866d6f4035967bfc76217c5a495fff9f0d0',
userFeePercentage: 0,
feeLimit: 1e8,
fullHost: 'http://172.17.0.2:' + port,
network_id: '9'
},
...
另外,Solidity 的 version 也需要指定。
docker$ cat tronbox.js
...
compilers: {
solc: {
version: '0.5.4'
}
}
...
開始編譯智能合約。
docker$ cd $HOME/tron-contract-tronbox
docker$ tronbox compile --compile-all
docker$ tronbox migrate --reset
成功時,會輸出智能合約的地址,請務必記得其中我們編寫的 MyContract 地址。
4. 試著與 TRON 智能合約交互前,我們需要知其 ABI
在撰寫智能合約交互之前,我們需要取得先前部署在 TRON 私鏈上該智能合約的 ABI。
方法有兩種。
Method 1 : Solidity Compiler (solc)
安裝步驟。
docker$ npm install -g solc
接著初始化智能合約目錄結構。
docker$ mkdir -p $HOME/tron-contract-solc
docker$ mkdir -p $HOME/tron-contract-solc/build
把先前智能合約的程式碼再寫一次。
docker$ cd $HOME/tron-contract-solc
docker$ cat MyContract.sol
pragma solidity >=0.4.23 <0.6.0;
contract MyContract {
string message = "hello";
function postMessage(string memory value) public returns (string memory) {
message = value;
return message;
}
function getMessage() public view returns (string memory) {
return message;
}
}
編譯及部署智能合約。
docker$ solcjs MyContract.sol -o build --bin --abi --opcodes
取得編譯出來的 ABI。
docker$ cat build/MyContract_sol_MyContract.abi
[{"inputs":[],"name":"getMessage","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"value","type":"string"}],"name":"postMessage","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"nonpayable","type":"function"}]
Method 2 : TronWeb
利用 TronWeb 從私鏈上獲取智能合約的 ABI。
$ docker exec -it tron ./tronWeb
然後將之前智能合約的地址,取代下述指令中的 OOOOO。
> tronWeb.contract().at("OOOOO").then(function(v){console.log(JSON.stringify(v.abi));});
> .exit
例如我先前圖中的範例,MyContract 智能合約的 base58 地址是 “TARcTNcPPw5yrKCYZZryfdwuTGZoSEcAno”。
> tronWeb.contract().at("TARcTNcPPw5yrKCYZZryfdwuTGZoSEcAno").then(function(v){console.log(JSON.stringify(v.abi));});
[{"outputs":[{"type":"string"}],"inputs":[{"name":"value","type":"string"}],"name":"postMessage","stateMutability":"Nonpayable","type":"Function"},{"outputs":[{"type":"string"}],"constant":true,"name":"getMessage","stateMutability":"View","type":"Function"}]
> .exit
此時輸出的就是 MyContract 的 ABI。
5. 試著與 TRON 智能合約交互
這裡採用的函式庫是 TRON-API,是非官方社群維護的 PHP 函式庫。目前最後的修改日是 2019-11-15,已歷時半年以上未更新,參照文件或範例運作時,會有幾處需要調整,而我已經踩過幾個坑,只要按照我的操作即可。
首先,安裝 PHP 及 Composer。
docker$ apt install php php-gmp php-bcmath composer
初始化智能合約目錄結構。
docker$ mkdir -p $HOME/tron-api-php
docker$ cd $HOME/tron-api-php
docker$ composer require iexbase/tron-api
在正式與智能合約交互之前,我們需要以下資料。
- MyContract 智能合約的地址。
- MyContract 智能合約的 ABI。
- TRON (私)鏈的服務器地址。
- TRON (私)鏈的指定錢包地址。
- TRON (私)鏈的指定錢包私鑰。
我們在前述的操作中已知 1, 2, 3,而 4 及 5,可利用先前提到的指令來從我們自建的 TRON 私鏈取得。
$ curl "http://172.17.0.2:9090/admin/accounts?format=all"
我們將使用其中的第一個錢包。即,
address = 414ee854e425347aa6c4a319330e17d2b507cdce6c
privacy_key = c72092f1a156efbc46e48683bef4d64c1005f97d668248e3e7a1411237bb82c1
於是,把上述五項資料,填寫於設定檔中。
docker$ cd $HOME/tron-api-php
docker$ cat config.php
<?php
$tron_host = 'http://172.17.0.2:9090';
$sun_limit = 1000000000;
$main_address = '414ee854e425347aa6c4a319330e17d2b507cdce6c';
$main_privacy_key = 'c72092f1a156efbc46e48683bef4d64c1005f97d668248e3e7a1411237bb82c1';
$contract_hex_address = '4104fe64a68fc938555092d88d441e8b883f684004';
$contract_abi = '[{"outputs":[{"type":"string"}],"inputs":[{"name":"value","type":"string"}],"name":"postMessage","stateMutability":"Nonpayable","type":"Function"},{"outputs":[{"type":"string"}],"constant":true,"name":"getMessage","stateMutability":"View","type":"Function"}]';
$tron_host = 'http://172.17.0.2:9090';
$sun_limit = 1000000000;
注意,contract_hex_address 必須使用智能合約的 hex 地址而不是 base58。如下圖,
另外,本文前述提供兩種取得智能合約 ABI 的方法,若是使用 Method 1 : Solidity Compiler (solc) 方法取得的,通常沒有問題,只需直接複製到 $contract_abi 變數後即可,如:
docker$ cd $HOME/tron-api-php
docker$ cat config.php
<?php
$tron_host = 'http://172.17.0.2:9090';
$sun_limit = 1000000000;
$main_address = '414ee854e425347aa6c4a319330e17d2b507cdce6c';
$main_privacy_key = 'c72092f1a156efbc46e48683bef4d64c1005f97d668248e3e7a1411237bb82c1';
$contract_hex_address = '4104fe64a68fc938555092d88d441e8b883f684004';
$contract_abi = '[{"inputs":[],"name":"getMessage","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"value","type":"string"}],"name":"postMessage","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"nonpayable","type":"function"}]';
但若是使用 Method 2 : TronWeb 方法取得時,在我們使用 TRON-API 時,會需要調整一下。
例如在本例中,TronWeb 方法會得到,
[{"outputs":[{"type":"string"}],"inputs":[{"name":"value","type":"string"}],"name":"postMessage","stateMutability":"Nonpayable","type":"Function"},{"outputs":[{"type":"string"}],"constant":true,"name":"getMessage","stateMutability":"View","type":"Function"}]
之後執行,會遇到如下述的錯誤。
PHP Notice: Undefined index: inputs in /root/tron-api-php/vendor/iexbase/tron-api/src/TransactionBuilder.php on line 432
PHP Warning: count(): Parameter must be an array or an object that implements Countable in /root/tron-api-php/vendor/iexbase/tron-api/src/TransactionBuilder.php on line 432
PHP Notice: Undefined index: inputs in /root/tron-api-php/vendor/iexbase/tron-api/src/TransactionBuilder.php on line 439
PHP Warning: array_map(): Argument #2 should be an array in /root/tron-api-php/vendor/iexbase/tron-api/src/TransactionBuilder.php on line 439
PHP Warning: count(): Parameter must be an array or an object that implements Countable in /root/tron-api-php/vendor/iexbase/tron-api/src/TransactionBuilder.php on line 441
PHP Fatal error: Uncaught InvalidArgumentException: encodeParameters number of types must equal to number of params. in /root/tron-api-php/vendor/sc0vu/web3.php/src/Contracts/Ethabi.php:154
Stack trace:
#0 /root/tron-api-php/vendor/iexbase/tron-api/src/TransactionBuilder.php(454): Web3\Contracts\Ethabi->encodeParameters(Array, Array)
#1 /root/tron-api-php/demo.php(31): IEXBase\TronAPI\TransactionBuilder->triggerSmartContract(Array, '4104fe64a68fc93...', 'getMessage', Array, 1000000000, '414ee854e425347...')
#2 {main}
thrown in /root/tron-api-php/vendor/sc0vu/web3.php/src/Contracts/Ethabi.php on line 154
我們要要調整後才可使用,例如此例是需要把 ““inputs”:[]“,加到 “getMessage” 中。
由原本 TronWeb 取得的,
[{"outputs":[{"type":"string"}],"inputs":[{"name":"value","type":"string"}],"name":"postMessage","stateMutability":"Nonpayable","type":"Function"},{"outputs":[{"type":"string"}],"constant":true,"name":"getMessage","stateMutability":"View","type":"Function"}]
調整為,
[{"outputs":[{"type":"string"}],"inputs":[{"name":"value","type":"string"}],"name":"postMessage","stateMutability":"Nonpayable","type":"Function"},{"outputs":[{"type":"string"}],"inputs":[],"constant":true,"name":"getMessage","stateMutability":"View","type":"Function"}]
最後得到的 config.php
為,
docker$ cat config.php
<?php
$tron_host = 'http://172.17.0.2:9090';
$sun_limit = 1000000000;
$main_address = '414ee854e425347aa6c4a319330e17d2b507cdce6c';
$main_privacy_key = 'c72092f1a156efbc46e48683bef4d64c1005f97d668248e3e7a1411237bb82c1';
$contract_hex_address = '4104fe64a68fc938555092d88d441e8b883f684004';
# Get from Solidity Compiler (solc)
$contract_abi = '[{"inputs":[],"name":"getMessage","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"value","type":"string"}],"name":"postMessage","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"nonpayable","type":"function"}]';
# Modified from TronWeb
// $contract_abi = '[{"outputs":[{"type":"string"}],"inputs":[{"name":"value","type":"string"}],"name":"postMessage","stateMutability":"Nonpayable","type":"Function"},{"outputs":[{"type":"string"}],"inputs":[],"constant":true,"name":"getMessage","stateMutability":"View","type":"Function"}]';
然後是主要交互的 PHP 程式碼。
與 TRON-API 官方提供的文件或範例之差異是,現在發送給 TRON Web API 的請求,必須包含 ‘Content-Type: application/json’ 的 Header,否則照官方網站的文件與範本是無法正常運行的。
docker$ cd $HOME/tron-api-php
docker$ cat demo.php
<?php
require __DIR__ . '/vendor/autoload.php';
require __DIR__ . '/config.php';
use IEXBase\TronAPI\Tron;
$fullNode = new \IEXBase\TronAPI\Provider\HttpProvider($tron_host, 30000, false, false, ['Content-Type' => 'application/json']);
$solidityNode = new \IEXBase\TronAPI\Provider\HttpProvider($tron_host, 30000, false, false, ['Content-Type' => 'application/json']);
$eventServer = new \IEXBase\TronAPI\Provider\HttpProvider($tron_host, 30000, false, false, ['Content-Type' => 'application/json']);
try {
$tron = new \IEXBase\TronAPI\Tron($fullNode, $solidityNode, $eventServer);
} catch (\IEXBase\TronAPI\Exception\TronException $e) {
exit($e->getMessage());
}
$tron->setAddress($main_address);
$balance = $tron->getBalance(null, true);
$tron->setPrivateKey($main_privacy_key);
var_dump($balance);
$transaction_builder = $tron->getTransactionBuilder();
$abi = json_decode($contract_abi, true);
$response = $transaction_builder->triggerSmartContract($abi, $contract_hex_address, "getMessage", [], $sun_limit, $main_address);
var_dump($response);
$transaction = $transaction_builder->triggerSmartContract($abi, $contract_hex_address, "postMessage", ["ant_is_awesome"], $sun_limit, $main_address);
$signedTransaction = $tron->signTransaction($transaction);
$response = $tron->sendRawTransaction($signedTransaction);
var_dump($response);
$response = $transaction_builder->triggerSmartContract($abi, $contract_hex_address, "getMessage", [], $sun_limit, $main_address);
var_dump($response);
最後執行時,就會取得我們要的結果。
docker$ php demo.php
float(9918.07856)
array(1) {
[0]=>
string(5) "hello"
}
array(1) {
["result"]=>
bool(true)
}
array(1) {
[0]=>
string(14) "ant_is_awesome"
}
另外,若需要取得某次交易的狀況,可以取代下述指令中的 OOOOO,換為 TRON (私)鏈上的 TxID。
// parameter , TxID (transaction id)
$response = $tron->getTransaction('OOOOO');
var_dump($response);
例如我想要取得 TxID 為 “b73e42ff5da9f9083fcff340678a1a1ce09533b56a6e795b5cdc1cfcf98e1356” 的紀錄時,
// parameter , TxID (transaction id)
$response = $tron->getTransaction('b73e42ff5da9f9083fcff340678a1a1ce09533b56a6e795b5cdc1cfcf98e1356');
var_dump($response);
6. 清理、銷毀 TRON (波場) 開發環境
因為開發都是採用容器化的方式,所以僅需要下列步驟即可恢復回原本乾淨的本機環境。
$ docker stop ubuntu
$ docker rm ubuntu
$ docker rmi ubuntu:18.04
$ docker stop tron
$ docker rm tron
$ docker rmi trontools/quickstart