EOS 合约开发中的 _code 关键字谜团

yufei       6 年, 1 月 前       1471

开发 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 所表示的是 当前动作的合约

这个 当前动作的合约 有三层意思:

  1. 如果是直接执行合约的某个动作 ( action ),那么当前动作的合约就是当前合约自己,是当前合约自己调用这个动作的。

  2. 如果是合约调用另一个合约,也就是 ( action().send() ) 方法,那么,这个 _code 还是当前被调用的合约自己。因为 action() 发起的是另一个动作,那么,另一个动作的执行合约肯定是动作所在的合约。

  3. 但是,如果是通知另一个合约,也就是 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

从上面的调用中可以看到,不管用什么账号来调用 hellohi 动作,_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 的所有谜团都解开了。

目前尚无回复
简单教程 = 简单教程,简单编程
简单教程 是一个关于技术和学习的地方
现在注册
已注册用户请 登入
关于   |   FAQ   |   我们的愿景   |   广告投放   |  博客

  简单教程,简单编程 - IT 入门首选站

Copyright © 2013-2022 简单教程 twle.cn All Rights Reserved.