2.7 外部合约的Inline Action
刚刚,我们将一个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
, 将要调用的的actionstd::make_tuple(user, type)
,数据,name user
和string 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))