Contract Threshold
ACS5 - Contract Threshold Standard#
To raise the threshold for using a contract, consider implementing ACS5.
Interface#
To limit calling a method in a contract, implement these 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 | Check balance only. | |
ALLOWANCE | 1 | Check balance and allowance at the same time. |
Usage#
ACS5 works similarly to ACS1, which uses a pre-plugin transaction called ChargeTransactionFees to charge a transaction fee. ACS5 uses a pre-plugin transaction called CheckThreshold to ensure the account sending the transaction can invoke the method.
Implementation of CheckThreshold:#
1public override Empty CheckThreshold(CheckThresholdInput input)2{3var meetThreshold = false;4var meetBalanceSymbolList = new List<string>();5foreach (var symbolToThreshold in input.SymbolToThreshold)6{7if (GetBalance(input.Sender, symbolToThreshold.Key) < symbolToThreshold.Value)8continue;9meetBalanceSymbolList.Add(symbolToThreshold.Key);10}11if (meetBalanceSymbolList.Count > 0)12{13if (input.IsCheckAllowance)14{15foreach (var symbol in meetBalanceSymbolList)16{17if (State.Allowances[input.Sender][Context.Sender][symbol] <18input.SymbolToThreshold[symbol]) continue;19meetThreshold = true;20break;21}22}23else24{25meetThreshold = true;26}27}28if (input.SymbolToThreshold.Count == 0)29{30meetThreshold = true;31}32Assert(meetThreshold, "Cannot meet the calling threshold.");33return new Empty();34}
If the sender's token balance or the authorized amount for the target contract doesn't meet the set limit, the pre-plugin transaction throws an exception and prevents the original transaction from executing.
Implementation#
Implement a single GetMethodCallingThreshold method like GetMethodFee in ACS1. Use MappedState<string, MethodCallingThreshold> in the State class:
1public MappedState<string, MethodCallingThreshold> MethodCallingThresholds { get; set; }
Configure the call permission of SetMethodCallingThreshold, requiring an Admin in the State:
1public SingletonState<Address> Admin { get; set; }
1public override Empty SetMethodCallingThreshold(SetMethodCallingThresholdInput input)2{3Assert(State.Admin.Value == Context.Sender, "No permission.");4State.MethodCallingThresholds[input.Method] = new MethodCallingThreshold5{6SymbolToAmount = {input.SymbolToAmount}7};8return new Empty();9}1011public override MethodCallingThreshold GetMethodCallingThreshold(StringValue input)12{13return State.MethodCallingThresholds[input.Value];14}1516public override Empty Foo(Empty input)17{18return new Empty();19}2021message SetMethodCallingThresholdInput {22string method = 1;23map<string, int64> symbol_to_amount = 2;// The order matters.24ThresholdCheckType threshold_check_type = 3;25}
Test#
Test the Foo method:
1var keyPair = SampleECKeyPairs.KeyPairs[0];2var acs5DemoContractStub =3GetTester<ACS5DemoContractContainer.ACS5DemoContractStub>(DAppContractAddress, keyPair);
1var methodResult = await acs5DemoContractStub.GetMethodCallingThreshold.CallAsync(2new StringValue3{4Value = nameof(acs5DemoContractStub.Foo)5});6methodResult.SymbolToAmount.Count.ShouldBe(0);
1await acs5DemoContractStub.SetMethodCallingThreshold.SendAsync(2new SetMethodCallingThresholdInput3{4Method = nameof(acs5DemoContractStub.Foo),5SymbolToAmount =6{7{"ELF", 1_0000_0000}8},9ThresholdCheckType = ThresholdCheckType.Balance10});
1methodResult = await acs5DemoContractStub.GetMethodCallingThreshold.CallAsync(2new StringValue3{4Value = nameof(acs5DemoContractStub.Foo)5});6methodResult.SymbolToAmount.Count.ShouldBe(1);7methodResult.ThresholdCheckType.ShouldBe(ThresholdCheckType.Balance);
1// Call with enough balance.2{3var executionResult = await acs5DemoContractStub.Foo.SendAsync(new Empty());4executionResult.TransactionResult.Status.ShouldBe(TransactionResultStatus.Mined);5}
1// Call without enough balance.2{3var poorStub =4GetTester<ACS5DemoContractContainer.ACS5DemoContractStub>(DAppContractAddress,5SampleECKeyPairs.KeyPairs[1]);6var executionResult = await poorStub.Foo.SendWithExceptionAsync(new Empty());7executionResult.TransactionResult.Error.ShouldContain("Cannot meet the calling threshold.");8}
Edited on: 15 July 2024 05:16:22 GMT+0