ACS5 - 合约调用阈值标准¶
如果想提高合约的调用门槛,可以考虑实现ACS5。
接口¶
要限制对合约中某个方法的调用门槛,仅需要实现一个接口:
- GetMethodCallingThreshold,参数为string,返回值为acs5.proto文件中定义的MethodCallingThreshold类型。
但是出于合约部署后通过交易来修改门槛,可以实现另一个接口:
- SetMethodCallingThreshold,参数为SetMethodCallingThresholdInput。
MethodCallingThreshold类型的定义为:
message MethodCallingThreshold {
map<string, int64> symbol_to_amount = 1;// The order matters.
ThresholdCheckType threshold_check_type = 2;
}
enum ThresholdCheckType {
BALANCE = 0;
ALLOWANCE = 1;
}
枚举类型ThresholdCheckType存在的意义在于,合约方法调用的门槛有两类:
- 账户某种代币的余额充足即可调用,这种情况对应ThresholdCheckType.Balance;
- 不仅要求该账户某种代币的的余额充足,该账户对目标合约的授权额度也需要充足,这种情况对应ThresholdCheckType.Allowance。
- SetMethodCallingThresholdInput的定义为:
message SetMethodCallingThresholdInput {
string method = 1;
map<string, int64> symbol_to_amount = 2;// The order matters.
ThresholdCheckType threshold_check_type = 3;
}
应用¶
类似于ACS1中使用一个自动生成的名为ChargeTransactionFees的pre-plugin交易来收取交易手续费,ACS5会自动生成一个名为CheckThreshold的pre-plugin交易来检验发送该交易的账户是否能够调用对应的方法。
CheckThreshold的实现为:
public override Empty CheckThreshold(CheckThresholdInput input)
{
var meetThreshold = false;
var meetBalanceSymbolList = new List<string>();
foreach (var symbolToThreshold in input.SymbolToThreshold)
{
if (GetBalance(input.Sender, symbolToThreshold.Key) < symbolToThreshold.Value)
continue;
meetBalanceSymbolList.Add(symbolToThreshold.Key);
}
if (meetBalanceSymbolList.Count > 0)
{
if (input.IsCheckAllowance)
{
foreach (var symbol in meetBalanceSymbolList)
{
if (State.Allowances[input.Sender][Context.Sender][symbol] <
input.SymbolToThreshold[symbol]) continue;
meetThreshold = true;
break;
}
}
else
{
meetThreshold = true;
}
}
if (input.SymbolToThreshold.Count == 0)
{
meetThreshold = true;
}
Assert(meetThreshold, "Cannot meet the calling threshold.");
return new Empty();
}
也就是说,如果交易发送者的某种代币余额没达到设定的额度,或对目标合约授权的额度没达到设定的额度,该pre-plugin交易会抛出异常,从而阻止原始交易的执行,以此达到设置调用门槛的效果。
接口实现¶
可以和ACS1的GetMethodFee一样,仅实现一个GetMethodCallingThreshold方法。
也可以通过在State文件中一个MappedState<string, MethodCallingThreshold>类型的属性来实现:
public MappedState<string, MethodCallingThreshold> MethodCallingThresholds { get; set; }
不过与此同时不能忘记配置一下SetMethodCallingThreshold的调用权限,这就需要在State中定义一个Admin(当然也可以使用ACS3来完成):
public SingletonState<Address> Admin { get; set; }
最简单的实现如下:
public override Empty SetMethodCallingThreshold(SetMethodCallingThresholdInput input)
{
Assert(State.Admin.Value == Context.Sender, "No permission.");
State.MethodCallingThresholds[input.Method] = new MethodCallingThreshold
{
SymbolToAmount = {input.SymbolToAmount}
};
return new Empty();
}
public override MethodCallingThreshold GetMethodCallingThreshold(StringValue input)
{
return State.MethodCallingThresholds[input.Value];
}
public override Empty Foo(Empty input)
{
return new Empty();
}
测试¶
可以针对上面定义的Foo这个方法进行测试。
准备一个Stub:
var keyPair = SampleECKeyPairs.KeyPairs[0];
var acs5DemoContractStub =
GetTester<ACS5DemoContractContainer.ACS5DemoContractStub>(DAppContractAddress, keyPair);
在开始设置门槛之前,查一下现在门槛,是0:
var methodResult = await acs5DemoContractStub.GetMethodCallingThreshold.CallAsync(
new StringValue
{
Value = nameof(acs5DemoContractStub.Foo)
});
methodResult.SymbolToAmount.Count.ShouldBe(0);
虽然直接进行设置,需要Foo的调用者ELF余额大于1个ELF,才可以进行调用:
await acs5DemoContractStub.SetMethodCallingThreshold.SendAsync(
new SetMethodCallingThresholdInput
{
Method = nameof(acs5DemoContractStub.Foo),
SymbolToAmount =
{
{"ELF", 1_0000_0000}
},
ThresholdCheckType = ThresholdCheckType.Balance
});
此时再查门槛:
methodResult = await acs5DemoContractStub.GetMethodCallingThreshold.CallAsync(
new StringValue
{
Value = nameof(acs5DemoContractStub.Foo)
});
methodResult.SymbolToAmount.Count.ShouldBe(1);
methodResult.ThresholdCheckType.ShouldBe(ThresholdCheckType.Balance);
让一个余额充足的账户去调用Foo,可以成功:
// Call with enough balance.
{
var executionResult = await acs5DemoContractStub.Foo.SendAsync(new Empty());
executionResult.TransactionResult.Status.ShouldBe(TransactionResultStatus.Mined);
}
换一个没有ELF的账户去调用Foo,失败:
// Call without enough balance.
{
var poorStub =
GetTester<ACS5DemoContractContainer.ACS5DemoContractStub>(DAppContractAddress,
SampleECKeyPairs.KeyPairs[1]);
var executionResult = await poorStub.Foo.SendWithExceptionAsync(new Empty());
executionResult.TransactionResult.Error.ShouldContain("Cannot meet the calling threshold.");
}