2.7 外部合约的Inline Action

优质
小牛编辑
119浏览
2023-12-01

刚刚,我们将一个inline action发到一个在合约中定义好的action中.教程的这一部分中,我们将学到如何将actions发送到外部合约.因为我们已经完成了相当多的合约编写,我们将会让这个合约非常简单.我们将编写一个计数其他合约actions的合约.这个合约在真实世界用途很少,但是可以用来演示对外部合约进行inline action的调用.

Step 1:Addressbook 计数合约

进入到CONTRACTS_DIR,创建abcounter文件夹并创建一个abcounter.cpp文件

cd /Users/zhong/coding/CLion/contracts
mkdir abcounter
touch abcounter.cpp

用你喜欢的编辑器打开abcounter.cpp然后将下面的代码复制进去.这个合约非常基础,并且在在很大程度上并没有涵盖我们在此之前尚未涉及的内容.只有一个例外,它在下面的的代码中被提到了.

#include <eosiolib/eosio.hpp>

using namespace eosio;

class [[eosio::contract]] abcounter : public eosio::contract {
  public:
    using contract::contract;

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

    [[eosio::action]]
    void count(name user, std::string type) {
      require_auth( name("addressbook"));
      count_index counts(name(_code), _code.value);
      auto iterator = counts.find(user.value);

      if (iterator == counts.end()) {
        counts.emplace("addressbook"_n, [&]( auto& row ) {
          row.key = user;
          row.emplaced = (type == "emplace") ? 1 : 0;
          row.modified = (type == "modify") ? 1 : 0;
          row.erased = (type == "erase") ? 1 : 0;
        });
      }
      else {
        counts.modify(iterator, "addressbook"_n, [&]( auto& row ) {
          if(type == "emplace") { row.emplaced += 1; }
          if(type == "modify") { row.modified += 1; }
          if(type == "erase") { row.erased += 1; }
        });
      }
    }

  private:
    struct [[eosio::table]] counter {
      name key;
      uint64_t emplaced;
      uint64_t modified;
      uint64_t erased;
      uint64_t primary_key() const { return key.value; }
    };

    using count_index = eosio::multi_index<"counts"_n, counter>;
};

EOSIO_DISPATCH( abcounter, (count));

在此代码中只有一个新的概念,我们在此合约中使用 require_auth 传入addressbook合约来明确限制对action的调用需要一个特定账户.如下:

//只有 addressbook account/contract 能使用该命令. 
require_auth( name("addressbook"));

前面的教程中,我们使用require_auth都是传入了动态的值.

Step 2:为abcounter合约创建账户

打开你的terminal并执行下面的命令来创建abcounter用户.

cleos create account eosio abcounter EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV

Step 3:编译与部署

编译:

eosio-cpp -o abcounter.wasm abcounter.cpp --abigen

最后,部署abcounter合约.

cleos set contract abcounter /Users/zhong/coding/CLion/contracts/abcounter

Step 4:修改addressbook合约以发送inline action到abcounter

进入addressbook文件夹:

cd /Users/zhong/coding/CLion/contracts/addressbook

在编辑器中打开addressbook.cpp.本系列的最后一部分,我们将对自己的合约使用inline actions.现在,发送一个inline action到其他合约,我们新的abcounter合约.

在合约的private区块创建另一个helper叫做increment_counter.

void increment_counter(name user, std::string type) {

  action counter = action(
    permission_level{get_self(),"active"_n},
    "abcounter"_n,
    "count"_n,
    std::make_tuple(user, type)
  );

  counter.send();
}
  • permission_level{get_self(),"active"_n} ,get_self()返回当前的addressbook合约.active是被使用的permission.
  • "abcounter"_n, abcounter合约的账户名
  • "count"_n, 将要调用的的action
  • std::make_tuple(user, type),数据,name userstring type

    现在将下面的helpers调用添加到相应的action范围中.

//Emplace
increment_counter(user, "emplace");
//Modify
increment_counter(user, "modify");
//Erase
increment_counter(user, "erase");

你的addressbook合约应该像下面一样:

#include <eosiolib/eosio.hpp>
#include <eosiolib/print.hpp>

using namespace eosio;

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

public:
  using contract::contract;

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

  [[eosio::action]]
  void upsert(name user, std::string first_name, std::string last_name, uint64_t age, std::string street, std::string city, std::string state) {
    require_auth(user);
    address_index addresses(_code, _code.value);
    auto iterator = addresses.find(user.value);
    if( iterator == addresses.end() )
    {
      addresses.emplace(user, [&]( auto& row ) {
       row.key = user;
       row.first_name = first_name;
       row.last_name = last_name;
       row.age = age;
       row.street = street;
       row.city = city;
       row.state = state;

       send_summary(user, " successfully emplaced record to addressbook");
       increment_counter(user, "emplace");
      });
    }
    else {
      std::string changes;
      addresses.modify(iterator, user, [&]( auto& row ) {
        row.key = user;
        row.first_name = first_name;
        row.last_name = last_name;
        row.age = age;
        row.street = street;
        row.city = city;
        row.state = state;

        send_summary(user, " successfully modified record to addressbook");
        increment_counter(user, "modify");
      });

    }
  }

  [[eosio::action]]
  void erase(name user) {
    require_auth(user);

    address_index addresses(_code, _code.value);
    auto iterator = addresses.find(user.value);
    eosio_assert(iterator != addresses.end(), "Record does not exist");
    addresses.erase(iterator);
    send_summary(user, " successfully erased record from addressbook");
    increment_counter(user, "erase");
  }

  [[eosio::action]]
  void notify(name user, std::string msg) {
    require_auth(get_self());
    require_recipient(user);
  }

private:
  struct [[eosio::table]] person {
    name key;
    std::string first_name;
    std::string last_name;
    uint64_t age;
    std::string street;
    std::string city;
    std::string state;

    uint64_t primary_key() const { return key.value; }
    uint64_t get_secondary_1() const { return age;}

  };

  void send_summary(name user, std::string message) {
    action(
      permission_level{get_self(),"active"_n},
      get_self(),
      "notify"_n,
      std::make_tuple(user, name{user}.to_string() + message)
    ).send();
  };

  void increment_counter(name user, std::string type) {

    action counter = action(
      permission_level{get_self(),"active"_n},
      "abcounter"_n,
      "count"_n,
      std::make_tuple(user, type)
    );

    counter.send();
  }

  typedef eosio::multi_index<"people"_n, person, 
    indexed_by<"byage"_n, const_mem_fun<person, uint64_t, &person::get_secondary_1>>
  > address_index;

};

EOSIO_DISPATCH( addressbook, (upsert)(notify)(erase))

Step 5:重新编译与重新部署addressdbook合约

重新编译addressbook合约,我们不用重新生成ABI,因为我们的改动不会影响到ABI.

eosio-cpp -o addressbook.wasm addressbook.cpp --abigen

重新部署:

cleos set contract addressbook /Users/zhong/coding/CLion/contracts/addressbook

Step 6:测试

现在,我们部署好了abcounter以及重新部署好了addressbook,我们可以来测试了.

cleos push action addressbook upsert '["alice", "alice", "liddell", 19, "123 drink me way", "wonderland", "amsterdam"]' -p alice@active

如果出现以下错误,说明没有授权eosio.code :

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.

Error Details:

transaction declares authority '{"actor":"addressbook","permission":"active"}', but does not have signatures for it under a provided delay of 0 ms, provided permissions [{"actor":"addressbook","permission":"eosio.code"}], provided keys [], and a delay max limit of 3888000000 ms

重新执行:

cleos set account permission addressbook active '{"threshold": 1,"keys": [{"key": "EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV","weight": 1}], "accounts": [{"permission":{"actor":"addressbook","permission":"eosio.code"},"weight":1}]}' -p addressbook@owner

c

Result:

executed transaction: cc46f20da7fc431124e418ecff90aa882d9ca017a703da78477b381a0246eaf7  152 bytes  1493 us
#   addressbook <= addressbook::upsert          {"user":"alice","first_name":"alice","last_name":"liddell","street":"123 drink me way","city":"wonde...
#   addressbook <= addressbook::notify          {"user":"alice","msg":"alice successfully modified record in addressbook"}
#         alice <= addressbook::notify          {"user":"alice","msg":"alice successfully modified record in addressbook"}
#     abcounter <= abcounter::count             {"user":"alice","type":"modify"}

你可以看到,counter已经被成功的通知到了.现在查看下面这张表:

cleos get table abcounter abcounter counts --lower alice --limit 1

Result:

{
  "rows": [{
      "key": "alice",
      "emplaced": 1,
      "modified": 0,
      "erased": 0
    }
  ],
  "more": false
}

接下来,我们测试剩下的cation,因为我们知道alice的记录是已经存在的,所以upsert会修改记录.

cleos push action addressbook upsert '["alice", "alice", "liddell", 23, "runaway", "wonderland", "amsterdam"]' -p alice@active

Result:

executed transaction: a2ae779a4ecb286e0c45c9e0c7187c89c8de31e006479d3b44db2f26ccadedaf  152 bytes  17056 us
#   addressbook <= addressbook::upsert          {"user":"alice","first_name":"alice","last_name":"liddell","street":"1 there we go","city":"wonderla...
#   addressbook <= addressbook::notify          {"user":"alice","msg":"alice successfully modified record in addressbook."}
#         alice <= addressbook::notify          {"user":"alice","msg":"alice successfully modified record in addressbook."}
#     abcounter <= abcounter::count             {"user":"alice","type":"modify"}

清除记录:

cleos push action addressbook erase '["alice"]' -p alice@active

Result:

executed transaction: aa82577cb1efecf7f2871eac062913218385f6ab2597eaf31a4c0d25ef1bd7df  104 bytes  973 us
#   addressbook <= addressbook::erase           {"user":"alice"}
>> Erased
#   addressbook <= addressbook::notify          {"user":"alice","msg":"alice successfully erased record from addressbook"}
>> Notified
#         alice <= addressbook::notify          {"user":"alice","msg":"alice successfully erased record from addressbook"}
#     abcounter <= abcounter::count             {"user":"alice","type":"erase"}
warning: transaction executed locally, but may not be confirmed by the network yet    ]
Toaster:addressbook sandwich$

接下来,我们试试直接调用abcounter的函数:

cleos push action abcounter count '["alice","emplace"]' -p alice@active

Result:

Error 3090004: Missing required authority
Ensure that you have the related authority inside your transaction!;
If you are currently using 'cleos push action' command, try to add the relevant authority using -p option.

因为我们在require_auth中声明需要"addressbook"_n的权限,所以只有addressbook合约可以成功的调用该cation,alice来调用调用action是不会有用.

通过addressbook擦除记录:

cleos push action addressbook erase '["alice"]' -p alice@active

Result:

executed transaction: d00d697b86b99777f0ddff1fa51d72fa55b4e57b570c5e95317323fb36fc6dc4  104 bytes  13381 us
#   addressbook <= addressbook::erase           {"user":"alice"}
#   addressbook <= addressbook::notify          {"user":"alice","msg":"alice successfully erased record from addressbook"}
#         alice <= addressbook::notify          {"user":"alice","msg":"alice successfully erased record from addressbook"}
#     abcounter <= abcounter::count             {"user":"alice","type":"erase"}
warning: transaction executed locally, but may not be confirmed by the network yet    ]

查看abcounter表:

cleos get table abcounter abcounter counts --lower alice --limit

Result:

{
  "rows": [{
      "key": "alice",
      "emplaced": 1,
      "modified": 1,
      "erased": 1
    }
  ],
  "more": false
}

完美!

Extra Credit:更冗长的收据

以下修改根据所做的更改发送自定义收据,如果在修改期间没有改变,收据会反应情况.以下代码可以跑通,但是不怎么效率,你能优化它吗?

#include <eosiolib/eosio.hpp>
#include <eosiolib/print.hpp>

using namespace eosio;


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

public:
  using contract::contract;

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

  [[eosio::action]]
  void upsert(name user, std::string first_name, std::string last_name, uint64_t age, std::string street, std::string city, std::string state) {
    require_auth(user);

    address_index addresses(_code, _code.value);

    auto iterator = addresses.find(user.value);
    if( iterator == addresses.end() )
    {
      addresses.emplace(user, [&]( auto& row ){
       row.key = user;
       row.first_name = first_name;
       row.last_name = last_name;
       row.age = age;
       row.street = street;
       row.city = city;
       row.state = state;
       send_summary(user, " successfully emplaced record to addressbook");
       increment_counter(user, "emplace");
      });
    }
    else {
      std::string changes;
      addresses.modify(iterator, user, [&]( auto& row ) {

        if(row.first_name != first_name) {
          row.first_name = first_name;
          changes += "first name ";
        }

        if(row.last_name != last_name) {
          row.last_name = last_name;
          changes += "last name ";
        }

        if(row.age != age) {
          row.age = age;
          changes += "age ";
        }

        if(row.street != street) {
          row.street = street;
          changes += "street ";
        }

        if(row.city != city) {
          row.city = city;
          changes += "city ";
        }

        if(row.state != state) {
          row.state = state;
          changes += "state ";
        }
      });

      if(changes.length() > 0) {
        send_summary(user, " successfully modified record in addressbook. Fields changed: " + changes);
        increment_counter(user, "modify");
      } else {
        send_summary(user, " called upsert, but request resulted in no changes.");
      }
    }
  }

  [[eosio::action]]
  void erase(name user) {
    require_auth(user);
    address_index addresses(_code, _code.value);
    auto iterator = addresses.find(user.value);
    eosio_assert(iterator != addresses.end(), "Record does not exist");
    addresses.erase(iterator);
    send_summary(user, " successfully erased record from addressbook");
    increment_counter(user, "erase");
  }

  [[eosio::action]]
  void notify(name user, std::string msg) {
    require_auth(get_self());
    require_recipient(user);
  }

private:

  struct [[eosio::table]] person {
    name key;
    std::string first_name;
    std::string last_name;
    uint64_t age;
    std::string street;
    std::string city;
    std::string state;
    uint64_t primary_key() const { return key.value; }
    uint64_t get_secondary_1() const { return age;}
  };

  void send_summary(name user, std::string message) {
    action(
      permission_level{get_self(),"active"_n},
      get_self(),
      "notify"_n,
      std::make_tuple(user, name{user}.to_string() + message)
    ).send();
  };

  void increment_counter(name user, std::string type) {

    action counter = action(
      permission_level{get_self(),"active"_n},
      "abcounter"_n,
      "count"_n,
      std::make_tuple(user, type)
    );

    counter.send();
  }

  typedef eosio::multi_index<"people"_n, person, 
    indexed_by<"byage"_n, const_mem_fun<person, uint64_t, &person::get_secondary_1>>
  > address_index;
};

EOSIO_DISPATCH( addressbook, (upsert)(notify)(erase))