EOS 上で複数の contract をまたいだシステムを実装したい場合にぶつかる課題とその解決方法についてのメモ。
課題
横断的(ex. 猫を食うゾンビ)・縦断的(ex. レイヤー構造化)なニーズの下、別の contract とやりとりする contract を実装したいシーンは往々にして存在する。また、こういった contract は、ユーザーの permission で別の contract の action を実行したい(プロキシのような役割を担いたい)ことが多いと思われる。
このような場合、基本的には下図のような構成になる。
ここで注意する必要があるのは、
appaccount11
にデプロイされた contract が useraccount1
の permission を利用して inline action を実行するためには、利用したい useraccount1
の permission(ex. active
permission)に対して、appaccount11
の eosio.code
permission が付与されている必要がある
ということである。これは、裏を返せば、
appaccount11
の eosio.code
permission が付与された useraccount1
の permission は、appaccount11
にデプロイされた contract が自由に利用できてしまう
ということである。
すなわち、この状況下では、appaccount11
の管理者に useraccount1
の permission を濫用されてしまう可能性がある。また、appaccount11
の管理者に悪意がなくとも、デプロイされた contract に脆弱性があった場合の被害拡大に繋がる可能性がある。冒頭で述べたようなニーズを満たすためとはいえ、こういったリスクは極力低減したい。
解決方法
上記の問題を解決する 1 つの方法は、
appaccount11
の eosio.code
permission を、それが必要な action の実行時のみ付与し、その action が完了したら即座に外す
ことである。
このように、action 実行と atomic に permission の着脱を行うことで、上述したリスクを低減することができる。これは、下図のような構成で実現できる。
上図における controller contract のサンプル実装は こちら。実装は非常にシンプルなので、controller.cpp
を見るだけで、何をやっているかは把握できるはず。
ユーザーは、この controller contract をあらかじめ useraccount1
にデプロイしておき、execute
action を介して別の contract の action を実行すればよい。このとき、execute
action は、
update_auth(auth_before)
:active
permission にauth_before
を設定execute_action(acnt, act, data)
:指定した action を実行update_auth(auth_after)
:active
permission にauth_after
を設定
というフローで処理を行うため、例えば、execute
action 実行前の active
permission が
{
"threshold": 1,
"keys": [
{
"key": "EOS57edFL2dE8sxaVQ6uT7Maizi6bD3zh9moXFjDCA35rCMxNYPyf",
"weight": 1
}
],
"accounts": [
{
"permission": {
"actor": "useraccount1",
"permission": "eosio.code"
},
"weight": 1
}
],
"waits": []
}
だとすると、auth_before
を
{
"threshold": 1,
"keys": [
{
"key": "EOS57edFL2dE8sxaVQ6uT7Maizi6bD3zh9moXFjDCA35rCMxNYPyf",
"weight": 1
}
],
"accounts": [
{
"permission": {
"actor": "useraccount1",
"permission": "eosio.code"
},
"weight": 1
},
{
"permission": {
"actor": "appaccount11",
"permission": "eosio.code"
},
"weight": 1
}
],
"waits": []
}
とし、auth_after
を
{
"threshold": 1,
"keys": [
{
"key": "EOS57edFL2dE8sxaVQ6uT7Maizi6bD3zh9moXFjDCA35rCMxNYPyf",
"weight": 1
}
],
"accounts": [
{
"permission": {
"actor": "useraccount1",
"permission": "eosio.code"
},
"weight": 1
}
],
"waits": []
}
とすれば、appaccount11
の eosio.code
permission が useraccount1
の active
permission に付与された状態をこの action 実行中のみに絞ることができるため、冒頭で述べたようなリスクを低減できる。
もちろん、直接お目当ての action を実行する場合と比べると実行コストが増大していることに注意する必要はある。
検証
実際に testnet で検証してみた結果が こちら。
なお、testnet 上の account と上図に記載された account の対応関係は以下のようになっている。
motokichi111
:useraccount1
proxytest111
:appaccount11
countertest1
:appaccount12
補足
上記 controller contract の execute
action の第 3 引数 std::vector<char> data
には、実行したい action の引数を適切にエンコードして渡す必要がある。このエンコードを行う方法はいくつかあるが、cleos を利用する方法が簡単なので記載しておく。
例えば、proxytest111
の increment
action の引数をエンコードしたい場合は以下のようにすればよい。
$ cleos --url https://api-kylin.eosasia.one convert pack_action_data proxytest111 increment '{"me":"motokichi111"}'
1042700d39483395