Transaction Fee
ACS1 - Transaction Fee Standard
ACS1 handles transaction fees.
Interface#
Contracts using ACS1 must implement these methods:
Methods#
Method Name | Request Type | Response Type | Description |
---|---|---|---|
SetMethodFee | acs1.MethodFees | google.protobuf.Empty | Sets the method fees for a method, overriding all fees. |
ChangeMethodFeeController | AuthorityInfo | google.protobuf.Empty | Changes the method fee controller. Default is parliament. |
GetMethodFee | google.protobuf.StringValue | acs1.MethodFees | Queries the fee for a method by name. |
GetMethodFeeController | google.protobuf.Empty | AuthorityInfo | Queries the method fee controller. |
Types#
acs1.MethodFee
Field | Type | Description |
---|---|---|
symbol | string | The token symbol for the fee. |
basic_fee | int64 | The fee amount. |
acs1.MethodFees
Field | Type | Description |
---|---|---|
method_name | string | The name of the method. |
fees | MethodFee | List of fees. |
is_size_fee_free | bool | Optional based on implementation. |
AuthorityInfo
Field | Type | Description |
---|---|---|
contract_address | aelf.Address | The controller's contract address. |
owner_address | aelf.Address | The owner's address. |
Note: Only system contracts on the main chain can implement ACS1.
Usage#
A pre-transaction, generated by FeeChargePreExecutionPlugin, charges the transaction fee before main processing.
1/// <summary>2/// Related transactions will be generated by acs1 pre-plugin service,3/// and will be executed before the origin transaction.4/// </summary>5/// <param name="input"></param>6/// <returns></returns>7public override BoolValue ChargeTransactionFees(ChargeTransactionFeesInput input)8{9// ...10// Record tx fee bill during current charging process.11var bill = new TransactionFeeBill();12var fromAddress = Context.Sender;13var methodFees = Context.Call<MethodFees>(input.ContractAddress, nameof(GetMethodFee),14new StringValue {Value = input.MethodName});15var successToChargeBaseFee = true;16if (methodFees != null && methodFees.Fees.Any())17{18successToChargeBaseFee = ChargeBaseFee(GetBaseFeeDictionary(methodFees), ref bill);19}20var successToChargeSizeFee = true;21if (!IsMethodFeeSetToZero(methodFees))22{23// Then also do not charge size fee.24successToChargeSizeFee = ChargeSizeFee(input, ref bill);25}26// Update balances.27foreach (var tokenToAmount in bill.FeesMap)28{29ModifyBalance(fromAddress, tokenToAmount.Key, -tokenToAmount.Value);30Context.Fire(new TransactionFeeCharged31{32Symbol = tokenToAmount.Key,33Amount = tokenToAmount.Value34});35if (tokenToAmount.Value == 0)36{37//Context.LogDebug(() => $"Maybe incorrect charged tx fee of {tokenToAmount.Key}: it's 0.");38}39}40return new BoolValue {Value = successToChargeBaseFee && successToChargeSizeFee};41}
Steps:#
1/// <summary>2/// Burn 10% of tx fees.3/// If Side Chain didn't set FeeReceiver, burn all.4/// </summary>5/// <param name="symbol"></param>6/// <param name="totalAmount"></param>7private void TransferTransactionFeesToFeeReceiver(string symbol, long totalAmount)8{9Context.LogDebug(() => "Transfer transaction fee to receiver.");10if (totalAmount <= 0) return;11var burnAmount = totalAmount.Div(10);12if (burnAmount > 0)13Context.SendInline(Context.Self, nameof(Burn), new BurnInput14{15Symbol = symbol,16Amount = burnAmount17});18var transferAmount = totalAmount.Sub(burnAmount);19if (transferAmount == 0)20return;21var treasuryContractAddress =22Context.GetContractAddressByName(SmartContractConstants.TreasuryContractSystemName);23if ( treasuryContractAddress!= null)24{25// Main chain would donate tx fees to dividend pool.26if (State.DividendPoolContract.Value == null)27State.DividendPoolContract.Value = treasuryContractAddress;28State.DividendPoolContract.Donate.Send(new DonateInput29{30Symbol = symbol,31Amount = transferAmount32});33}34else35{36if (State.FeeReceiver.Value != null)37{38Context.SendInline(Context.Self, nameof(Transfer), new TransferInput39{40To = State.FeeReceiver.Value,41Symbol = symbol,42Amount = transferAmount,43});44}45else46{47// Burn all!48Context.SendInline(Context.Self, nameof(Burn), new BurnInput49{50Symbol = symbol,51Amount = transferAmount52});53}54}55}
Implementation#
Simple Implementation#
Implement only GetMethodFee to set fixed fees for methods.
1public override MethodFees GetMethodFee(StringValue input)2{3if (input.Value == nameof(Foo1) || input.Value == nameof(Foo2))4{5return new MethodFees6{7MethodName = input.Value,8Fees =9{10new MethodFee11{12BasicFee = 1_00000000,13Symbol = Context.Variables.NativeSymbol14}15}16};17}18if (input.Value == nameof(Bar1) || input.Value == nameof(Bar2))19{20return new MethodFees21{22MethodName = input.Value,23Fees =24{25new MethodFee26{27BasicFee = 2_00000000,28Symbol = Context.Variables.NativeSymbol29}30}31};32}33return new MethodFees();34}
Recommended Implementation#
1public MappedState<string, MethodFees> TransactionFees { get; set; }
1public override MethodFees GetMethodFee(StringValue input) {2return State.TransactionFees[input.Value];3}
1public SingletonState<AuthorityInfo> MethodFeeController { get; set; }
1public override Empty SetMethodFee(MethodFees input)2{3foreach (var symbolToAmount in input.Fees)4{5AssertValidToken(symbolToAmount.Symbol, symbolToAmount.BasicFee);6}7RequiredMethodFeeControllerSet();8Assert(Context.Sender == State.MethodFeeController.Value.OwnerAddress, "Unauthorized to set method fee.");9State.TransactionFees[input.MethodName] = input;10return new Empty();11}
Permission Management#
1private void RequiredMethodFeeControllerSet()2{3if (State.MethodFeeController.Value != null) return;4if (State.ParliamentContract.Value == null)5{6State.ParliamentContract.Value = Context.GetContractAddressByName(SmartContractConstants.ParliamentContractSystemName);7}8var defaultAuthority = new AuthorityInfo();9// Parliament Auth Contract maybe not deployed.10if (State.ParliamentContract.Value != null)11{12defaultAuthority.OwnerAddress = State.ParliamentContract.GetDefaultOrganizationAddress.Call(new Empty());13defaultAuthority.ContractAddress = State.ParliamentContract.Value;14}15State.MethodFeeController.Value = defaultAuthority;16}
Changing Authority#
The authority for SetMethodFee can be changed through a transaction from the default parliament address.
1public override Empty ChangeMethodFeeController(AuthorityInfo input)2{3RequiredMethodFeeControllerSet();4AssertSenderAddressWith(State.MethodFeeController.Value.OwnerAddress);5var organizationExist = CheckOrganizationExist(input);6Assert(organizationExist, "Invalid authority input.");7State.MethodFeeController.Value = input;8return new Empty();9}
1public override AuthorityInfo GetMethodFeeController(Empty input)2{3RequiredMethodFeeControllerSet();4return State.MethodFeeController.Value;5}
Testing#
Create ACS1’s Stub and call GetMethodFee and GetMethodFeeController to check the return values.
Example#
All aelf system contracts implement ACS1 and can be used as references.
Edited on: 15 July 2024 04:24:19 GMT+0