在合约执行过程中,合约平台允许该合约调用其他合约。本文先对合约运行的上下文环境进行介绍,然后通过示例来讲解合约间普通调用的方法。
合约运行的上下文环境
合约上下文指的是合约运行环境。例如,谁调用了这个合约(sender),本合约的ID是什么(self),当前合约能使用的 gas 是多少,当前合约本次收到多少资产。 这些信息可以通过以下 API 获取到:
Identity GetSender();
返回一个Identity类型的数据,该数据表示本次调用是谁发起的,消耗的gas是由发起者提供的。
uint64_t GetValue();
返回一个uint64_t类型的数据,该数据表示本次调用调用者给了本合约多少资产。
uint64_t GetGas();
返回一个uint64_t类型的数据,该数据表示当前合约还剩余多少可用gas。
Identity GetSelf();
返回一个Identity类型的数据,该数据表示返回本合约的唯一标识-Identity。
Identity GetOrigin();;
返回交易发起者的id。这是一个不随着调用深度变化的值,例:账户alice调用了A合约,A合约调用了B合约,B合约调用了C合约…,无论调用层次多深,执行GetOrigin()得到的都是alice的账户Identity。
普通调用
普通调用指在发生 CallContract
函数调用时,当前合约的上下文被保存,智能合约平台随后切换到被调用合约的上下文环境并执行相关代码。例如 A 合约(简称“A”)调用 B 合约(简称“B”)的某个方法 f,则在 f 执行过程中,上下文环境切换到 B 合约的环境,其对存储的影响体现在 B 合约相关的成员变量存储中。当普通调用发生时,需要指定被调用合约的Identity,转移给被调用合约的资产数量,gas数量,被调用合约接口名称,被调用合约接口的参数列表。
A->B
A 合约调用 B 合约。B 执行过程中,
GetSender()
是 A;B 的代码操作的都是 B 合约本身的存储。GetSelf()
得到的是 B 的合约 ID。A->B->C
A 合约调用 B 合约;B 合约调用 C 合约。B 执行过程中,
GetSender()
是 A,GetSelf()
是 B;C 执行过程中,GetSender()
是 B,GetSelf()
是 C。
普通调用使用CallContract
,基本形式为:
auto ret = CallContract<TYPE>(contractId, methodName, value, gas, args...);
其中TYPE是被调用合约方法的返回值类型。ret 是一个自动模板结构,其定义如下:
template<class TYPE>
struct {
int code; //如果被调用合约没有异常终止,则该值为0,否则为1
std::string msg; //如果被调用合约异常终止,则该值就是异常的消息内容;如果被调用合约没有出现异常,该值为空字符串
TYPE result; //如果合约没有出现异常,该值是被调用方法的返回值;若出现了异常,该值无意义。若TYPE为void,则没有该字段
};
CallContract
不支持指针类型作为参数或引用类型作为返回值,否则会导致合约编译错误。不要出现环形调用,即A->B->…->B,若出现环形调用,第一次调用B,也许修改了B中某些值,第二次调用B合约感知不到第一次调用造成的修改。这种情况很难精准预测B合约的行为,请尽量避免。
示例:
/*A合约*/
#include <mychainlib/contract.h>
using namespace mychain;
class A: public Contract {
public:
uint32_t last_value;
/* A合约调用B合约,已知B合约的id为b */
INTERFACE void CallB() {
last_value = 100;
Identity b = "0011223344556677889900112233445566778899001122334455667788990000";
auto ret = CallContract<uint32_t>(b/*contractID*/, "add"/*methodName*/, 0/*value*/,10000/*gas*/, 11/*x*/, 12/*y*/);
// B合约中last_value的值为33,A合约中的last_value依然为100
//ret.result的值为B合约的add方法的返回值,即1234
}
};
INTERFACE_EXPORT(A,(CallB));
/*B合约*/
#include <mychainlib/contract.h>
using namespace mychain;
class B: public Contract {
uint32_t last_value;
public:
INTERFACE uint32_t add( uint32_t x, uint32_t y) {
last_value = x + y;
return 1234;
}
};
INTERFACE_EXPORT(B, (add))