开发 EOS 合约,_code
是一个迈不过去的坎,也是导致 EOS 合约开发难度上升好几个级别的一个坎。
_code
算是 EOS 合约中的一个关键字,当然,也不能算是一个关键字。
_code
是什么?
_code
是 EOS 合约中的基础类 eosio::contract
中的一个受保护 ( protected
)的变量
eosiolib/contract.hpp
protected: /** * The code name of the action this contract is processing. * * @brief The code name of the action this contract is processing. */ name _code;
那么, 这个 _code
到底代表什么意思呢 ?
经过作者多次的实验,发现这个 _code
所表示的是 当前动作的合约。
这个 当前动作的合约 有三层意思:
-
如果是直接执行合约的某个动作 (
action
),那么当前动作的合约就是当前合约自己,是当前合约自己调用这个动作的。 -
如果是合约调用另一个合约,也就是 (
action().send()
) 方法,那么,这个_code
还是当前被调用的合约自己。因为action()
发起的是另一个动作,那么,另一个动作的执行合约肯定是动作所在的合约。 -
但是,如果是通知另一个合约,也就是
require_recipient()
方法,,那么,这个_code
就是调用合约,也就是require_recipient()
方法所在的合约。因为通知,并没有调用其他合约的意思,仅仅是告诉其它合约发生了什么事。
我们写一些代码来佐证下刚刚提到的想法
合约代码
假设我们存在一个 hello
合约,有一个 hi
动作和一个 hito
动作,代码如下
hello.cpp
#include<eosiolib/eosio.hpp> #include<eosiolib/dispatcher.hpp> using namespace eosio; class [[eosio::contract]] hello: eosio::contract { public: using eosio::contract::contract; [[eosio::action]] void hi(){ print("_code id:"); print(_code); } [[eosio::action]] void hito(){ print("_code id:"); print(_code); } }; extern "C" { void apply(uint64_t receiver, uint64_t code, uint64_t action) { print("in apply,code is:"); print(name{code}); print(" "); if (code == receiver) { switch (action) { EOSIO_DISPATCH_HELPER(hello,(hi)) } } else { if ( action == "hito"_n.value) { eosio::execute_action(eosio::name(receiver), eosio::name(code), &hello::hito); return; } eosio::execute_action(eosio::name(receiver), eosio::name(code), &hello::hi); } eosio_exit(0); } }
然后使用下面的命令编译合约
eosio-cpp -o hello.wasm hello.cpp --abigen
接着使用下面的命令部署合约
cleos set contract hello ../hello -p hello@active
最后使用下面的命令来运行 hello
合约的 hi
方法
cleos push action hello hi '[]' -p hello@active executed transaction: ed8ef66de6318765d307c2943b584cfa28bb2c3a775a60f513b72b6969afad87 96 bytes 231 us # hello <= hello::hi "" >> in apply,code is:hello _code id:hello
使用其他账号调用
cleos push action hello hi '[]' -p hi@active executed transaction: cc010bcc2b6a7abd93478038943c5d16c9b12786ddc64a511ae9d7781a1fda6d 96 bytes 264 us # hello <= hello::hi "" >> in apply,code is:hello _code id:hello
从上面的调用中可以看到,不管用什么账号来调用 hello
的 hi
动作,_code
始终是当前合约自己。
合约调合约
我们再写一个合约 hi
,添加一个 hello
动作,然后在 hello
动作内调用 hello
合约的 hi
方法。
hi.hpp
#include<eosiolib/eosio.hpp> #include<eosiolib/action.hpp> using namespace std; using namespace eosio; class [[eosio::contract]] hi: eosio::contract { public: using eosio::contract::contract; [[eosio::action]] void hello(){ action( permission_level(_self,"active"_n), "hello"_n, "hi"_n, std::make_tuple() ).send(); } [[eosio::action]] void hito(){ require_recipient("hello"_n); } }; EOSIO_DISPATCH(hi,(hello)(hito))
然后使用下面的命令编译合约
eosio-cpp -o hi.wasm hi.cpp --abigen
接着使用下面的命令部署合约
cleos set contract hi ../hi -p hi@active
如果我们这时候使用下面的命令调用合约,会发生权限错误
cleos push action hi hello '[]' -p hi@active
出错提示如下
debug 2018-11-11T06:28:40.909 thread-0 http_plugin.cpp:581 handle_exception ] Exception Details: 3050000 action_validate_exception: Action validate exception inline action's authorizations include a non-existent permission: {"actor":"hi","permission":"hi"} {"permission":{"actor":"hi","permission":"hi"}} thread-0 apply_context.cpp:214 execute_inline pending console output: {"console":""} thread-0 apply_context.cpp:72 exec_one
修复这个错误的办法也很简单,就是给 hi
合约自己设定一个 eosio.code
权限,命令如下
cleos set account permission hi active '{"threshold": 1,"keys": [{"key":"EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV","weight":1}],"accounts": [{"permission":{"actor":"hi","permission":"eosio.code"},"weight":1}]}' owner -p hi@owner
权限设置完成后,就可以正常调用了
cleos push action hello hi '[]' -p hi@active executed transaction: e995ce018e73292ff25b0958681d5b9462b5b82d7898a0af41987747b4259cca 96 bytes 235 us # hello <= hello::hi "" >> in apply,code is:hello _code id:hello
换一个账号测试一下
cleos push action hello hi '[]' -p hello@active executed transaction: c087f56f4f41e81aeafc342f3661029c9c9b256b25e914d1f759264387c63565 96 bytes 324 us # hello <= hello::hi "" >> in apply,code is:hello _code id:hello
从上面的测试中可以看到,合约调用合约,_code
还是被调用的合约自己。
通知另一个合约
上面代码中,我们为了偷懒,把通知另一个合约代码也写完了,就是使用 require_recipient()
方法
该方法会把调用当前合约的所有参数都原原本本的复制给被通知的合约。设置还会将自己 _self
作为 receiver
变量传递给被通知的合约
extern "C" { void apply(uint64_t receiver, uint64_t code, uint64_t action) { print("in apply,code is:"); print(name{code}); print(" "); if (code == receiver) { switch (action) { EOSIO_DISPATCH_HELPER(hello,(hi)) } } else { if ( action == "hito"_n.value) { eosio::execute_action(eosio::name(receiver), eosio::name(code), &hello::hito); return; } eosio::execute_action(eosio::name(receiver), eosio::name(code), &hello::hi); } eosio_exit(0); } }
在通知机制下,被通知的合约中
receiver
是被调用的合约,而 _code
则是通知的发起者。而 action
动作和其它参数则是原原本本的复制。
我们使用下面的命令来演示下通知机制下的 _code
cleos push action hi hito '[]' -p hello@active executed transaction: 8caf169fa8667464432d527e28a37427dca64dd9143680367187ae04474fd8b8 96 bytes 243 us # hi <= hi::hito "" # hello <= hi::hito "" >> in apply,code is:hi _code id:hi
换一个账号测试下
cleos push action hi hito '[]' -p hi@active executed transaction: 4e67b0de8fc49447c907f0e2fee538b12329c84daeff23e9925acf427dc6836e 96 bytes 317 us # hi <= hi::hito "" # hello <= hi::hito "" >> in apply,code is:hi _code id:hi
从上面的结果中可以看出,在通知机制下,被通知的合约的 _code
则是合约的通知者。
至此,_code
的所有谜团都解开了。