Action.requireRecepient、Action.sendInline和Transaction.send
在Ultrain的合约体系中,我们是没有办法将其它合约的代码嵌入到当前代码中来执行的,但是这并不意味不能执行其它合约中的代码。我们提供了三个方法,允许你从自己的合约中调用其它合约的代码:Action.requireRecepient(),Action.sendInline(),Transaction.send()。这三个方法可以提供不同的调用其它合约的方法。
Action.requireRecepient
从这个方法的名字中我们也可以看出来,这是一个通知。它的原型是
Action.requireRecepient(to: account_name): void
这个方法被调用时,to
合约上部署的同名方法将被调用。假如我们编写了含有以下方法的两个合约:
合约1:
//...
@action
recepient(name: string): void {
Log.s("hi, it is ").s(RNAME(this.receiver)).s(", I will call recepient with parameter: ").s(name).flush();
Action.requireRecipient(NAME("jack"));
}
我们将这个合约部署到帐号rose
上。
合约2:
//...
@action
recepient(name: string): void {
Action.requireAuth(NAME("rose"));
Log.s("hi, it is ").s(RNAME(this.receiver)).s(", recepient was called with parameter: ").s(name).flush();
}
这个合约部署到了jack
上。
这样我们就可以发起一笔交易来测试一下:
clultrain push action rose recepient '["messi"]' -p rose
如果一切正常的话,会产生以下输出
executed transaction: 6d2cfdd6fa9de76f00b7f8a46eeafa7acef955f8890e5976ab4a42e0ac31ae8d 112 bytes 848 us
# rose <= rose::recepient {"name":"messi"}
>> hi, it is rose, I will call recepient with parameter: messi
# jack <= rose::recepient {"name":"messi"}
>> hi, it is jack, recepient was called with parameter: messi
从上面的小示例我们可以看到以下几个事实:
- jack的++同名方法recepient++也被调用了。
- 传递的参数和交易发起时的参数
messi
是一致,不需要明确的传递这个参数。 - rose和jack的recepient方法都在同一个transaction里被执行了。
- rose和jack的recepient方法都具有rose的权限。
Action.sendInline
从Action.requireRecepient()的测试结果中,我们看到,requireRecepient()方法只能用相同的参数调用同名方法,这个在很多时候是很受限制的。所以,我们需要另一种方式,能够调用任意的方法。
这个新的方法就是Action.sendInline(),它允许我们调用我们想调用的任意方法。同样的,我们用例子来说明。
rose帐号的合约:
//...
@action
inline(name: string): void {
Log.s("hi, it is ").s(RNAME(this.receiver)).s(", I will call sendInline with parameter: ").s(name).flush();
let pl = new PermissionLevel(this.receiver, NAME("active"));
let params = new Parameters();
params.name = "messi";
Action.sendInline([pl], NAME("jack"), NEX("onInline"), params);
}
//...
jack帐号的合约:
//...
@action
onInline(name: string): void {
Action.requireAuth(NAME("rose"));
Log.s("hi, it is ").s(RNAME(this.receiver)).s(", onInline was called with parameter: ").s(name).flush();
}
//...
代码编译完之后,分别将它们部署到rose和jack帐号上。然后执行下面的命令:
clultrain push action rose inline '["cr7"]' -p rose
成功执行之后,将会产生以下输出:
executed transaction: 6a65a2ee39d35e469d2ea21e36665547090d2ee9795994511d3e17d48b500131 112 bytes 821 us
# rose <= rose::inline {"name":"cr7"}
>> hi, it is rose, I will call sendInline with parameter: cr7
# jack <= jack::onInline {"name":"messi"}
>> hi, it is jack, onInline was called with parameter: messi
我们可以得到以下事实:
- rose的合约中可以调用jack任意的方法。
- 调用方法时,可以传递任意参数。
- jack中的方法被调用时,具有发起时一样的权限(rose)。
- 交易在同一个transaction中被执行。
Transaction.send
前面我们介绍了Action.requireRecepient()和Action.sendInline()的使用方法和它们的特点,其中一条就是它们都在++同一个transaction中++被执行,这也就意味着,整个执行链条上如果有一个action失败了,那么整个transaction也就失败了。有些情况下,我们并不想所有的actions作为一个事务处理,这时候我们就需要Transaction.send()。
我们来演示一下这个方法是怎么使用的。
rose的合约:
//...
@action
deferred(name: string): void {
Log.s("hi, it is ").s(RNAME(this.receiver)).s(", I will call Tx.send deferred with parameter: ").s(name).flush();
let p = new Parameters();
p.name = name;
let act = new ActionImpl();
act.account = NAME("jack");
act.name = NEX("onDeferred");
act.data = SerializableToArray(p);
act.authorization.push(new PermissionLevel(this.receiver, NAME("active")));
let tx = new Transaction(0);
tx.actions.push(act);
tx.header.delay_sec = 5;
tx.send(1111, this.receiver, false);
}
//...
jack的合约:
//...
@action
onDeferred(name: string): void {
Action.requireAuth(NAME("rose"));
Log.s("hi, it is ").s(RNAME(this.receiver)).s(", onDeferred was called with parameter: ").s(name).flush();
}
//...
把合约部署到链上之后,我们执行一下rose的deferred方法(需要将rose的active权限代理给utrio.code,否则这个方法执行时会失败,设置代理的命令参考
clultrain set account permission rose active '{"threshold": 1,"keys": [{"key":"pubkey_of_rose","weight": 1}],"accounts": [{"permission":{"actor":"rose","permission":"utrio.code"},"weight":1}]}' owner -p rose
):
clultrain push action rose deferred '["henry"]' -p rose
)
这时候我们发现产生的返回信息:
executed transaction: 386b8c647cf6812586e7d7c2a711482eb5b9b25851f78d2a608932ffc513ffe5 152 bytes 1522 us
# rose <= rose::deferred {"name":"henry"}
>> hi, it is rose, I will call Tx.send deferred with parameter: henry
这里并没有jack合约中打印的log信息啊,log到哪里去了呢?如果我们可以看到节点的log的话,会发现有这样的log:
[(jack,onDeferred)->jack]: CONSOLE OUTPUT BEGIN =====================
hi, it is jack, onDeferred was called with parameter: henry
[(jack,onDeferred)->jack]: CONSOLE OUTPUT END =====================
这也说明,这个action后面被执行了。
Transaction.send具有以下特性:
- Transaction.send()可以调用jack任意的方法。
- 调用方法时,可以传递任意参数。
- jack中的方法被调用时,具有发起时一样的权限(rose)。
- 交易在不同的transaction中被执行。
总结
通过上面三个方法的执行结果对比,我们可以做一个关于它们的小总结:
方法 | 调用对方的方法 | 参数 | 权限 | 是否事务性质 |
---|---|---|---|---|
Action.requireRecepient | 同名方法 | 相同参数 | 和发起方权限一致 | 是的 |
Action.sendInline | 任意方法 | 任意参数 | 和发起方权限一致 | 是的 |
Transaction.send | 任意方法 | 任意参数 | 和发起方权限一致 | 不是 |
源码
上面我们提供的是代码片断,下面我们附上完整的源码,供大家参考。
rose合约的源码:
import "allocator/arena";
import { Log } from "../../../src/log";
import { Contract } from "../../../src/contract";
import { NAME, RNAME } from "../../../src/account";
import { Action, ActionImpl, SerializableToArray } from "../../../src/action";
import { PermissionLevel } from "../../../src/permission-level";
import { NEX } from "../../../lib/name_ex";
import { Transaction, OnErrorValue } from "../../../src/transaction";
class Parameters implements Serializable {
name: string;
}
class SourceContract extends Contract {
@action
recepient(name: string): void {
Log.s("hi, it is ").s(RNAME(this.receiver)).s(", I will call recepient with parameter: ").s(name).flush();
Action.requireRecipient(NAME("jack"));
}
@action
inline(name: string): void {
Log.s("hi, it is ").s(RNAME(this.receiver)).s(", I will call sendInline with parameter: ").s(name).flush();
let pl = new PermissionLevel(this.receiver, NAME("active"));
let params = new Parameters();
params.name = "messi";
Action.sendInline([pl], NAME("jack"), NEX("onInline"), params);
}
@action
deferred(name: string): void {
Log.s("hi, it is ").s(RNAME(this.receiver)).s(", I will call Tx.send deferred with parameter: ").s(name).flush();
let p = new Parameters();
p.name = name;
let act = new ActionImpl();
act.account = NAME("jack");
act.name = NEX("onDeferred");
act.data = SerializableToArray(p);
act.authorization.push(new PermissionLevel(this.receiver, NAME("active")));
let tx = new Transaction(0);
tx.actions.push(act);
tx.header.delay_sec = 5;
tx.send(1111, this.receiver, false);
}
public onError(): void {
let error = OnErrorValue.fromCurrentAction();
Log.s("I am ").s(RNAME(this.receiver)).s(", I get a onError calling for id: ").i(error.sender_id).flush();
if (error.sender_id == 1111) {
let tx = error.getTransaction();
Log.s("onError action account: ").s(RNAME(tx.actions[0].account)).flush();
// you send deferred tx but something wrong happened.
// you can do something to handle this case.
}
}
}
jack的合约源码:
import "allocator/arena";
import { Log } from "../../../src/log";
import { Contract } from "../../../src/contract";
import { RNAME, NAME } from "../../../src/account";
import { Action } from "../../../src/action";
class TargetContract extends Contract {
@action
recepient(name: string): void {
Action.requireAuth(NAME("rose"));
Log.s("hi, it is ").s(RNAME(this.receiver)).s(", recepient was called with parameter: ").s(name).flush();
}
@action
onInline(name: string): void {
Action.requireAuth(NAME("rose"));
Log.s("hi, it is ").s(RNAME(this.receiver)).s(", onInline was called with parameter: ").s(name).flush();
}
@action
onDeferred(name: string): void {
Action.requireAuth(NAME("rose"));
Log.s("hi, it is ").s(RNAME(this.receiver)).s(", onDeferred was called with parameter: ").s(name).flush();
}
public filterAction(orginalReceiver: account_name): boolean {
return true; // 这里设置本合约可以接受requireRecepient()调用。
}
}