Edited at

ontologyのコントラクトコードの記述テクニック集

こんばんは @sugaret です。blockChainのadventCalendarがあることを発見し、そして翌日分が空席になっていたので忘年会を早めに切り上げて筆を執っています。


ontologyのハッカソンに参加してきました

先日 ontology のハッカソンに参加して来ました。

賞金総額100万円 Ontology ブロックチェーンハッカソン 東京 #1

cryptoZombies以外でコントラクトコードを書くのが初めてだったものの「pythonで書ける」という触れ込みに釣られて「それならいけるやーん」と思って参加したものの、ハマりにハマったのでその時に習ったテクニックを記しておきます。ちなみに個人的な反省記はこちらに。 ontologyのハッカソンでアイデア賞もらってきました

ハッカソン当日はontologyのデベロッパーの方々が中国からいらっしゃっていたのでいたので、リアルタイムで直接質問することができましたが、web上に日本語の情報はほぼ存在しないため、普段はdiscord経由で英語で質問するぐらいしか手段がないので、少しでもハマっている方の助けになればと思います。

※ ontologyは複数の言語でコントラクトコードを書くことができますが、今回はpythonについて触れます。将来的にはgolangでも書けるようになる予定らしいので、将来的にはこっちが主流になるのかも。


そもそもontologyって?

ontologyの出自や、基礎的なチュートリアルなどに関する話は ハッカソン主催でありLayerXの @moonty_sal 氏がまとめた 記事が非常に参考になるので、とりあえず独自トークンを発行するぐらいのところまでは記事に従って試してみることをオススメします。

Ontologyを用いたスマートコントラクト開発 vol.1 : Ontologyの概要からトークン発行まで


ハマったポイントとその解決策

基本的にimport句を利用したライブラリ読み込みは、標準ライブラリでさえも存在すると思ってはいけません。こちら にontologyで用意されているライブラリの説明がありますが、ここに書いててないけど用意されているものもあります。これはプロジェクトが日進月歩で進捗していて、アクティブに開発が行われているからでしょう、きっと。

ちなみに当日ぼくらが書いたコードはこのあたりにあります、イケてる感じじゃないですが参考になれば。


乱数生成

import random とかしたいですが、できないので以下のような手法を取ります。

from boa.interop.Ontology.Runtime import GetCurrentBlockHash

from boa.interop.System.Runtime import GetTime

randomMaxNumber = 100 # ランダムの最大値
# 0-99のランダムな整数値
randomNumber = (abs(GetCurrentBlockHash()) + GetTime()) % randomMaxNumber

GetCurrentBlockHash はこの関数が実行されるTxのblockのhashが入るようです。狙えば予測できるのでは?と思われるかもしれませんが、Txの発行者はそのTxがどのblockに入るかは予測できませんし、Txを取り込んだblockがどのタイミングでchainに取り込まれるのかも予測できないため、GetTime(実行時の時刻)も予測することができません。

体感、かなり乱数値が偏っていたような印象があったのですが、体感なので気のせいということにしておきます...


データ永続化

コントラクトコード内のglobal変数に定義した値の中に値を突っ込むだけでは永続化されないようなので、ある関数の実行時にコントラクトごとに用意されたkvsから読み出し、実行結果を書き込む必要があります。

from boa.interop.System.Storage import Put, Get, GetContext

from boa.interop.System.Runtime import Serialize, Deserialize

# kvsのkey
DATAKEY = 'key'

# save
dataMap = {
"key1":1,
"key2":2
}
mapInfo = Serialize(dataMap)
Put(GetContext(), DATAKEY, mapInfo)

# load
mapInfo = Get(GetContext(), DATAKEY)
dataMap = Deserialize(mapInfo)

このあたりにmapとlistのそれぞれの例がありますが、基本的にはlistもmapも同じようなノリです。


連想配列のkeyチェック

dataMap = {}

key = "key1" # 外から渡される
if key not in dataMap:
dataMap[key] = 0
dataMap[key] += 1

的なノリで書きたくても in句が使えません、諦めてください。

これに関しては結局、想定できるkeyに関してはデフォルト値を決めた上で突っ込んでおくことで解決しました。

dataMap = {

"key1":0,
"key2":0,
"key3":0
}
key = "key1" # 外から渡されるイメージ
dataMap[key] = dataMap[key] + 1 # += もついでに使えなかったのでこのように

予測できるものに関してはこれで一応回避できるんですが、予測できないものをkeyにしたいときはどうすうるのかは未だによくわかっていないです、どうやるんだろうか...


msg.sender とか onlyOwner 的な modifier

ある関数に対して叩ける人を制限する時とかに使いたいですね。というか制限しないと人のアセット触り放題になっちゃいかねないですね。今回僕らはこの実装が間に合わなかったのでガバガバ感じになってました。

CheckWitnessというのがあります。これはウォレットアドレスを渡すと、その関数の実行者とアドレスの所持者が一致しているかを判定してくれるようです。

たとえばコントラクトをデプロイした人しかさわれないようにするのであればこんな感じでしょうか。

from boa.interop.System.Runtime import CheckWitness

from boa.builtins import ToScriptHash

OWNER = ToScriptHash("XXXXXXXXXXXXXXXXXXXXXXX") # ownerのaddress

def getHoge():
if CheckWitness(OWNER) == False:
return False
# do something

また特定のアドレスが所持しているデータにアクセスする場合、そのアドレスの所有者からが実行しているかどうかを判定する必要があるので、こんな感じでしょうか。

from boa.interop.System.Runtime import CheckWitness

# このaddressは実行時にユーザに入力させる
def getHoge(address):
if CheckWitness(address) == False:
return False
# do something

若干遠回り感がありますが、このようにすることで実行者が主張しているアドレスと、実際の実行者のアドレスが一致していることの確認ができそうです。


文字列分割

splitなどは使えないのでスライスを使います。ぼくらのプロダクトの場合、文字列を1文字ずつに分割したかったので、以下のようにしています。

message = "hogehoge" # 外から渡される

message_array = []
i = 0
while i < len(message):
message_array.append(message[i:i+1])
i = i + 1


おわりに

自分が実装した時に本当に文献がなくてつらみが高まっていたので、どなたかのためになることを祈っております。ここにないことや、ここにあるものでも動かん、とかがあれば discordで相談されることをオススメします。英語で質問すれば開発者の方々が答えてくれると思います。

https://discordapp.com/invite/jRKSyMS