public override Empty ChargeTransactionFees(ChargeTransactionFeesInput input) { if (input.Equals(new ChargeTransactionFeesInput())) { return(new Empty()); } ChargeFirstSufficientToken(input.SymbolToAmount, out var symbol, out var amount, out var existingBalance); if (State.PreviousBlockTransactionFeeTokenSymbolList.Value == null) { State.PreviousBlockTransactionFeeTokenSymbolList.Value = new TokenSymbolList(); } if (!State.PreviousBlockTransactionFeeTokenSymbolList.Value.SymbolList.Contains(symbol)) { State.PreviousBlockTransactionFeeTokenSymbolList.Value.SymbolList.Add(symbol); } var fromAddress = Context.Sender; State.Balances[fromAddress][symbol] = existingBalance.Sub(amount); State.ChargedFees[fromAddress][symbol] = State.ChargedFees[fromAddress][symbol].Add(amount); return(new Empty()); }
private bool ChargeSizeFee(ChargeTransactionFeesInput input, ref TransactionFeeBill bill) { string symbolChargedForBaseFee = null; var amountChargedForBaseFee = 0L; var symbolToPayTxFee = input.PrimaryTokenSymbol; if (bill.FeesMap.Any()) { symbolChargedForBaseFee = bill.FeesMap.First().Key; amountChargedForBaseFee = bill.FeesMap.First().Value; } var availableBalance = symbolChargedForBaseFee == symbolToPayTxFee // Available balance need to deduct amountChargedForBaseFee ? GetBalance(Context.Sender, symbolToPayTxFee).Sub(amountChargedForBaseFee) : GetBalance(Context.Sender, symbolToPayTxFee); var txSizeFeeAmount = input.TransactionSizeFee; if (input.SymbolsToPayTxSizeFee.Any()) { var allSymbolToTxFee = input.SymbolsToPayTxSizeFee; var availableSymbol = allSymbolToTxFee.FirstOrDefault(x => GetBalanceCalculatedBaseOnPrimaryToken(x, symbolChargedForBaseFee, amountChargedForBaseFee) >= txSizeFeeAmount) ?? allSymbolToTxFee.FirstOrDefault(x => GetBalanceCalculatedBaseOnPrimaryToken(x, symbolChargedForBaseFee, amountChargedForBaseFee) > 0); if (availableSymbol != null && availableSymbol.TokenSymbol != symbolToPayTxFee) { symbolToPayTxFee = availableSymbol.TokenSymbol; txSizeFeeAmount = txSizeFeeAmount.Mul(availableSymbol.AddedTokenWeight) .Div(availableSymbol.BaseTokenWeight); availableBalance = symbolChargedForBaseFee == symbolToPayTxFee ? GetBalance(Context.Sender, symbolToPayTxFee).Sub(amountChargedForBaseFee) : GetBalance(Context.Sender, symbolToPayTxFee); } } var chargeAmount = availableBalance > txSizeFeeAmount ? txSizeFeeAmount : availableBalance; if (symbolToPayTxFee == null) { return(availableBalance >= txSizeFeeAmount); } if (symbolChargedForBaseFee == symbolToPayTxFee) { bill.FeesMap[symbolToPayTxFee] = bill.FeesMap[symbolToPayTxFee].Add(chargeAmount); } else { bill.FeesMap.Add(symbolToPayTxFee, chargeAmount); } return(availableBalance >= txSizeFeeAmount); }
/// <summary> /// Related transactions will be generated by acs1 pre-plugin service, /// and will be executed before the origin transaction. /// </summary> /// <param name="input"></param> /// <returns></returns> public override BoolValue ChargeTransactionFees(ChargeTransactionFeesInput input) { Assert(input.MethodName != null && input.ContractAddress != null, "Invalid charge transaction fees input."); // Primary token not created yet. if (string.IsNullOrEmpty(input.PrimaryTokenSymbol)) { return(new BoolValue { Value = true }); } // Record tx fee bill during current charging process. var bill = new TransactionFeeBill(); var fromAddress = Context.Sender; var methodFees = Context.Call <MethodFees>(input.ContractAddress, nameof(GetMethodFee), new StringValue { Value = input.MethodName }); var successToChargeBaseFee = true; if (methodFees != null && methodFees.Fees.Any()) { successToChargeBaseFee = ChargeBaseFee(GetBaseFeeDictionary(methodFees), ref bill); } var successToChargeSizeFee = true; if (!IsMethodFeeSetToZero(methodFees)) { // Then also do not charge size fee. successToChargeSizeFee = ChargeSizeFee(input, ref bill); } // Update balances. foreach (var tokenToAmount in bill.FeesMap) { ModifyBalance(fromAddress, tokenToAmount.Key, -tokenToAmount.Value); Context.Fire(new TransactionFeeCharged { Symbol = tokenToAmount.Key, Amount = tokenToAmount.Value }); if (tokenToAmount.Value == 0) { Context.LogDebug(() => $"Maybe incorrect charged tx fee of {tokenToAmount.Key}: it's 0."); } } return(new BoolValue { Value = successToChargeBaseFee && successToChargeSizeFee }); }
public async Task Set_Repeat_Token_Test() { await IssueTokenAsync(NativeTokenSymbol, 100000_00000000); await SetPrimaryTokenSymbolAsync(); var address = DefaultSender; var methodName = nameof(TokenContractContainer.TokenContractStub.Transfer); var basicMethodFee = 1000; var methodFee = new MethodFees { MethodName = methodName, Fees = { new MethodFee { Symbol = NativeTokenSymbol, BasicFee = basicMethodFee }, new MethodFee { Symbol = NativeTokenSymbol, BasicFee = basicMethodFee } } }; var sizeFee = 0; await SubmitAndPassProposalOfDefaultParliamentAsync(TokenContractAddress, nameof(TokenContractImplContainer.TokenContractImplStub.SetMethodFee), methodFee); var beforeChargeBalance = await GetBalanceAsync(address, NativeTokenSymbol); var chargeTransactionFeesInput = new ChargeTransactionFeesInput { MethodName = methodName, ContractAddress = TokenContractAddress, TransactionSizeFee = sizeFee, }; var chargeFeeRet = await TokenContractStub.ChargeTransactionFees.SendAsync(chargeTransactionFeesInput); chargeFeeRet.Output.Success.ShouldBeTrue(); var afterChargeBalance = await GetBalanceAsync(address, NativeTokenSymbol); beforeChargeBalance.Sub(afterChargeBalance).ShouldBe(basicMethodFee.Add(basicMethodFee)); }
/// <summary> /// Related transactions will be generated by acs1 pre-plugin service, /// and will be executed before the origin transaction. /// </summary> /// <param name="input"></param> /// <returns></returns> public override TransactionFee ChargeTransactionFees(ChargeTransactionFeesInput input) { Assert(input.MethodName != null && input.ContractAddress != null, "Invalid charge transaction fees input."); var transactionFee = new TransactionFee(); // Primary token not created yet. if (string.IsNullOrEmpty(input.PrimaryTokenSymbol)) { return(transactionFee); } // Record tx fee bill during current charging process. var bill = new TransactionFeeBill(); var fromAddress = Context.Sender; var methodFees = Context.Call <MethodFees>(input.ContractAddress, nameof(GetMethodFee), new StringValue { Value = input.MethodName }); var successToChargeBaseFee = true; if (methodFees != null && methodFees.Fees.Any()) { successToChargeBaseFee = ChargeBaseFee(GetBaseFeeDictionary(methodFees), ref bill); } var successToChargeSizeFee = ChargeSizeFee(input, ref bill); // Update the bill. var oldBill = State.ChargedFees[fromAddress]; State.ChargedFees[fromAddress] = oldBill == null ? bill : oldBill + bill; // Update balances. foreach (var tokenToAmount in bill.TokenToAmount) { State.Balances[fromAddress][tokenToAmount.Key] = State.Balances[fromAddress][tokenToAmount.Key].Sub(tokenToAmount.Value); transactionFee.Value[tokenToAmount.Key] = tokenToAmount.Value; } transactionFee.IsFailedToCharge = !successToChargeBaseFee || !successToChargeSizeFee; return(transactionFee); }
public override Empty ChargeTransactionFees(ChargeTransactionFeesInput input) { if (input.Equals(new ChargeTransactionFeesInput())) { return(new Empty()); } var tokenInfo = AssertValidToken(input.Symbol, input.Amount); Assert(tokenInfo.Symbol == State.NativeTokenSymbol.Value, "The paid fee is not in native token."); var fromAddress = Context.Sender; var existingBalance = State.Balances[fromAddress][input.Symbol]; Assert(existingBalance >= input.Amount, "Insufficient balance."); State.Balances[fromAddress][input.Symbol] = existingBalance.Sub(input.Amount); State.ChargedFees[fromAddress][input.Symbol] = State.ChargedFees[fromAddress][input.Symbol].Add(input.Amount); return(new Empty()); }
private bool ChargeSizeFee(ChargeTransactionFeesInput input, ref TransactionFeeBill bill) { string symbolChargedForBaseFee = null; var amountChargedForBaseFee = 0L; if (bill.TokenToAmount.Any()) { symbolChargedForBaseFee = bill.TokenToAmount.First().Key; amountChargedForBaseFee = bill.TokenToAmount.First().Value; } var availableBalance = symbolChargedForBaseFee == input.PrimaryTokenSymbol // Available balance need to deduct amountChargedForBaseFee ? State.Balances[Context.Sender][input.PrimaryTokenSymbol].Sub(amountChargedForBaseFee) : State.Balances[Context.Sender][input.PrimaryTokenSymbol]; var txSizeFeeAmount = input.TransactionSizeFee; var chargeAmount = availableBalance > txSizeFeeAmount // Is available balance enough to pay tx size fee? ? txSizeFeeAmount : availableBalance; if (input.PrimaryTokenSymbol == null) { return(availableBalance >= txSizeFeeAmount); } if (symbolChargedForBaseFee == input.PrimaryTokenSymbol) { bill.TokenToAmount[input.PrimaryTokenSymbol] = bill.TokenToAmount[input.PrimaryTokenSymbol].Add(chargeAmount); } else { bill.TokenToAmount.Add(input.PrimaryTokenSymbol, chargeAmount); } return(availableBalance >= txSizeFeeAmount); }
public async Task <IEnumerable <Transaction> > GetPreTransactionsAsync( IReadOnlyList <ServiceDescriptor> descriptors, ITransactionContext transactionContext) { try { var context = _contextService.Create(); if (_transactionFeeExemptionService.IsFree(transactionContext.Transaction)) { return(new List <Transaction>()); } context.TransactionContext = transactionContext; var tokenContractAddress = context.GetContractAddressByName(TokenSmartContractAddressNameProvider.Name); if (context.CurrentHeight < Constants.GenesisBlockHeight + 1 || tokenContractAddress == null) { return(new List <Transaction>()); } if (!IsAcs1(descriptors) && transactionContext.Transaction.To != tokenContractAddress) { return(new List <Transaction>()); } var tokenStub = GetTokenContractStub(transactionContext.Transaction.From, tokenContractAddress); if (transactionContext.Transaction.To == tokenContractAddress && transactionContext.Transaction.MethodName == nameof(tokenStub.ChargeTransactionFees)) { // Skip ChargeTransactionFees itself return(new List <Transaction>()); } var txSize = transactionContext.Transaction.Size(); var chainContext = new ChainContext { BlockHash = transactionContext.PreviousBlockHash, BlockHeight = transactionContext.BlockHeight - 1 }; var txCost = await _calStrategy.GetCostAsync(chainContext, txSize); var chargeTransactionFeesInput = new ChargeTransactionFeesInput { MethodName = transactionContext.Transaction.MethodName, ContractAddress = transactionContext.Transaction.To, TransactionSizeFee = txCost, PrimaryTokenSymbol = await _primaryTokenSymbolProvider.GetPrimaryTokenSymbol(), }; var symbolListToPayTxSizeFee = await _symbolListToPayTxFeeService.GetExtraAcceptedTokensInfoAsync(chainContext); if (symbolListToPayTxSizeFee != null) { foreach (var tokenInfo in symbolListToPayTxSizeFee) { chargeTransactionFeesInput.SymbolsToPayTxSizeFee.Add(new SymbolToPayTXSizeFee { TokenSymbol = tokenInfo.TokenSymbol, BaseTokenWeight = tokenInfo.BaseTokenWeight, AddedTokenWeight = tokenInfo.AddedTokenWeight }); } } var chargeFeeTransaction = (await tokenStub.ChargeTransactionFees.SendAsync(chargeTransactionFeesInput)) .Transaction; return(new List <Transaction> { chargeFeeTransaction }); } catch (Exception e) { Logger.LogError(e, "Failed to generate ChargeTransactionFees tx."); throw; } }
public async Task <IEnumerable <Transaction> > GetPreTransactionsAsync( IReadOnlyList <ServiceDescriptor> descriptors, ITransactionContext transactionContext) { try { var chainContext = new ChainContext { BlockHash = transactionContext.PreviousBlockHash, BlockHeight = transactionContext.BlockHeight - 1 }; var tokenContractAddress = await _smartContractAddressService.GetAddressByContractNameAsync(chainContext, TokenSmartContractAddressNameProvider.StringName); if (transactionContext.BlockHeight < AElfConstants.GenesisBlockHeight + 1 || tokenContractAddress == null) { return(new List <Transaction>()); } if (!IsTargetAcsSymbol(descriptors) && transactionContext.Transaction.To != tokenContractAddress) { return(new List <Transaction>()); } var tokenStub = _contractReaderFactory.Create(new ContractReaderContext { Sender = transactionContext.Transaction.From, ContractAddress = tokenContractAddress }); if (transactionContext.Transaction.To == tokenContractAddress && transactionContext.Transaction.MethodName == nameof(tokenStub.ChargeTransactionFees)) { // Skip ChargeTransactionFees itself return(new List <Transaction>()); } var txCost = await _txFeeService.CalculateFeeAsync(transactionContext, chainContext); var chargeTransactionFeesInput = new ChargeTransactionFeesInput { MethodName = transactionContext.Transaction.MethodName, ContractAddress = transactionContext.Transaction.To, TransactionSizeFee = txCost, }; var transactionSizeFeeSymbols = await _transactionSizeFeeSymbolsProvider.GetTransactionSizeFeeSymbolsAsync(chainContext); if (transactionSizeFeeSymbols != null) { foreach (var transactionSizeFeeSymbol in transactionSizeFeeSymbols.TransactionSizeFeeSymbolList) { chargeTransactionFeesInput.SymbolsToPayTxSizeFee.Add(new SymbolToPayTxSizeFee { TokenSymbol = transactionSizeFeeSymbol.TokenSymbol, BaseTokenWeight = transactionSizeFeeSymbol.BaseTokenWeight, AddedTokenWeight = transactionSizeFeeSymbol.AddedTokenWeight }); } } var chargeFeeTransaction = tokenStub.ChargeTransactionFees.GetTransaction(chargeTransactionFeesInput); return(new List <Transaction> { chargeFeeTransaction }); } catch (Exception e) { Logger.LogError(e, "Failed to generate ChargeTransactionFees tx."); throw; } }
public async Task ChargeTransactionFees_With_Different_Transaction_Size_Fee_Token(int[] order, long[] balance, int[] baseWeight, int[] tokenWeight, long sizeFee, string chargeSymbol, long chargeAmount, bool isSuccess) { await SetPrimaryTokenSymbolAsync(); var methodName = nameof(TokenContractContainer.TokenContractStub.Transfer); var basicMethodFee = 1000; var methodFee = new MethodFees { MethodName = methodName, Fees = { new MethodFee { Symbol = NativeTokenSymbol, BasicFee = basicMethodFee } } }; await SubmitAndPassProposalOfDefaultParliamentAsync(TokenContractAddress, nameof(TokenContractImplContainer.TokenContractImplStub.SetMethodFee), methodFee); var tokenSymbolList = new [] { NativeTokenSymbol, "CWJ", "YPA" }; var tokenCount = 3; var orderedSymbolList = new string[tokenCount]; var index = 0; foreach (var o in order) { orderedSymbolList[index++] = tokenSymbolList[o - 1]; } var sizeFeeSymbolList = new SymbolListToPayTxSizeFee(); for (var i = 0; i < tokenCount; i++) { var tokenSymbol = orderedSymbolList[i]; if (tokenSymbol != NativeTokenSymbol) { await CreateTokenAsync(DefaultSender, tokenSymbol); } if (balance[i] > 0) { await IssueTokenAsync(tokenSymbol, balance[i]); } sizeFeeSymbolList.SymbolsToPayTxSizeFee.Add(new SymbolToPayTxSizeFee { TokenSymbol = tokenSymbol, AddedTokenWeight = tokenWeight[i], BaseTokenWeight = baseWeight[i] }); } await SubmitAndPassProposalOfDefaultParliamentAsync(TokenContractAddress, nameof(TokenContractImplContainer.TokenContractImplStub.SetSymbolsToPayTxSizeFee), sizeFeeSymbolList); var beforeBalanceList = await GetDefaultBalancesAsync(orderedSymbolList); var chargeTransactionFeesInput = new ChargeTransactionFeesInput { MethodName = methodName, ContractAddress = TokenContractAddress, TransactionSizeFee = sizeFee, }; chargeTransactionFeesInput.SymbolsToPayTxSizeFee.AddRange(sizeFeeSymbolList.SymbolsToPayTxSizeFee); var chargeFeeRet = await TokenContractStub.ChargeTransactionFees.SendAsync(chargeTransactionFeesInput); chargeFeeRet.Output.Success.ShouldBe(isSuccess); var afterBalanceList = await GetDefaultBalancesAsync(orderedSymbolList); for (var i = 0; i < tokenCount; i++) { var balanceDiff = beforeBalanceList[i] - afterBalanceList[i]; if (orderedSymbolList[i] == chargeSymbol) { balanceDiff.ShouldBe(chargeAmount); } else { if (orderedSymbolList[i] == NativeTokenSymbol) { balanceDiff -= basicMethodFee; } balanceDiff.ShouldBe(0); } } }
/// <summary> /// Related transactions will be generated by acs1 pre-plugin service, /// and will be executed before the origin transaction. /// </summary> /// <param name="input"></param> /// <returns></returns> public override TransactionFee ChargeTransactionFees(ChargeTransactionFeesInput input) { Assert(input.MethodName != null && input.ContractAddress != null, "Invalid charge transaction fees input."); var result = new TransactionFee(); if (input.PrimaryTokenSymbol == string.Empty) { // Primary token not created yet. return(result); } var fee = Context.Call <MethodFees>(input.ContractAddress, nameof(GetMethodFee), new StringValue { Value = input.MethodName }); var bill = new TransactionFeeBill(); var fromAddress = Context.Sender; if (fee != null && fee.Fees.Any()) { if (!ChargeFirstSufficientToken(fee.Fees.ToDictionary(f => f.Symbol, f => f.BasicFee), out var symbol, out var amount, out var existingBalance)) { Assert(false, "Failed to charge first sufficient token."); } bill.TokenToAmount.Add(symbol, amount); // Charge base fee. State.Balances[fromAddress][symbol] = existingBalance.Sub(amount); result.Value.Add(symbol, amount); } var balanceAfterChargingBaseFee = State.Balances[fromAddress][input.PrimaryTokenSymbol]; var txSizeFeeAmount = input.TransactionSizeFee; txSizeFeeAmount = balanceAfterChargingBaseFee > txSizeFeeAmount // Enough to pay tx size fee. ? txSizeFeeAmount // It's safe to convert from long to int here because // balanceAfterChargingBaseFee <= txSizeFeeAmount and // typeof(txSizeFeeAmount) == int : (int)balanceAfterChargingBaseFee; bill += new TransactionFeeBill { TokenToAmount = { { input.PrimaryTokenSymbol, txSizeFeeAmount } } }; // Charge tx size fee. var finalBalanceOfNativeSymbol = balanceAfterChargingBaseFee.Sub(txSizeFeeAmount); State.Balances[fromAddress][input.PrimaryTokenSymbol] = finalBalanceOfNativeSymbol; // Record the bill finally. var oldBill = State.ChargedFees[fromAddress]; State.ChargedFees[fromAddress] = oldBill == null ? bill : oldBill + bill; // If balanceAfterChargingBaseFee < txSizeFeeAmount, make sender's balance of native symbol to 0 and make current execution failed. Assert( balanceAfterChargingBaseFee >= input.TransactionSizeFee, $"Insufficient balance to pay tx size fee: {balanceAfterChargingBaseFee} < {input.TransactionSizeFee}.\n " + $"Primary token: {input.PrimaryTokenSymbol}"); if (result.Value.ContainsKey(input.PrimaryTokenSymbol)) { result.Value[input.PrimaryTokenSymbol] = result.Value[input.PrimaryTokenSymbol].Add(txSizeFeeAmount); } else { result.Value.Add(input.PrimaryTokenSymbol, txSizeFeeAmount); } return(result); }
/// <summary> /// Related transactions will be generated by acs1 pre-plugin service, /// and will be executed before the origin transaction. /// </summary> /// <param name="input"></param> /// <returns></returns> public override ChargeTransactionFeesOutput ChargeTransactionFees(ChargeTransactionFeesInput input) { AssertTransactionGeneratedByPlugin(); Assert(input.MethodName != null && input.ContractAddress != null, "Invalid charge transaction fees input."); // Primary token not created yet. if (State.ChainPrimaryTokenSymbol.Value == null) { return(new ChargeTransactionFeesOutput { Success = true }); } // Record tx fee bill during current charging process. var bill = new TransactionFeeBill(); var fromAddress = Context.Sender; var methodFees = Context.Call <MethodFees>(input.ContractAddress, nameof(GetMethodFee), new StringValue { Value = input.MethodName }); var successToChargeBaseFee = true; if (methodFees != null && methodFees.Fees.Any()) { successToChargeBaseFee = ChargeBaseFee(GetBaseFeeDictionary(methodFees), ref bill); } var successToChargeSizeFee = true; if (!IsMethodFeeSetToZero(methodFees)) { // Then also do not charge size fee. successToChargeSizeFee = ChargeSizeFee(input, ref bill); } // Update balances. foreach (var tokenToAmount in bill.FeesMap) { ModifyBalance(fromAddress, tokenToAmount.Key, -tokenToAmount.Value); Context.Fire(new TransactionFeeCharged { Symbol = tokenToAmount.Key, Amount = tokenToAmount.Value }); if (tokenToAmount.Value == 0) { Context.LogDebug(() => $"Maybe incorrect charged tx fee of {tokenToAmount.Key}: it's 0."); } } var chargingResult = successToChargeBaseFee && successToChargeSizeFee; var chargingOutput = new ChargeTransactionFeesOutput { Success = chargingResult }; if (!chargingResult) { chargingOutput.ChargingInformation = "Transaction fee not enough."; } return(chargingOutput); }