ACS5 - Contract Threshold Standard¶
If you want to raise the threshold for using contract, consider implementing ACS5.
Interface¶
To limit to call a method in a contract, you only need to implement the following five interfaces:
Methods¶
Method Name | Request Type | Response Type | Description |
---|---|---|---|
SetMethodCallingThreshold | acs5.SetMethodCallingThresholdInput | google.protobuf.Empty | Set the threshold for method calling. |
GetMethodCallingThreshold | google.protobuf.StringValue | acs5.MethodCallingThreshold | Get the threshold for method calling. |
Types¶
acs5.MethodCallingThreshold¶
Field | Type | Description | Label |
---|---|---|---|
symbol_to_amount | MethodCallingThreshold.SymbolToAmountEntry | The threshold for method calling, token symbol -> amount. | repeated |
threshold_check_type | ThresholdCheckType | The type of threshold check. |
acs5.MethodCallingThreshold.SymbolToAmountEntry¶
Field | Type | Description | Label |
---|---|---|---|
key | string | ||
value | int64 |
acs5.SetMethodCallingThresholdInput¶
Field | Type | Description | Label |
---|---|---|---|
method | string | The method name to check. | |
symbol_to_amount | SetMethodCallingThresholdInput.SymbolToAmountEntry | The threshold for method calling, token symbol -> amount. | repeated |
threshold_check_type | ThresholdCheckType | The type of threshold check. |
acs5.SetMethodCallingThresholdInput.SymbolToAmountEntry¶
Field | Type | Description | Label |
---|---|---|---|
key | string | ||
value | int64 |
acs5.ThresholdCheckType¶
Name | Number | Description |
---|---|---|
BALANCE | 0 | Check balance only. |
ALLOWANCE | 1 | Check balance and allowance at the same time. |
Usage¶
Similar to ACS1, which uses an automatically generated pre-plugin
transaction called ChargeTransactionFees
to charge a transaction
fee, ACS5 automatically generates a pre-plugin transaction called
CheckThreshold
to test whether the account that sent the transaction
can invoke the corresponding method.
The implementation of 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();
}
In other words, if the token balance of the sender of the transaction or the amount authorized for the target contract does not reach the set limit, the pre-plugin transaction will throw an exception, thereby it prevents the original transaction from executing.
Implementation¶
Just lik the GetMethodFee
of ACS1, you can implement only one
GetMethodCallingThreshold
method.
It can also be achieved by using MappedState<string, MethodCallingThreshold> in the State class:
public MappedState<string, MethodCallingThreshold> MethodCallingThresholds { get; set; }
But at the same time, do not forget to configure the call permission of
SetMethodCallingThreshold
, which requires the definition of an Admin
in the State (of course, you can also use ACS3):
public SingletonState<Address> Admin { get; set; }
The easiest implementation:
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();
}
message SetMethodCallingThresholdInput {
string method = 1;
map<string, int64> symbol_to_amount = 2;// The order matters.
ThresholdCheckType threshold_check_type = 3;
}
Test¶
You can test the Foo method defined above.
Make a Stub:
var keyPair = SampleECKeyPairs.KeyPairs[0];
var acs5DemoContractStub =
GetTester<ACS5DemoContractContainer.ACS5DemoContractStub>(DAppContractAddress, keyPair);
Before setting the threshold, check the current threshold, which should be 0:
var methodResult = await acs5DemoContractStub.GetMethodCallingThreshold.CallAsync(
new StringValue
{
Value = nameof(acs5DemoContractStub.Foo)
});
methodResult.SymbolToAmount.Count.ShouldBe(0);
The ELF balance of the caller of Foo should be greater than 1 ELF:
await acs5DemoContractStub.SetMethodCallingThreshold.SendAsync(
new SetMethodCallingThresholdInput
{
Method = nameof(acs5DemoContractStub.Foo),
SymbolToAmount =
{
{"ELF", 1_0000_0000}
},
ThresholdCheckType = ThresholdCheckType.Balance
});
Check the threshold again:
methodResult = await acs5DemoContractStub.GetMethodCallingThreshold.CallAsync(
new StringValue
{
Value = nameof(acs5DemoContractStub.Foo)
});
methodResult.SymbolToAmount.Count.ShouldBe(1);
methodResult.ThresholdCheckType.ShouldBe(ThresholdCheckType.Balance);
Send the Foo transaction via an account who has sufficient balance can succeed:
// Call with enough balance.
{
var executionResult = await acs5DemoContractStub.Foo.SendAsync(new Empty());
executionResult.TransactionResult.Status.ShouldBe(TransactionResultStatus.Mined);
}
Send the Foo transaction via another account without ELF fails:
// 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.");
}