Smart Contract Quick Start
DApp開発の開始
IOSTのDAppの基礎
ブロックチェーンは、ネットワーク内で同期するステートマシンとして抽象化されています。スマートコントラクトは、ブロックチェーンシステム上で、実行されるコードで、ステートマシンのステート(状態)をトランザクションを通じて変化させます。ブロックチェーンの特徴により、スマートコントラクトの呼び出しは、連続かつグローバルな一貫性を保証します。
IOSTのスマートコントラクトは、現在はJavaScript (ES6) での開発をサポートしています。
IOSTのスマートコントラクトはスマートコントラクトのコードとABIを記述したJSONファイルを含みます。それは名前空間と独立したストレージを持ちます。外部からは、ストレージの内容の読み出しのみが可能です。
キーワード
キーワード | 説明 |
---|---|
ABI | スマートコントラクトのインターフェース。ここで宣言したインターフェースを通してのみ外部から呼び出すことが可能 |
Tx | トランザクション。トランザクションをサブミットすることでブロックチェーンの状態を変。トランザクションはブロック内にパッケージ化されている |
デバッグ環境設定
iwalletのセットアップとローカルテストノード
スマートコントラクトの開発には、iwalletが必要です。同時に、テストノードを開始することで、デバッグを促進できます。
admin
のインポート
iwalletの初期アカウントテストをするには、秘密鍵をiwalletにインポートする必要があります。ローカルテストノードの'admin'アカウントインポートできます。
iwallet account import admin 2yquS3ySrGWPEKywCPzX4RTJugqRh7kJSo5aehsLYPEWkUxBWA39oMrZ7ZxuM4fgyXYs2cPwh5n8aNNpH5x2VyK1
Hello world
コードの準備
最初にJavaScriptクラスを用意します。
// helloWorld.js
class HelloWorld {
init() {} // needs to provide an init function that will be called during deployment
hello(someone) {
return "hello, "+ someone
}
}
module.exports = HelloWorld;
このスマートコントラクトには入力を受け取って、hello, + 入力した値
を出力するインターフェースがあります。このインターフェースで、スマートコントラクトの外部から呼び出すことができるように、ABIファイルを用意します。
// helloWorld.abi
{
"lang": "javascript",
"version": "1.0.0",
"abi": [
{
"name": "hello",
"args": [
"string"
]
}
]
}
ABIのnameフィールドは、jsファイルの関数名と関連付けられ、argsが事前の型チェック情報になります。3つの型、string、number、boolだけを使うことを推奨します。
ローカルテストノードへのパブリッシュ
スマートコントラクトをパブリッシュするには次のようにします。
iwallet \
--expiration 10000 --gas_limit 1000000 --gas_ratio 1 \
--server localhost:30002 \
--account admin \
--amount_limit '*:unlimited' \
publish helloworld.js helloworld.abi
実行例
Sending transaction...
Transaction has been sent.
The transaction hash is: 2xC6ziTqXaat7dsrya9pHog6NEEAMgBMKWcMv5YNDEpa
Checking transaction receipt...
SUCCESS!
The contract id is: Contract2xC6ziTqXaat7dsrya9pHog6NEEAMgBMKWcMv5YNDEpa
Test ABI call
iwallet \
--expiration 10000 --gas_limit 1000000 --gas_ratio 1 \
--server localhost:30002 \
--account admin \
--amount_limit '*:unlimited' \
call "Contract96YFqvomoAnX6Zyj993fkv29D2HVfm8cjGhCEM1ymXGf" "hello" '["developer"]' # contract id needs to be changed to the id you received
出力
Sending transaction...
Transaction has been sent.
The transaction hash is: CzQi1ro44E6ysVq6o6c6UEqYNrPbN7HruAjewoGfRTBy
Checking transaction receipt...
SUCCESS!
その後、いつでも次のコマンドでTxReceipt(レシート)を取得できます。
iwallet receipt GTUmtpWPdPMVvJdsVf8AiEPy9EzCBUwUCim9gqKjvFLc
HTTPを通して取得することもできます。
curl -X GET \
http://localhost:30001/getTxReceiptByTxHash/GTUmtpWPdPMVvJdsVf8AiEPy9EzCBUwUCim9gqKjvFLc
呼び出しは、IOSTによって永久に記録され、手を入れることはできません。
スマートコントラクトのステートストレージ
スマートコントラクトの出力を(UXTOのコンセプトと同様の)使うのは不便です。IOSTはこのモードは使いません。IOSTはtxReceipt内のそれぞれのフィールドのインデックスを提供しませんし、スマートコントラクトは特定のTxReceiptにアクセスすることもできません。ブロックチェーンのステートマシンを維持するために、ステートを保持するブロックチェーンデータベースを使います。
データベースは純粋なK-V(Key-Value)データベースで、値の型は文字列(string)です。各スマートコントラクトは、他のスマートコントラクトからステータスデータを読むことができますが、自分のフィールド以外は書き込むことができません。
コーディング
class Test {
init() {
storage.put("value1", "foobar")
}
read() {
console.log(storage.get("value1"))
}
change(someone) {
storage.put("value1", someone)
}
}
module.exports = Test;
ABIは省略します。
ステートストレージの使用
コードをデプロイした後、次の方法でストレージを入手できます。
curl -X POST \
http://localhost:30001/getContractStorage \
-H 'Content-Type: application/json' \
-H 'cache-control: no-cache' \
-d '{
"id": "Contract5bxTBndRrNjMJqJdRwiC9MVtfp6Z2LFFDp3AEjceHo2e",
"key": "value1",
"by_longest_chain": true
}'
このPOSTメッセージはJSONを返します。
{
"data": "foobar"
}
この値は、changeを呼び出すことで変更できます。
iwallet \
--expiration 10000 --gas_limit 1000000 --gas_ratio 1 \
--server localhost:30002 \
--account admin \
--amount_limit '*:unlimited' \
call "Contract5bxTBndRrNjMJqJdRwiC9MVtfp6Z2LFFDp3AEjceHo2e" "change" '["foobaz"]'
権限管理とスマートコントラクトの失敗
権限管理の基礎は次のようになっています。
例
if (!blockchain.requireAuth("someone", "active")) {
Throw "require auth error" // throw that is not caught will be thrown to the virtual machine, causing failure
}
次の点に注意してください。
- requireAuth自身は、操作を終了しないで、ブール値を返すだけですので、判断が必要になります。
- requireAuth(tx.publisher, "active")は、常にtrueを返します。
トランザクションが実行に失敗したら、スマートコントラクトは完全にロールバックされますが、トランザクションを実行したユーザのGAS代は差し引かれます。(ロールバックされるので、RAMは請求されません)
失敗したトランザクションは簡単なテストで確認できます。
iwallet \
--expiration 10000 --gas_limit 1000000 --gas_ratio 1 \
--server localhost:30002 \
--account admin \
--amount_limit '*:unlimited' \
call "token.iost" "transfer" '["iost", "someone", "me". "10000.00", "this is steal"]'
この結果は次のようになります。
Sending transaction...
Transaction has been sent.
The transaction hash is: 6KY4h4gKHFwuovXZJEDzvPtN9YYcJ5kUFHLf84gktYYu
Checking transaction receipt...
running action Action{Contract: token.iost, ActionName: transfer, Data: ["iost", "someone", "me". "10000.00", "this... error: prepare contract: error in data: invalid character '.' after array element, ["iost", "someone", "me". "10000.00", "this is steal"]
デバッグ
上記のように、最初にローカルノードを開始します。Dockerなら、次のコマンドでログを表示することができます。
Docker ps -f <container>
この時点で、console.log()を追加することでコードに必要なログを追加できます。以下はストレージサンプルの実行中に出力されたログです。
Info 2019-01-08 06:44:11.110 pob.go:378 Gen block - @7 id:IOSTfQFocq..., t:1546929851105164574, num:378, confirmed:377, txs:1, pendingtxs:0, et: 4ms
Info 2019-01-08 06:44:11.416 value.go:447 foobar
Info 2019-01-08 06:44:11.419 pob.go:378 Gen block - @8 id:IOSTfQFocq..., t:1546929851402828690, num:379, confirmed:378, txs:2, pendingtxs:0, et: 16ms