diff --git a/contract/AElf.Contracts.MultiToken/TokenContractConstants.cs b/contract/AElf.Contracts.MultiToken/TokenContractConstants.cs index a14a4e9496..8a41b78140 100644 --- a/contract/AElf.Contracts.MultiToken/TokenContractConstants.cs +++ b/contract/AElf.Contracts.MultiToken/TokenContractConstants.cs @@ -22,4 +22,6 @@ public static class TokenContractConstants public const string SeedCollectionSymbol = "SEED-0"; public const string SeedOwnedSymbolExternalInfoKey = "__seed_owned_symbol"; public const string SeedExpireTimeExternalInfoKey = "__seed_exp_time"; + public const string NftCreateChainIdExternalInfoKey = "__nft_create_chain_id"; + public const int DefaultMaxBatchApproveCount = 100; } \ No newline at end of file diff --git a/contract/AElf.Contracts.MultiToken/TokenContractState.cs b/contract/AElf.Contracts.MultiToken/TokenContractState.cs index c32b0ea62f..b687cf1598 100644 --- a/contract/AElf.Contracts.MultiToken/TokenContractState.cs +++ b/contract/AElf.Contracts.MultiToken/TokenContractState.cs @@ -65,4 +65,6 @@ public partial class TokenContractState : ContractState public SingletonState
VoteContractAddress { get; set; } public SingletonState TokenIssuerAndOwnerModificationDisabled { get; set; } + + public SingletonState MaxBatchApproveCount { get; set; } } \ No newline at end of file diff --git a/contract/AElf.Contracts.MultiToken/TokenContract_Actions.cs b/contract/AElf.Contracts.MultiToken/TokenContract_Actions.cs index 249afc33ea..f7ff4e2812 100644 --- a/contract/AElf.Contracts.MultiToken/TokenContract_Actions.cs +++ b/contract/AElf.Contracts.MultiToken/TokenContract_Actions.cs @@ -32,8 +32,6 @@ public override Empty InitializeFromParentChain(InitializeFromParentChainInput i /// public override Empty Create(CreateInput input) { - // can not call create on side chain - Assert(State.SideChainCreator.Value == null, "Failed to create token if side chain creator already set."); var inputSymbolType = GetCreateInputSymbolType(input.Symbol); if (input.Owner == null) { @@ -52,6 +50,9 @@ private Empty CreateToken(CreateInput input, SymbolType symbolType = SymbolType. AssertValidCreateInput(input, symbolType); if (symbolType == SymbolType.Token || symbolType == SymbolType.NftCollection) { + // can not call create on side chain + Assert(State.SideChainCreator.Value == null, + "Failed to create token if side chain creator already set."); if (!IsAddressInCreateWhiteList(Context.Sender) && input.Symbol != TokenContractConstants.SeedCollectionSymbol) { @@ -253,14 +254,35 @@ public override Empty Approve(ApproveInput input) { AssertValidInputAddress(input.Spender); AssertValidToken(input.Symbol, input.Amount); - State.Allowances[Context.Sender][input.Spender][input.Symbol] = input.Amount; + Approve(input.Spender, input.Symbol, input.Amount); + return new Empty(); + } + + private void Approve(Address spender, string symbol, long amount) + { + State.Allowances[Context.Sender][spender][symbol] = amount; Context.Fire(new Approved { Owner = Context.Sender, - Spender = input.Spender, - Symbol = input.Symbol, - Amount = input.Amount + Spender = spender, + Symbol = symbol, + Amount = amount }); + } + + public override Empty BatchApprove(BatchApproveInput input) + { + Assert(input != null && input.Value != null && input.Value.Count > 0, "Invalid input ."); + Assert(input.Value.Count <= GetMaxBatchApproveCount(), "Exceeds the max batch approve count."); + foreach (var approve in input.Value) + { + AssertValidInputAddress(approve.Spender); + AssertValidToken(approve.Symbol, approve.Amount); + } + var approveInputList = input.Value.GroupBy(approve => approve.Symbol + approve.Spender, approve => approve) + .Select(approve => approve.Last()).ToList(); + foreach (var approve in approveInputList) + Approve(approve.Spender, approve.Symbol, approve.Amount); return new Empty(); } @@ -468,7 +490,6 @@ public override Empty CrossChainCreateToken(CrossChainCreateTokenInput input) Owner = validateTokenInfoExistsInput.Owner ?? validateTokenInfoExistsInput.Issuer }; RegisterTokenInfo(tokenInfo); - Context.Fire(new TokenCreated { Symbol = validateTokenInfoExistsInput.Symbol, @@ -626,4 +647,27 @@ public override BoolValue GetTokenIssuerAndOwnerModificationEnabled(Empty input) Value = !State.TokenIssuerAndOwnerModificationDisabled.Value }; } + + public override Empty SetMaxBatchApproveCount(Int32Value input) + { + Assert(input.Value > 0, "Invalid input."); + AssertSenderAddressWith(GetDefaultParliamentController().OwnerAddress); + State.MaxBatchApproveCount.Value = input.Value; + return new Empty(); + } + + public override Int32Value GetMaxBatchApproveCount(Empty input) + { + return new Int32Value + { + Value = GetMaxBatchApproveCount() + }; + } + + private int GetMaxBatchApproveCount() + { + return State.MaxBatchApproveCount.Value == 0 + ? TokenContractConstants.DefaultMaxBatchApproveCount + : State.MaxBatchApproveCount.Value; + } } \ No newline at end of file diff --git a/contract/AElf.Contracts.MultiToken/TokenContract_NFTHelper.cs b/contract/AElf.Contracts.MultiToken/TokenContract_NFTHelper.cs index bddfb444bf..4c3efdf6a3 100644 --- a/contract/AElf.Contracts.MultiToken/TokenContract_NFTHelper.cs +++ b/contract/AElf.Contracts.MultiToken/TokenContract_NFTHelper.cs @@ -13,8 +13,4 @@ private SymbolType GetCreateInputSymbolType(string symbol) return words[1] == TokenContractConstants.CollectionSymbolSuffix ? SymbolType.NftCollection : SymbolType.Nft; } - private void AssertNFTCreateInput(CreateInput input) - { - Assert(input.Decimals == 0, "NFT's decimals must be 0"); - } } \ No newline at end of file diff --git a/contract/AElf.Contracts.MultiToken/TokenContract_NFT_Actions.cs b/contract/AElf.Contracts.MultiToken/TokenContract_NFT_Actions.cs index 1926727e68..124d1dac73 100644 --- a/contract/AElf.Contracts.MultiToken/TokenContract_NFT_Actions.cs +++ b/contract/AElf.Contracts.MultiToken/TokenContract_NFT_Actions.cs @@ -9,23 +9,34 @@ public partial class TokenContract { private Empty CreateNFTCollection(CreateInput input) { - AssertNFTCreateInput(input); return CreateToken(input, SymbolType.NftCollection); } private Empty CreateNFTInfo(CreateInput input) { - AssertNFTCreateInput(input); var nftCollectionInfo = AssertNftCollectionExist(input.Symbol); input.IssueChainId = input.IssueChainId == 0 ? nftCollectionInfo.IssueChainId : input.IssueChainId; - Assert(input.IssueChainId == nftCollectionInfo.IssueChainId, - "NFT create ChainId must be collection's issue chainId"); - + Assert( + input.IssueChainId == nftCollectionInfo.IssueChainId, + "NFT issue ChainId must be collection's issue chainId"); + if (nftCollectionInfo.ExternalInfo != null && nftCollectionInfo.ExternalInfo.Value.TryGetValue( + TokenContractConstants.NftCreateChainIdExternalInfoKey, + out var nftCreateChainId) && long.TryParse(nftCreateChainId, out var nftCreateChainIdLong)) + { + Assert(nftCreateChainIdLong == Context.ChainId, + "NFT create ChainId must be collection's NFT create chainId"); + } + else + { + Assert(State.SideChainCreator.Value == null, + "Failed to create token if side chain creator already set."); + } + var owner = nftCollectionInfo.Owner ?? nftCollectionInfo.Issuer; Assert(Context.Sender == owner && owner == input.Owner, "NFT owner must be collection's owner"); - if (nftCollectionInfo.Symbol == TokenContractConstants.SeedCollectionSymbol) { + Assert(input.Decimals == 0 && input.TotalSupply == 1, "SEED must be unique."); Assert(input.ExternalInfo.Value.TryGetValue(TokenContractConstants.SeedOwnedSymbolExternalInfoKey, out var ownedSymbol), "OwnedSymbol does not exist."); Assert(input.ExternalInfo.Value.TryGetValue(TokenContractConstants.SeedExpireTimeExternalInfoKey, diff --git a/contract/AElf.Contracts.Profit/ProfitContract.cs b/contract/AElf.Contracts.Profit/ProfitContract.cs index c3c09b3aba..a1f43d1af4 100644 --- a/contract/AElf.Contracts.Profit/ProfitContract.cs +++ b/contract/AElf.Contracts.Profit/ProfitContract.cs @@ -769,18 +769,19 @@ public override Empty ClaimProfits(ClaimProfitsInput input) Context.LogDebug(() => $"Profitable details: {profitableDetails.Aggregate("\n", (profit1, profit2) => profit1.ToString() + "\n" + profit2)}"); + var profitableDetailCount = + Math.Min(ProfitContractConstants.ProfitReceivingLimitForEachTime, profitableDetails.Count); + var maxProfitReceivingPeriodCount = GetMaximumPeriodCountForProfitableDetail(profitableDetailCount); // Only can get profit from last profit period to actual last period (profit.CurrentPeriod - 1), // because current period not released yet. - for (var i = 0; - i < Math.Min(ProfitContractConstants.ProfitReceivingLimitForEachTime, profitableDetails.Count); - i++) + for (var i = 0; i < profitableDetailCount; i++) { var profitDetail = profitableDetails[i]; if (profitDetail.LastProfitPeriod == 0) // This detail never performed profit before. profitDetail.LastProfitPeriod = profitDetail.StartPeriod; - ProfitAllPeriods(scheme, profitDetail, beneficiary); + ProfitAllPeriods(scheme, profitDetail, beneficiary, maxProfitReceivingPeriodCount); } var profitDetailsToRemove = profitableDetails @@ -807,7 +808,41 @@ public override Empty ClaimProfits(ClaimProfitsInput input) return new Empty(); } - private Dictionary ProfitAllPeriods(Scheme scheme, ProfitDetail profitDetail, Address beneficiary, + /// + /// This method calculates the maximum period count for a profitable detail + /// based on the number of profitable details and the maximum profit receiving period + /// For example: + /// If the number of profitable detail is 10 and the maximum profit receiving period is 100, + /// the maximum period count for a profitable detail will be 10. + /// If the number of profitable detail is 10 and the maximum profit receiving period is 5, + /// the maximum period count for a profitable detail will be 1. + /// + /// The number of profitable details + /// + private int GetMaximumPeriodCountForProfitableDetail(int profitableDetailCount) + { + // Get the maximum profit receiving period count + var maxPeriodCount = GetMaximumProfitReceivingPeriodCount(); + // Check if the maximum period count is greater than the profitable detail count + // and if the profitable detail count is greater than 0 + return maxPeriodCount > profitableDetailCount && profitableDetailCount > 0 + // Divide the maximum period count by the profitable detail count + ? maxPeriodCount.Div(profitableDetailCount) + // If the conditions are not met, return 1 as the maximum period count + : 1; + } + + public override Empty SetMaximumProfitReceivingPeriodCount(Int32Value input) + { + ValidateContractState(State.ParliamentContract, SmartContractConstants.ParliamentContractSystemName); + Assert(Context.Sender == State.ParliamentContract.GetDefaultOrganizationAddress.Call(new Empty()), + "No permission."); + Assert(input.Value > 0, "Invalid maximum profit receiving period count."); + State.MaximumProfitReceivingPeriodCount.Value = input.Value; + return new Empty(); + } + + private Dictionary ProfitAllPeriods(Scheme scheme, ProfitDetail profitDetail, Address beneficiary, long maxProfitReceivingPeriodCount, bool isView = false, string targetSymbol = null) { var profitsMap = new Dictionary(); @@ -818,13 +853,11 @@ private Dictionary ProfitAllPeriods(Scheme scheme, ProfitDetail pr foreach (var symbol in symbols) { var totalAmount = 0L; - for (var period = profitDetail.LastProfitPeriod; - period <= (profitDetail.EndPeriod == long.MaxValue - ? Math.Min(scheme.CurrentPeriod - 1, - profitDetail.LastProfitPeriod.Add(ProfitContractConstants - .MaximumProfitReceivingPeriodCountOfOneTime)) - : Math.Min(scheme.CurrentPeriod - 1, profitDetail.EndPeriod)); - period++) + var targetPeriod = Math.Min(scheme.CurrentPeriod - 1, profitDetail.EndPeriod); + var maxProfitPeriod = profitDetail.EndPeriod == long.MaxValue + ? Math.Min(scheme.CurrentPeriod - 1, profitDetail.LastProfitPeriod.Add(maxProfitReceivingPeriodCount)) + : Math.Min(targetPeriod, profitDetail.LastProfitPeriod.Add(maxProfitReceivingPeriodCount)); + for (var period = profitDetail.LastProfitPeriod; period <= maxProfitPeriod; period++) { var periodToPrint = period; var detailToPrint = profitDetail; @@ -885,6 +918,15 @@ private Dictionary ProfitAllPeriods(Scheme scheme, ProfitDetail pr return profitsMap; } + + private int GetMaximumProfitReceivingPeriodCount() + { + var maxPeriodCount = State.MaximumProfitReceivingPeriodCount.Value; + maxPeriodCount = maxPeriodCount == 0 + ? ProfitContractConstants.DefaultMaximumProfitReceivingPeriodCountOfOneTime + : maxPeriodCount; + return maxPeriodCount; + } private void ValidateContractState(ContractReferenceState state, string contractSystemName) { diff --git a/contract/AElf.Contracts.Profit/ProfitContractConstants.cs b/contract/AElf.Contracts.Profit/ProfitContractConstants.cs index 59b80b971f..0aaf080aa5 100644 --- a/contract/AElf.Contracts.Profit/ProfitContractConstants.cs +++ b/contract/AElf.Contracts.Profit/ProfitContractConstants.cs @@ -6,5 +6,5 @@ public class ProfitContractConstants public const int DefaultProfitReceivingDuePeriodCount = 10; public const int MaximumProfitReceivingDuePeriodCount = 1024; public const int TokenAmountLimit = 5; - public const int MaximumProfitReceivingPeriodCountOfOneTime = 100; + public const int DefaultMaximumProfitReceivingPeriodCountOfOneTime = 100; } \ No newline at end of file diff --git a/contract/AElf.Contracts.Profit/ProfitContractState.cs b/contract/AElf.Contracts.Profit/ProfitContractState.cs index 7a525e3ff6..b0046830e0 100644 --- a/contract/AElf.Contracts.Profit/ProfitContractState.cs +++ b/contract/AElf.Contracts.Profit/ProfitContractState.cs @@ -17,4 +17,6 @@ public partial class ProfitContractState : ContractState public MappedState TransactionFees { get; set; } public SingletonState MethodFeeController { get; set; } + + public SingletonState MaximumProfitReceivingPeriodCount { get; set; } } \ No newline at end of file diff --git a/contract/AElf.Contracts.Profit/ViewMethods.cs b/contract/AElf.Contracts.Profit/ViewMethods.cs index 31d8c0d4d6..190204c3c5 100644 --- a/contract/AElf.Contracts.Profit/ViewMethods.cs +++ b/contract/AElf.Contracts.Profit/ViewMethods.cs @@ -61,49 +61,53 @@ private Hash GeneratePeriodVirtualAddressFromHash(Hash schemeId, long period) public override Int64Value GetProfitAmount(GetProfitAmountInput input) { - var profitItem = State.SchemeInfos[input.SchemeId]; - Assert(profitItem != null, "Scheme not found."); - var beneficiary = input.Beneficiary ?? Context.Sender; - var profitDetails = State.ProfitDetailsMap[input.SchemeId][beneficiary]; + var allProfitsMapResult = GetAllProfitsMap(input.SchemeId, input.Beneficiary, input.Symbol); - if (profitDetails == null) return new Int64Value { Value = 0 }; - - var profitVirtualAddress = Context.ConvertVirtualAddressToContractAddress(input.SchemeId); + return new Int64Value + { + Value = allProfitsMapResult.AllProfitsMap.TryGetValue(input.Symbol, out var value) ? value : 0 + }; + } - // ReSharper disable once PossibleNullReferenceException - var availableDetails = profitDetails.Details.Where(d => - d.LastProfitPeriod < profitItem.CurrentPeriod && (d.LastProfitPeriod == 0 - ? d.EndPeriod >= d.StartPeriod - : d.EndPeriod >= d.LastProfitPeriod) - ).ToList(); + public override GetAllProfitAmountOutput GetAllProfitAmount(GetAllProfitAmountInput input) + { + var allProfitsMapResult = GetAllProfitsMap(input.SchemeId, input.Beneficiary, input.Symbol); + return new GetAllProfitAmountOutput + { + AllProfitAmount = allProfitsMapResult.AllProfitsMap.TryGetValue(input.Symbol, out var allProfitAmount) + ? allProfitAmount + : 0, + OneTimeClaimableProfitAmount = + allProfitsMapResult.OneTimeClaimableProfitsMap.TryGetValue(input.Symbol, + out var oneTimeClaimableProfitAmount) + ? oneTimeClaimableProfitAmount + : 0 + }; + } - var amount = 0L; + public override ReceivedProfitsMap GetProfitsMap(ClaimProfitsInput input) + { + var allProfitsMapResult = GetAllProfitsMap(input.SchemeId, input.Beneficiary); - for (var i = 0; - i < Math.Min(ProfitContractConstants.ProfitReceivingLimitForEachTime, availableDetails.Count); - i++) + return new ReceivedProfitsMap { - var profitDetail = availableDetails[i]; - if (profitDetail.LastProfitPeriod == 0) profitDetail.LastProfitPeriod = profitDetail.StartPeriod; - - var profitsDict = ProfitAllPeriods(profitItem, profitDetail, beneficiary, true, - input.Symbol); - amount = amount.Add(profitsDict[input.Symbol]); - } + Value = { allProfitsMapResult.AllProfitsMap } + }; + } - return new Int64Value { Value = amount }; + public override GetAllProfitsMapOutput GetAllProfitsMap(GetAllProfitsMapInput input) + { + return GetAllProfitsMap(input.SchemeId, input.Beneficiary); } - public override ReceivedProfitsMap GetProfitsMap(ClaimProfitsInput input) + private GetAllProfitsMapOutput GetAllProfitsMap(Hash schemeId, Address beneficiary, string symbol = null) { - var scheme = State.SchemeInfos[input.SchemeId]; + var scheme = State.SchemeInfos[schemeId]; Assert(scheme != null, "Scheme not found."); - var beneficiary = input.Beneficiary ?? Context.Sender; - var profitDetails = State.ProfitDetailsMap[input.SchemeId][beneficiary]; - - if (profitDetails == null) return new ReceivedProfitsMap(); + beneficiary = beneficiary ?? Context.Sender; + var profitDetails = State.ProfitDetailsMap[schemeId][beneficiary]; - var profitVirtualAddress = Context.ConvertVirtualAddressToContractAddress(input.SchemeId); + if (profitDetails == null) return new GetAllProfitsMapOutput(); // ReSharper disable once PossibleNullReferenceException var availableDetails = profitDetails.Details.Where(d => @@ -111,26 +115,46 @@ public override ReceivedProfitsMap GetProfitsMap(ClaimProfitsInput input) ? d.EndPeriod >= d.StartPeriod : d.EndPeriod >= d.LastProfitPeriod) ).ToList(); - - var profitsDict = new Dictionary(); - for (var i = 0; - i < Math.Min(ProfitContractConstants.ProfitReceivingLimitForEachTime, availableDetails.Count); - i++) + + var profitableDetailCount = + Math.Min(ProfitContractConstants.ProfitReceivingLimitForEachTime, availableDetails.Count); + var maxProfitReceivingPeriodCount = GetMaximumPeriodCountForProfitableDetail(profitableDetailCount); + + var allProfitsDict = new Dictionary(); + var claimableProfitsDict = new Dictionary(); + for (var i = 0; i < availableDetails.Count; i++) { var profitDetail = availableDetails[i]; if (profitDetail.LastProfitPeriod == 0) profitDetail.LastProfitPeriod = profitDetail.StartPeriod; - - var profitsDictForEachProfitDetail = ProfitAllPeriods(scheme, profitDetail, beneficiary, true); - foreach (var kv in profitsDictForEachProfitDetail) - if (profitsDict.ContainsKey(kv.Key)) - profitsDict[kv.Key] = profitsDict[kv.Key].Add(kv.Value); - else - profitsDict[kv.Key] = kv.Value; + + var totalProfitsDictForEachProfitDetail = ProfitAllPeriods(scheme, profitDetail, beneficiary, profitDetail.EndPeriod.Sub(profitDetail.LastProfitPeriod),true, symbol); + AddProfitToDict(allProfitsDict, totalProfitsDictForEachProfitDetail); + if(i >= profitableDetailCount) continue; + var claimableProfitsDictForEachProfitDetail = ProfitAllPeriods(scheme, profitDetail, beneficiary, maxProfitReceivingPeriodCount,true, symbol); + AddProfitToDict(claimableProfitsDict, claimableProfitsDictForEachProfitDetail); } - return new ReceivedProfitsMap + return new GetAllProfitsMapOutput + { + AllProfitsMap = { allProfitsDict }, + OneTimeClaimableProfitsMap = { claimableProfitsDict } + }; + } + + private void AddProfitToDict(Dictionary profitsDict, Dictionary profitsToAdd) + { + foreach (var kv in profitsToAdd) + if (profitsDict.ContainsKey(kv.Key)) + profitsDict[kv.Key] = profitsDict[kv.Key].Add(kv.Value); + else + profitsDict[kv.Key] = kv.Value; + } + + public override Int32Value GetMaximumProfitReceivingPeriodCount(Empty input) + { + return new Int32Value { - Value = { profitsDict } + Value = GetMaximumProfitReceivingPeriodCount() }; } } \ No newline at end of file diff --git a/protobuf/profit_contract.proto b/protobuf/profit_contract.proto index 1326da62ff..aac44f2d6a 100644 --- a/protobuf/profit_contract.proto +++ b/protobuf/profit_contract.proto @@ -63,6 +63,9 @@ service ProfitContract { // Reset the manager of a scheme. rpc ResetManager (ResetManagerInput) returns (google.protobuf.Empty) { } + + rpc SetMaximumProfitReceivingPeriodCount(google.protobuf.Int32Value) returns (google.protobuf.Empty) { + } // Get all schemes managed by the specified manager. rpc GetManagingSchemeIds (GetManagingSchemeIdsInput) returns (CreatedSchemeIds) { @@ -93,11 +96,25 @@ service ProfitContract { rpc GetProfitAmount (GetProfitAmountInput) returns (google.protobuf.Int64Value) { option (aelf.is_view) = true; } + + // Query the amount of profit according to token symbol. + rpc GetAllProfitAmount (GetAllProfitAmountInput) returns (GetAllProfitAmountOutput) { + option (aelf.is_view) = true; + } // Query all profit (up to 10 periods). rpc GetProfitsMap (ClaimProfitsInput) returns (ReceivedProfitsMap) { option (aelf.is_view) = true; } + + // Query all profit. + rpc GetAllProfitsMap (GetAllProfitsMapInput) returns (GetAllProfitsMapOutput) { + option (aelf.is_view) = true; + } + + rpc GetMaximumProfitReceivingPeriodCount(google.protobuf.Empty) returns (google.protobuf.Int32Value) { + option (aelf.is_view) = true; + } } message CreateSchemeInput { @@ -308,11 +325,39 @@ message GetProfitAmountInput { aelf.Address beneficiary = 3; } +message GetAllProfitAmountInput { + // The scheme id. + aelf.Hash scheme_id = 1; + // The token symbol. + string symbol = 2; + // The beneficiary's address. + aelf.Address beneficiary = 3; +} + +message GetAllProfitAmountOutput{ + int64 all_profit_amount = 1; + int64 one_time_claimable_profit_amount = 2; +} + message ReceivedProfitsMap { // The collection of profits received, token symbol -> amount. map value = 1; } +message GetAllProfitsMapInput { + // The scheme id. + aelf.Hash scheme_id = 1; + // The address of beneficiary. + aelf.Address beneficiary = 2; +} + +message GetAllProfitsMapOutput { + // The collection of all profits received, token symbol -> amount. + map all_profits_map = 1; + // The collection of claimable profits received, token symbol -> amount. + map one_time_claimable_profits_map = 2; +} + message SchemeCreated { option (aelf.is_event) = true; // The virtual address of the created scheme. diff --git a/protobuf/token_contract.proto b/protobuf/token_contract.proto index d858bf5f1d..e1174b8b45 100644 --- a/protobuf/token_contract.proto +++ b/protobuf/token_contract.proto @@ -40,7 +40,10 @@ service TokenContract { // enabling the Spender to call TransferFrom. rpc Approve (ApproveInput) returns (google.protobuf.Empty) { } - + + rpc BatchApprove (BatchApproveInput) returns (google.protobuf.Empty) { + } + // This is the reverse operation for Approve, it will decrease the allowance. rpc UnApprove (UnApproveInput) returns (google.protobuf.Empty) { } @@ -354,6 +357,9 @@ message ApproveInput { // The amount of token to approve. int64 amount = 3; } +message BatchApproveInput { + repeated ApproveInput value = 1; +} message UnApproveInput { // The address that allowance will be decreased. diff --git a/protobuf/token_contract_impl.proto b/protobuf/token_contract_impl.proto index 9a7779ec45..5885914e80 100644 --- a/protobuf/token_contract_impl.proto +++ b/protobuf/token_contract_impl.proto @@ -83,6 +83,11 @@ service TokenContractImpl { rpc RemoveTransactionFeeFreeAllowancesConfig (RemoveTransactionFeeFreeAllowancesConfigInput) returns (google.protobuf.Empty) { } + rpc SetMaxBatchApproveCount (google.protobuf.Int32Value) returns (google.protobuf.Empty) { + + } + + // Delegatee sets the delegation and related information of the delegator based on a transaction. rpc SetTransactionFeeDelegateInfos (SetTransactionFeeDelegateInfosInput) returns (google.protobuf.Empty){ } @@ -173,6 +178,10 @@ service TokenContractImpl { rpc GetTokenIssuerAndOwnerModificationEnabled(google.protobuf.Empty) returns (google.protobuf.BoolValue) { option (aelf.is_view) = true; } + + rpc GetMaxBatchApproveCount (google.protobuf.Empty) returns (google.protobuf.Int32Value) { + + } } message AdvanceResourceTokenInput { diff --git a/src/AElf.Kernel.FeeCalculation/Extensions/TransactionResultExtensions.cs b/src/AElf.Kernel.FeeCalculation/Extensions/TransactionResultExtensions.cs index ff1233b211..138d7853f3 100644 --- a/src/AElf.Kernel.FeeCalculation/Extensions/TransactionResultExtensions.cs +++ b/src/AElf.Kernel.FeeCalculation/Extensions/TransactionResultExtensions.cs @@ -8,21 +8,30 @@ namespace AElf.Kernel.FeeCalculation.Extensions; public static class TransactionResultExtensions { - public static Dictionary GetChargedTransactionFees(this TransactionResult transactionResult) + public static Dictionary> GetChargedTransactionFees( + this TransactionResult transactionResult) { return transactionResult.Logs .Where(l => l.Name == nameof(TransactionFeeCharged)) - .Select(l => TransactionFeeCharged.Parser.ParseFrom(l.NonIndexed)) - .GroupBy(fee => fee.Symbol, fee => fee.Amount) - .ToDictionary(g => g.Key, g => g.Sum()); + .GroupBy( + log => TransactionFeeCharged.Parser.ParseFrom(log.Indexed[0]).ChargingAddress, + log => TransactionFeeCharged.Parser.ParseFrom(log.NonIndexed)) + .ToDictionary(e => e.Key, + e => e + .GroupBy(fee => fee.Symbol, fee => fee.Amount) + .ToDictionary(g => g.Key, g => g.Sum())); } - public static Dictionary GetConsumedResourceTokens(this TransactionResult transactionResult) + public static Dictionary> GetConsumedResourceTokens(this TransactionResult transactionResult) { var relatedLogs = transactionResult.Logs.Where(l => l.Name == nameof(ResourceTokenCharged)).ToList(); - if (!relatedLogs.Any()) return new Dictionary(); + if (!relatedLogs.Any()) return new Dictionary>(); return relatedLogs.Select(l => ResourceTokenCharged.Parser.ParseFrom(l.NonIndexed)) - .ToDictionary(e => e.Symbol, e => e.Amount); + .GroupBy(g => g.ContractAddress) + .ToDictionary(e => e.Key, + e => e + .GroupBy(fee => fee.Symbol, fee => fee.Amount) + .ToDictionary(g => g.Key, g => g.Sum())); } public static Dictionary GetOwningResourceTokens(this TransactionResult transactionResult) diff --git a/src/AElf.WebApp.Application.Chain/Dto/CalculateTransactionFeeOutput.cs b/src/AElf.WebApp.Application.Chain/Dto/CalculateTransactionFeeOutput.cs index bbba632175..1446177873 100644 --- a/src/AElf.WebApp.Application.Chain/Dto/CalculateTransactionFeeOutput.cs +++ b/src/AElf.WebApp.Application.Chain/Dto/CalculateTransactionFeeOutput.cs @@ -1,4 +1,6 @@ +using System; using System.Collections.Generic; +using AElf.Types; namespace AElf.WebApp.Application.Chain.Dto; @@ -6,7 +8,21 @@ public class CalculateTransactionFeeOutput { public bool Success { get; set; } + [Obsolete("This property is deprecated and will be removed in the next version. Use the TransactionFees instead.")] public Dictionary TransactionFee { get; set; } - + + [Obsolete("This property is deprecated and will be removed in the next version. Use the ResourceFees instead.")] public Dictionary ResourceFee { get; set; } + + public FeeDto TransactionFees { get; set; } + + public FeeDto ResourceFees { get; set; } + + public string Error { get; set; } +} + +public class FeeDto +{ + public string ChargingAddress { get; set; } + public Dictionary Fee { get; set; } } \ No newline at end of file diff --git a/src/AElf.WebApp.Application.Chain/Services/TransactionAppService.cs b/src/AElf.WebApp.Application.Chain/Services/TransactionAppService.cs index 2c6702ee8d..8d365d88d1 100644 --- a/src/AElf.WebApp.Application.Chain/Services/TransactionAppService.cs +++ b/src/AElf.WebApp.Application.Chain/Services/TransactionAppService.cs @@ -17,6 +17,7 @@ using Google.Protobuf.WellKnownTypes; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; +using Microsoft.Extensions.Options; using Volo.Abp; using Volo.Abp.EventBus.Local; using Volo.Abp.ObjectMapping; @@ -47,17 +48,21 @@ public class TransactionAppService : AElfAppService, ITransactionAppService private readonly ITransactionReadOnlyExecutionService _transactionReadOnlyExecutionService; private readonly ITransactionResultStatusCacheProvider _transactionResultStatusCacheProvider; private readonly IPlainTransactionExecutingService _plainTransactionExecutingService; + private readonly WebAppOptions _webAppOptions; + public TransactionAppService(ITransactionReadOnlyExecutionService transactionReadOnlyExecutionService, IBlockchainService blockchainService, IObjectMapper objectMapper, ITransactionResultStatusCacheProvider transactionResultStatusCacheProvider, - IPlainTransactionExecutingService plainTransactionExecutingService) + IPlainTransactionExecutingService plainTransactionExecutingService, + IOptionsMonitor webAppOptions) { _transactionReadOnlyExecutionService = transactionReadOnlyExecutionService; _blockchainService = blockchainService; _objectMapper = objectMapper; _transactionResultStatusCacheProvider = transactionResultStatusCacheProvider; _plainTransactionExecutingService = plainTransactionExecutingService; + _webAppOptions = webAppOptions.CurrentValue; LocalEventBus = NullLocalEventBus.Instance; Logger = NullLogger.Instance; @@ -293,17 +298,39 @@ private async Task EstimateTransactionFee(Transac executionReturnSets.FirstOrDefault()?.TransactionResult.GetChargedTransactionFees(); var resourceFees = executionReturnSets.FirstOrDefault()?.TransactionResult.GetConsumedResourceTokens(); result.Success = true; - result.TransactionFee = transactionFees; - result.ResourceFee = resourceFees; + result.TransactionFee = GetFeeValue(transactionFees); + result.ResourceFee = GetFeeValue(resourceFees); + result.TransactionFees = GetFee(transactionFees); + result.ResourceFees = GetFee(resourceFees); } else { result.Success = false; + result.Error = TransactionErrorResolver.TakeErrorMessage( + executionReturnSets.FirstOrDefault()?.TransactionResult.Error, _webAppOptions.IsDebugMode); } return result; } + private Dictionary GetFeeValue(Dictionary> feeMap) + { + return feeMap?.SelectMany(pair => pair.Value) + .GroupBy(p => p.Key) + .ToDictionary(g => g.Key, g => g.Sum(pair => pair.Value)); + } + + private FeeDto GetFee(Dictionary> feeMap) + { + var fee = feeMap?.Select(f => new FeeDto + { + ChargingAddress = f.Key.ToBase58(), + Fee = f.Value + }).FirstOrDefault(); + + return fee; + } + private async Task PublishTransactionsAsync(string[] rawTransactions) { var txIds = new string[rawTransactions.Length]; diff --git a/test/AElf.Contracts.Election.Tests/Full/ReleaseProfitsFromTreasury.cs b/test/AElf.Contracts.Election.Tests/Full/ReleaseProfitsFromTreasury.cs index cbe677415b..96657c3cd2 100644 --- a/test/AElf.Contracts.Election.Tests/Full/ReleaseProfitsFromTreasury.cs +++ b/test/AElf.Contracts.Election.Tests/Full/ReleaseProfitsFromTreasury.cs @@ -383,11 +383,11 @@ public async Task CheckTreasuryProfitsDistribution_Test() })).Balance; var profitTester = GetProfitContractTester(VoterKeyPairs[0]); - var profitAmount = (await profitTester.GetProfitAmount.CallAsync(new GetProfitAmountInput + var profitAmount = (await profitTester.GetAllProfitAmount.CallAsync(new GetAllProfitAmountInput { SchemeId = ProfitItemsIds[ProfitType.CitizenWelfare], Symbol = EconomicContractsTestConstants.NativeTokenSymbol - })).Value; + })).OneTimeClaimableProfitAmount; profitAmount.ShouldBeGreaterThan(0); var profitResult = await profitTester.ClaimProfits.SendAsync(new ClaimProfitsInput @@ -636,11 +636,11 @@ private async Task GetProfitAmount(ProfitType type) break; } - return (await stub.GetProfitAmount.CallAsync(new GetProfitAmountInput + return (await stub.GetAllProfitAmount.CallAsync(new GetAllProfitAmountInput { SchemeId = ProfitItemsIds[type], Symbol = EconomicContractsTestConstants.NativeTokenSymbol - })).Value; + })).OneTimeClaimableProfitAmount; } private async Task GetReleasedAmount() diff --git a/test/AElf.Contracts.MultiToken.Tests/BVT/NftApplicationTests.cs b/test/AElf.Contracts.MultiToken.Tests/BVT/NftApplicationTests.cs index 9733a8952a..6187172a80 100644 --- a/test/AElf.Contracts.MultiToken.Tests/BVT/NftApplicationTests.cs +++ b/test/AElf.Contracts.MultiToken.Tests/BVT/NftApplicationTests.cs @@ -15,6 +15,7 @@ public static class NftCollectionMetaFields public static string BaseUriKey = "__nft_base_uri"; public static string NftType = "__nft_type"; public const string IsItemIdReuseKey = "__nft_is_item_id_reuse"; + public const string NftCreateChainIdExternalInfoKey = "__nft_create_chain_id"; } public static class NftInfoMetaFields @@ -182,6 +183,47 @@ private async Task> CreateNftCollectionAndNft(bool reuseItemId = tr return symbols; } + private async Task CreateNftFailed() + { + var collectionInfo = new TokenInfo + { + Symbol = NftCollection1155Info.Symbol, + TokenName = NftCollection1155Info.TokenName, + TotalSupply = NftCollection1155Info.TotalSupply, + Decimals = NftCollection1155Info.Decimals, + Issuer = NftCollection1155Info.Issuer, + IssueChainId = NftCollection1155Info.IssueChainId, + ExternalInfo = new ExternalInfo() + { + Value = + { + { + NftCollectionMetaFields.NftCreateChainIdExternalInfoKey, + "1234" + } + } + }, + Owner = NftCollection1155Info.Issuer + }; + var createCollectionRes = await CreateNftCollectionAsync(collectionInfo); + createCollectionRes.TransactionResult.Status.ShouldBe(TransactionResultStatus.Mined); + var createNft2Res = await TokenContractStub.Create.SendWithExceptionAsync(new CreateInput + { + Symbol = $"{collectionInfo.Symbol}{Nft1155Info.Symbol}", + TokenName = Nft1155Info.TokenName, + TotalSupply = Nft1155Info.TotalSupply, + Decimals = Nft1155Info.Decimals, + Issuer = Nft1155Info.Issuer, + IsBurnable = Nft1155Info.IsBurnable, + IssueChainId = Nft1155Info.IssueChainId, + ExternalInfo = Nft1155Info.ExternalInfo, + Owner = Nft1155Info.Issuer + }); + createNft2Res.TransactionResult.Status.ShouldBe(TransactionResultStatus.Failed); + createNft2Res.TransactionResult.Error.Contains("NFT create ChainId must be collection's NFT create chainId") + .ShouldBeTrue(); + } + private void AssertTokenEqual(TokenCreated log, TokenInfo input) { Assert.Equal(log.TokenName, input.TokenName); @@ -202,6 +244,12 @@ public async Task MultiTokenContract_Create_1155Nft_Test() await CreateNftCollectionAndNft(); } + [Fact(DisplayName = "[MultiToken_Nft] Create 1155 nfts failed.")] + public async Task MultiTokenContract_Create_1155Nft_failed_Test() + { + await CreateNftFailed(); + } + [Fact(DisplayName = "[MultiToken_Nft] Create 721 nfts.")] public async Task MultiTokenContract_Create_721Nft_Test() { @@ -212,22 +260,6 @@ public async Task MultiTokenContract_Create_721Nft_Test() public async Task MultiTokenContract_Create_NFTCollection_Input_Check_Test() { var input = NftCollection721Info; - // Decimals check - { - var result = await CreateMutiTokenWithExceptionAsync(TokenContractStub, new CreateInput - { - Symbol = $"{input.Symbol}0", - TokenName = input.TokenName, - TotalSupply = input.TotalSupply, - Decimals = 8, - Issuer = input.Issuer, - IssueChainId = input.IssueChainId, - ExternalInfo = input.ExternalInfo, - Owner = input.Owner - }); - result.TransactionResult.Status.ShouldBe(TransactionResultStatus.Failed); - result.TransactionResult.Error.ShouldContain("NFT's decimals must be 0"); - } // Symbol check { var seedInput = BuildSeedCreateInput( new CreateInput @@ -287,21 +319,6 @@ public async Task MultiTokenContract_Create_NFT_Input_Check_Test() await CreateNftCollectionAsync(NftCollection721Info); var input = Nft721Info; - // Decimals check - { - var result = await CreateMutiTokenWithExceptionAsync(TokenContractStub, new CreateInput - { - Symbol = "GHJ-0", - TokenName = input.TokenName, - TotalSupply = input.TotalSupply, - Decimals = 8, - Issuer = input.Issuer, - IssueChainId = input.IssueChainId, - ExternalInfo = input.ExternalInfo - }); - result.TransactionResult.Status.ShouldBe(TransactionResultStatus.Failed); - result.TransactionResult.Error.ShouldContain("NFT's decimals must be 0"); - } // Symbol check { var result = await CreateSeedNftWithExceptionAsync(TokenContractStub, new CreateInput @@ -376,7 +393,7 @@ public async Task MultiTokenContract_Create_NFT_Input_Check_Test() Owner = input.Owner }); result.TransactionResult.Status.ShouldBe(TransactionResultStatus.Failed); - result.TransactionResult.Error.ShouldContain("NFT create ChainId must be collection's issue chainId"); + result.TransactionResult.Error.ShouldContain("NFT issue ChainId must be collection's issue chainId"); } } diff --git a/test/AElf.Contracts.MultiToken.Tests/BVT/TokenApplicationTests.cs b/test/AElf.Contracts.MultiToken.Tests/BVT/TokenApplicationTests.cs index dba96e1023..49642bae3a 100644 --- a/test/AElf.Contracts.MultiToken.Tests/BVT/TokenApplicationTests.cs +++ b/test/AElf.Contracts.MultiToken.Tests/BVT/TokenApplicationTests.cs @@ -171,6 +171,145 @@ public async Task MultiTokenContract_Approve_ContractAddress_Test() basicAllowanceOutput.Allowance.ShouldBe(2000L); } + [Fact(DisplayName = "[MultiToken] BatchApprove token to Contract")] + public async Task MultiTokenContract_BatchApprove_ContractAddress_Test() + { + await CreateTokenAndIssue(); + var approveBasisResult = (await TokenContractStub.BatchApprove.SendAsync(new BatchApproveInput + { + Value = + { + new ApproveInput + { + Symbol = SymbolForTest, + Amount = 2000L, + Spender = BasicFunctionContractAddress + }, + new ApproveInput + { + Symbol = SymbolForTest, + Amount = 1000L, + Spender = OtherBasicFunctionContractAddress + }, + new ApproveInput + { + Symbol = SymbolForTest, + Amount = 5000L, + Spender = TreasuryContractAddress + } + } + })).TransactionResult; + approveBasisResult.Status.ShouldBe(TransactionResultStatus.Mined); + + var basicAllowanceOutput = await TokenContractStub.GetAllowance.CallAsync(new GetAllowanceInput + { + Owner = DefaultAddress, + Spender = BasicFunctionContractAddress, + Symbol = SymbolForTest + }); + basicAllowanceOutput.Allowance.ShouldBe(2000L); + var otherBasicAllowanceOutput = await TokenContractStub.GetAllowance.CallAsync(new GetAllowanceInput + { + Owner = DefaultAddress, + Spender = OtherBasicFunctionContractAddress, + Symbol = SymbolForTest + }); + otherBasicAllowanceOutput.Allowance.ShouldBe(1000L); + var treasuryAllowanceOutput = await TokenContractStub.GetAllowance.CallAsync(new GetAllowanceInput + { + Owner = DefaultAddress, + Spender = TreasuryContractAddress, + Symbol = SymbolForTest + }); + treasuryAllowanceOutput.Allowance.ShouldBe(5000L); + + approveBasisResult = (await TokenContractStub.BatchApprove.SendAsync(new BatchApproveInput + { + Value = + { + new ApproveInput + { + Symbol = SymbolForTest, + Amount = 1000L, + Spender = BasicFunctionContractAddress + }, + new ApproveInput + { + Symbol = SymbolForTest, + Amount = 3000L, + Spender = BasicFunctionContractAddress + }, + new ApproveInput + { + Symbol = SymbolForTest, + Amount = 3000L, + Spender = TreasuryContractAddress + } + } + })).TransactionResult; + approveBasisResult.Status.ShouldBe(TransactionResultStatus.Mined); + basicAllowanceOutput = await TokenContractStub.GetAllowance.CallAsync(new GetAllowanceInput + { + Owner = DefaultAddress, + Spender = BasicFunctionContractAddress, + Symbol = SymbolForTest + }); + basicAllowanceOutput.Allowance.ShouldBe(3000L); + + treasuryAllowanceOutput = await TokenContractStub.GetAllowance.CallAsync(new GetAllowanceInput + { + Owner = DefaultAddress, + Spender = TreasuryContractAddress, + Symbol = SymbolForTest + }); + treasuryAllowanceOutput.Allowance.ShouldBe(3000L); + } + + [Fact] + public async Task MultiTokenContract_SetMaximumBatchApproveCount_Test() + { + var result = await TokenContractStub.SetMaxBatchApproveCount.SendWithExceptionAsync(new Int32Value + { + Value = 1 + }); + result.TransactionResult.Status.ShouldBe(TransactionResultStatus.Failed); + result.TransactionResult.Error.ShouldContain("Unauthorized behavior"); + var maximumBatchApproveCountOutput = await TokenContractStub.GetMaxBatchApproveCount.CallAsync(new Empty()); + maximumBatchApproveCountOutput.Value.ShouldBe(100); + var defaultParliament = await ParliamentContractStub.GetDefaultOrganizationAddress.CallAsync(new Empty()); + var proposalId = await CreateProposalAsync(TokenContractAddress, + defaultParliament, nameof(TokenContractStub.SetMaxBatchApproveCount), + new Int32Value + { + Value = 1 + }); + await ApproveWithMinersAsync(proposalId); + await ParliamentContractStub.Release.SendAsync(proposalId); + maximumBatchApproveCountOutput = await TokenContractStub.GetMaxBatchApproveCount.CallAsync(new Empty()); + maximumBatchApproveCountOutput.Value.ShouldBe(1); + await CreateTokenAndIssue(); + var approveBasisResult = (await TokenContractStub.BatchApprove.SendWithExceptionAsync(new BatchApproveInput + { + Value = + { + new ApproveInput + { + Symbol = SymbolForTest, + Amount = 2000L, + Spender = BasicFunctionContractAddress + }, + new ApproveInput + { + Symbol = SymbolForTest, + Amount = 1000L, + Spender = OtherBasicFunctionContractAddress + } + } + })).TransactionResult; + approveBasisResult.Status.ShouldBe(TransactionResultStatus.Failed); + approveBasisResult.Error.ShouldContain("Exceeds the max batch approve count"); + } + [Fact(DisplayName = "[MultiToken] Approve token out of owner's balance")] public async Task MultiTokenContract_Approve_OutOfAmount_Test() { @@ -999,16 +1138,21 @@ public async Task Side_Chain_Creat_Token_Test() }); await ApproveWithMinersAsync(proposalId); await ParliamentContractStub.Release.SendAsync(proposalId); - var createTokenRet = await TokenContractStub.Create.SendWithExceptionAsync(new CreateInput - { - Symbol = "ALI", - TokenName = "Ali", - Decimals = 4, - TotalSupply = 100_000, - Issuer = DefaultAddress, - Owner = DefaultAddress - }); - createTokenRet.TransactionResult.Error.ShouldContain( + + proposalId = await CreateProposalAsync(TokenContractAddress, + defaultParliament, nameof(TokenContractStub.Create), + new CreateInput + { + Symbol = "ALI", + TokenName = "Ali", + Decimals = 4, + TotalSupply = 100_000, + Issuer = DefaultAddress, + Owner = DefaultAddress + }); + await ApproveWithMinersAsync(proposalId); + var createTokenRe = await ParliamentContractStub.Release.SendWithExceptionAsync(proposalId); + createTokenRe.TransactionResult.Error.ShouldContain( "Failed to create token if side chain creator already set."); } diff --git a/test/AElf.Contracts.Profit.Tests/ProfitContractTestConstants.cs b/test/AElf.Contracts.Profit.Tests/ProfitContractTestConstants.cs index d4f937117a..bda82297c0 100644 --- a/test/AElf.Contracts.Profit.Tests/ProfitContractTestConstants.cs +++ b/test/AElf.Contracts.Profit.Tests/ProfitContractTestConstants.cs @@ -5,4 +5,5 @@ public class ProfitContractTestConstants public const string NativeTokenSymbol = "ELF"; public const long NativeTokenTotalSupply = 1_000_000_000_00000000; public const int MaximumProfitReceivingDuePeriodCount = 1024; + public const int DefaultMaximumProfitReceivingPeriodCountOfOneTime = 100; } \ No newline at end of file diff --git a/test/AElf.Contracts.Profit.Tests/ProfitTests.cs b/test/AElf.Contracts.Profit.Tests/ProfitTests.cs index 34979069da..601fd7ccf6 100644 --- a/test/AElf.Contracts.Profit.Tests/ProfitTests.cs +++ b/test/AElf.Contracts.Profit.Tests/ProfitTests.cs @@ -3,6 +3,7 @@ using AElf.Contracts.MultiToken; using AElf.CSharp.Core; using AElf.Types; +using Google.Protobuf.WellKnownTypes; using Shouldly; using Xunit; @@ -1604,13 +1605,32 @@ await creator.DistributeProfits.SendAsync(new DistributeProfitsInput Symbol = tokenSymbol, SchemeId = schemeId }); + profitAmount.Value.ShouldBe(amount); + + var allProfitAmount = await ProfitContractStub.GetAllProfitAmount.CallAsync(new GetAllProfitAmountInput + { + Beneficiary = receiver, + Symbol = tokenSymbol, + SchemeId = schemeId + }); + allProfitAmount.AllProfitAmount.ShouldBe(amount); + allProfitAmount.OneTimeClaimableProfitAmount.ShouldBe(amount); var profitMap = await ProfitContractStub.GetProfitsMap.CallAsync(new ClaimProfitsInput { SchemeId = schemeId, Beneficiary = receiver }); + profitMap.Value[tokenSymbol].ShouldBe(amount); + + var allProfitMap = await ProfitContractStub.GetAllProfitsMap.CallAsync(new GetAllProfitsMapInput + { + SchemeId = schemeId, + Beneficiary = receiver + }); + allProfitMap.AllProfitsMap[tokenSymbol].ShouldBe(amount); + allProfitMap.OneTimeClaimableProfitsMap[tokenSymbol].ShouldBe(amount); } // after claim @@ -1633,12 +1653,30 @@ await ProfitContractStub.ClaimProfits.SendAsync(new ClaimProfitsInput SchemeId = schemeId }); profitAmount.Value.ShouldBe(0); + + var allProfitAmount = await ProfitContractStub.GetAllProfitAmount.CallAsync(new GetAllProfitAmountInput + { + Beneficiary = receiver, + Symbol = tokenSymbol, + SchemeId = schemeId + }); + allProfitAmount.AllProfitAmount.ShouldBe(0); + allProfitAmount.OneTimeClaimableProfitAmount.ShouldBe(0); + var profitMap = await ProfitContractStub.GetProfitsMap.CallAsync(new ClaimProfitsInput { SchemeId = schemeId, Beneficiary = receiver }); profitMap.Value.ShouldNotContainKey(tokenSymbol); + + var allProfitMap = await ProfitContractStub.GetAllProfitsMap.CallAsync(new GetAllProfitsMapInput + { + SchemeId = schemeId, + Beneficiary = receiver + }); + allProfitMap.AllProfitsMap.ShouldNotContainKey(tokenSymbol); + allProfitMap.OneTimeClaimableProfitsMap.ShouldNotContainKey(tokenSymbol); } //second time @@ -1659,12 +1697,28 @@ await creator.DistributeProfits.SendAsync(new DistributeProfitsInput SchemeId = schemeId }); profitAmount.Value.ShouldBe(amount); + var allProfitAmount = await ProfitContractStub.GetAllProfitAmount.CallAsync(new GetAllProfitAmountInput + { + Beneficiary = receiver, + Symbol = tokenSymbol, + SchemeId = schemeId + }); + allProfitAmount.AllProfitAmount.ShouldBe(amount); + allProfitAmount.OneTimeClaimableProfitAmount.ShouldBe(amount); var profitMap = await ProfitContractStub.GetProfitsMap.CallAsync(new ClaimProfitsInput { SchemeId = schemeId, Beneficiary = receiver }); profitMap.Value[tokenSymbol].ShouldBe(amount); + + var allProfitMap = await ProfitContractStub.GetAllProfitsMap.CallAsync(new GetAllProfitsMapInput + { + SchemeId = schemeId, + Beneficiary = receiver + }); + allProfitMap.AllProfitsMap[tokenSymbol].ShouldBe(amount); + allProfitMap.OneTimeClaimableProfitsMap[tokenSymbol].ShouldBe(amount); await ProfitContractStub.ClaimProfits.SendAsync(new ClaimProfitsInput { @@ -1680,6 +1734,47 @@ await ProfitContractStub.ClaimProfits.SendAsync(new ClaimProfitsInput } } + + [Fact] + public async Task MaximumProfitReceivingPeriodCount_Test() + { + var maximumProfitReceivingPeriodCount = + await ProfitContractStub.GetMaximumProfitReceivingPeriodCount.CallAsync(new Empty()); + maximumProfitReceivingPeriodCount.Value.ShouldBe(ProfitContractTestConstants + .DefaultMaximumProfitReceivingPeriodCountOfOneTime); + var maxPeriodCount = 10; + var result = await ProfitContractStub.SetMaximumProfitReceivingPeriodCount.SendWithExceptionAsync(new Int32Value + { + Value = maxPeriodCount + }); + result.TransactionResult.Status.ShouldBe(TransactionResultStatus.Failed); + result.TransactionResult.Error.ShouldContain("No permission"); + + var defaultOrganizationAddress = + await ParliamentContractStub.GetDefaultOrganizationAddress.CallAsync(new Empty()); + var proposalId = await CreateProposalAsync(ProfitContractAddress, + defaultOrganizationAddress, nameof(ProfitContractStub.SetMaximumProfitReceivingPeriodCount), new Int32Value + { + Value = 0 + }); + await ApproveWithMinersAsync(proposalId); + result = await ParliamentContractStub.Release.SendWithExceptionAsync(proposalId); + result.TransactionResult.Status.ShouldBe(TransactionResultStatus.Failed); + result.TransactionResult.Error.ShouldContain("Invalid maximum profit receiving period count"); + + proposalId = await CreateProposalAsync(ProfitContractAddress, + defaultOrganizationAddress, nameof(ProfitContractStub.SetMaximumProfitReceivingPeriodCount), new Int32Value + { + Value = maxPeriodCount + }); + await ApproveWithMinersAsync(proposalId); + await ParliamentContractStub.Release.SendAsync(proposalId); + + maximumProfitReceivingPeriodCount = + await ProfitContractStub.GetMaximumProfitReceivingPeriodCount.CallAsync(new Empty()); + maximumProfitReceivingPeriodCount.Value.ShouldBe(maxPeriodCount); + } + private async Task ContributeProfits(Hash schemeId, long amount = 100) { await ProfitContractStub.ContributeProfits.SendAsync(new ContributeProfitsInput diff --git a/test/AElf.Kernel.FeeCalculation.Tests/Infrastructure/CalculateFunctionTest.cs b/test/AElf.Kernel.FeeCalculation.Tests/Infrastructure/CalculateFunctionTest.cs index 2e8484cd82..211d309fb0 100644 --- a/test/AElf.Kernel.FeeCalculation.Tests/Infrastructure/CalculateFunctionTest.cs +++ b/test/AElf.Kernel.FeeCalculation.Tests/Infrastructure/CalculateFunctionTest.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Linq; using AElf.Contracts.MultiToken; using AElf.CSharp.Core.Extension; @@ -65,30 +66,47 @@ public void GetChargedTransactionFees_Test() var transactionResult = new TransactionResult(); transactionResult.Logs.Add(new TransactionFeeCharged { + ChargingAddress = SampleAddress.AddressList[0], Amount = 1, Symbol = "ELF" }.ToLogEvent()); transactionResult.Logs.Add(new TransactionFeeCharged { + ChargingAddress = SampleAddress.AddressList[0], Amount = 2, Symbol = "ELF" }.ToLogEvent()); transactionResult.Logs.Add(new TransactionFeeCharged { + ChargingAddress = SampleAddress.AddressList[0], + Amount = 3, + Symbol = "USDT" + }.ToLogEvent()); + transactionResult.Logs.Add(new TransactionFeeCharged + { + ChargingAddress = SampleAddress.AddressList[0], + Amount = 4, + Symbol = "USDT" + }.ToLogEvent()); + transactionResult.Logs.Add(new TransactionFeeCharged + { + ChargingAddress = SampleAddress.AddressList[1], Amount = 3, Symbol = "TEST" }.ToLogEvent()); transactionResult.Logs.Add(new TransactionFeeCharged { + ChargingAddress = SampleAddress.AddressList[1], Amount = 4, Symbol = "TEST" }.ToLogEvent()); var feeDic = transactionResult.GetChargedTransactionFees(); feeDic.Count.ShouldBe(2); - feeDic.Keys.First().ShouldBe("ELF"); - feeDic.Values.First().ShouldBe(3); - feeDic.Keys.Last().ShouldBe("TEST"); - feeDic.Values.Last().ShouldBe(7); + feeDic.Keys.First().ShouldBe(SampleAddress.AddressList[0]); + feeDic.Values.First()["ELF"].ShouldBe(3); + feeDic.Values.First()["USDT"].ShouldBe(7); + feeDic.Keys.Last().ShouldBe(SampleAddress.AddressList[1]); + feeDic.Values.Last()["TEST"].ShouldBe(7); } [Fact] diff --git a/test/AElf.Kernel.SmartContract.ExecutionPluginForMethodFee.Tests/ExecutionPluginForMethodFeeTest.cs b/test/AElf.Kernel.SmartContract.ExecutionPluginForMethodFee.Tests/ExecutionPluginForMethodFeeTest.cs index b265f1cca9..1264e6fb14 100644 --- a/test/AElf.Kernel.SmartContract.ExecutionPluginForMethodFee.Tests/ExecutionPluginForMethodFeeTest.cs +++ b/test/AElf.Kernel.SmartContract.ExecutionPluginForMethodFee.Tests/ExecutionPluginForMethodFeeTest.cs @@ -268,14 +268,14 @@ public async Task ChargeFee_SuccessfulTest() chargingAddress.ShouldContain(dummy.Transaction.From); var transactionFeeDic = dummy.TransactionResult.GetChargedTransactionFees(); - await CheckTransactionFeesMapAsync(transactionFeeDic); + await CheckTransactionFeesMapAsync(DefaultSender,transactionFeeDic); var after = await tokenContractStub.GetBalance.CallAsync(new GetBalanceInput { Owner = DefaultSender, Symbol = "ELF" }); - after.Balance.ShouldBe(before.Balance - transactionFeeDic[before.Symbol]); + after.Balance.ShouldBe(before.Balance - transactionFeeDic[DefaultAddress][before.Symbol]); } private static List
GetChargingAddress(TransactionResult transactionResult) @@ -284,7 +284,7 @@ private static List
GetChargingAddress(TransactionResult transactionRes return relatedLogs.Select(l => TransactionFeeCharged.Parser.ParseFrom(l.Indexed[0]).ChargingAddress).ToList(); } - private async Task CheckTransactionFeesMapAsync(Dictionary transactionFeeDic) + private async Task CheckTransactionFeesMapAsync(Address chargingAddress, Dictionary> transactionFeeDic) { var chain = await _blockchainService.GetChainAsync(); var transactionFeesMap = await _totalTransactionFeesMapProvider.GetTotalTransactionFeesMapAsync(new ChainContext @@ -293,7 +293,13 @@ private async Task CheckTransactionFeesMapAsync(Dictionary transac BlockHeight = chain.BestChainHeight }); foreach (var transactionFee in transactionFeeDic) - transactionFeesMap.Value[transactionFee.Key].ShouldBe(transactionFee.Value); + { + transactionFee.Key.ShouldBe(chargingAddress); + foreach (var value in transactionFee.Value) + { + transactionFeesMap.Value[value.Key].ShouldBe(value.Value); + } + } } [Fact] @@ -324,7 +330,7 @@ await tokenContractStub.Transfer.SendAsync(new TransferInput dummy.TransactionResult.Status.ShouldBe(TransactionResultStatus.Failed); dummy.TransactionResult.Error.ShouldBe("Pre-Error: Transaction fee not enough."); var transactionFeeDic = dummy.TransactionResult.GetChargedTransactionFees(); - await CheckTransactionFeesMapAsync(transactionFeeDic); + await CheckTransactionFeesMapAsync(Accounts[1].Address,transactionFeeDic); var afterFee = (await tokenContractStub.GetBalance.CallAsync(new GetBalanceInput { @@ -332,7 +338,7 @@ await tokenContractStub.Transfer.SendAsync(new TransferInput Symbol = "ELF" })).Balance; afterFee.ShouldBe(0); - transactionFeeDic["ELF"].ShouldBe(issueAmount); + transactionFeeDic[Accounts[1].Address]["ELF"].ShouldBe(issueAmount); } [Theory] @@ -378,7 +384,7 @@ await tokenContractStub.Transfer.SendAsync(new TransferInput Symbol = chargedSymbol ?? "ELF" })).Balance; - Dictionary transactionFeeDic; + Dictionary> transactionFeeDic; var userTestContractStub = GetTester(_testContractAddress, Accounts[1].KeyPair); if (isChargingSuccessful) @@ -387,8 +393,9 @@ await tokenContractStub.Transfer.SendAsync(new TransferInput dummyResult.TransactionResult.Status.ShouldBe(TransactionResultStatus.Mined); if (chargedSymbol != null) { - dummyResult.TransactionResult.GetChargedTransactionFees().Keys.ShouldContain(chargedSymbol); - dummyResult.TransactionResult.GetChargedTransactionFees().Values.ShouldContain(chargedAmount); + dummyResult.TransactionResult.GetChargedTransactionFees().Keys.ShouldContain(Accounts[1].Address); + dummyResult.TransactionResult.GetChargedTransactionFees()[Accounts[1].Address].Keys.ShouldContain(chargedSymbol); + dummyResult.TransactionResult.GetChargedTransactionFees()[Accounts[1].Address].Values.ShouldContain(chargedAmount); } transactionFeeDic = dummyResult.TransactionResult.GetChargedTransactionFees(); @@ -399,13 +406,13 @@ await tokenContractStub.Transfer.SendAsync(new TransferInput dummyResult.TransactionResult.Status.ShouldBe(TransactionResultStatus.Failed); dummyResult.TransactionResult.Error.ShouldBe("Pre-Error: Transaction fee not enough."); if (chargedSymbol != null) - dummyResult.TransactionResult.GetChargedTransactionFees().Keys.ShouldContain(chargedSymbol); + dummyResult.TransactionResult.GetChargedTransactionFees()[Accounts[1].Address].Keys.ShouldContain(chargedSymbol); transactionFeeDic = dummyResult.TransactionResult.GetChargedTransactionFees(); } - await CheckTransactionFeesMapAsync(transactionFeeDic); + await CheckTransactionFeesMapAsync(Accounts[1].Address,transactionFeeDic); if (chargedSymbol != null) - transactionFeeDic[chargedSymbol].ShouldBe(chargedAmount); + transactionFeeDic[Accounts[1].Address][chargedSymbol].ShouldBe(chargedAmount); var finalBalance = (await tokenContractStub.GetBalance.CallAsync(new GetBalanceInput { diff --git a/test/AElf.Kernel.SmartContract.ExecutionPluginForMethodFee.Tests/ExecutionPluginForMethodFeeWithForkTest.cs b/test/AElf.Kernel.SmartContract.ExecutionPluginForMethodFee.Tests/ExecutionPluginForMethodFeeWithForkTest.cs index 6a6ffe5a73..02aebc36aa 100644 --- a/test/AElf.Kernel.SmartContract.ExecutionPluginForMethodFee.Tests/ExecutionPluginForMethodFeeWithForkTest.cs +++ b/test/AElf.Kernel.SmartContract.ExecutionPluginForMethodFee.Tests/ExecutionPluginForMethodFeeWithForkTest.cs @@ -55,8 +55,9 @@ await SetMethodFeeWithProposalAsync(new MethodFees Memo = Guid.NewGuid().ToString(), To = SampleAddress.AddressList[0] }); + var fromAddress = Address.FromPublicKey(Tester.KeyPair.PublicKey); var transactionResult = await Tester.GetTransactionResultAsync(result.Item2.GetHash()); - var targetFee = transactionResult.GetChargedTransactionFees().First().Value; + var targetFee = transactionResult.GetChargedTransactionFees()[fromAddress].First().Value; var transactionFeesMap = await GetTransactionFeesMapAsync(new ChainContext { @@ -97,7 +98,7 @@ await SetMethodFeeWithProposalAsync(new MethodFees To = SampleAddress.AddressList[0] }); transactionResult = await Tester.GetTransactionResultAsync(result.Item2.GetHash()); - var fee = transactionResult.GetChargedTransactionFees().First().Value; + var fee = transactionResult.GetChargedTransactionFees()[fromAddress].First().Value; transactionFeesMap = await GetTransactionFeesMapAsync(new ChainContext { BlockHash = result.Item1.GetHash(), BlockHeight = result.Item1.Height diff --git a/test/AElf.Kernel.SmartContract.ExecutionPluginForMethodFee.Tests/ExecutionPluginForUserContractMethodFeeTest.cs b/test/AElf.Kernel.SmartContract.ExecutionPluginForMethodFee.Tests/ExecutionPluginForUserContractMethodFeeTest.cs index 25e8daa5f4..688abbd017 100644 --- a/test/AElf.Kernel.SmartContract.ExecutionPluginForMethodFee.Tests/ExecutionPluginForUserContractMethodFeeTest.cs +++ b/test/AElf.Kernel.SmartContract.ExecutionPluginForMethodFee.Tests/ExecutionPluginForUserContractMethodFeeTest.cs @@ -75,14 +75,14 @@ public async Task ChargeUserContractFeeTest_Success() var result = await _testContractStub.TestMethod.SendAsync(new Empty()); result.TransactionResult.Status.ShouldBe(TransactionResultStatus.Mined); var transactionFeeDic = result.TransactionResult.GetChargedTransactionFees(); - await CheckTransactionFeesMapAsync(transactionFeeDic); + await CheckTransactionFeesMapAsync(DefaultAddress,transactionFeeDic); var after = await TokenContractStub.GetBalance.CallAsync(new GetBalanceInput { Owner = DefaultAddress, Symbol = "ELF" }); - after.Balance.ShouldBe(beforeBalance.Balance - transactionFeeDic[beforeBalance.Symbol]); + after.Balance.ShouldBe(beforeBalance.Balance - transactionFeeDic[DefaultAddress][beforeBalance.Symbol]); } [Fact] @@ -97,7 +97,7 @@ public async Task ChargeUserContractFeeTest_Success_BaseFeeIsFree() var result = await _testContractStub.TestMethod.SendAsync(new Empty()); result.TransactionResult.Status.ShouldBe(TransactionResultStatus.Mined); var transactionFeeDic = result.TransactionResult.GetChargedTransactionFees(); - await CheckTransactionFeesMapAsync(transactionFeeDic); + await CheckTransactionFeesMapAsync(DefaultAddress,transactionFeeDic); var after = await TokenContractStub.GetBalance.CallAsync(new GetBalanceInput { @@ -193,7 +193,7 @@ await TokenContractStub.Transfer.SendAsync(new TransferInput Symbol = chargedSymbol ?? "ELF" })).Balance; - Dictionary transactionFeeDic; + Dictionary> transactionFeeDic; var userTestContractStub = GetTester(_testContractAddress, Accounts[1].KeyPair); if (isChargingSuccessful) @@ -202,8 +202,13 @@ await TokenContractStub.Transfer.SendAsync(new TransferInput result.TransactionResult.Status.ShouldBe(TransactionResultStatus.Mined); if (chargedSymbol != null) { - result.TransactionResult.GetChargedTransactionFees().Keys.ShouldContain(chargedSymbol); - result.TransactionResult.GetChargedTransactionFees().Values.ShouldContain(chargedAmount); + var token = new Dictionary + { + [chargedSymbol] = chargedAmount + }; + result.TransactionResult.GetChargedTransactionFees().Keys.ShouldContain(Accounts[1].Address); + var fee = result.TransactionResult.GetChargedTransactionFees()[Accounts[1].Address]; + fee.ShouldContainKeyAndValue(chargedSymbol,chargedAmount); } transactionFeeDic = result.TransactionResult.GetChargedTransactionFees(); @@ -214,13 +219,21 @@ await TokenContractStub.Transfer.SendAsync(new TransferInput result.TransactionResult.Status.ShouldBe(TransactionResultStatus.Failed); result.TransactionResult.Error.ShouldBe("Pre-Error: Transaction fee not enough."); if (chargedSymbol != null) - result.TransactionResult.GetChargedTransactionFees().Keys.ShouldContain(chargedSymbol); + { + var token = new Dictionary + { + [chargedSymbol] = chargedAmount + }; + result.TransactionResult.GetChargedTransactionFees().Keys.ShouldContain(Accounts[1].Address); + var fee = result.TransactionResult.GetChargedTransactionFees()[Accounts[1].Address]; + fee.ShouldContainKeyAndValue(chargedSymbol,chargedAmount); + } transactionFeeDic = result.TransactionResult.GetChargedTransactionFees(); } - await CheckTransactionFeesMapAsync(transactionFeeDic); + await CheckTransactionFeesMapAsync(Accounts[1].Address,transactionFeeDic); if (chargedSymbol != null) - transactionFeeDic[chargedSymbol].ShouldBe(chargedAmount); + transactionFeeDic[Accounts[1].Address][chargedSymbol].ShouldBe(chargedAmount); var finalBalance = (await TokenContractStub.GetBalance.CallAsync(new GetBalanceInput { @@ -253,7 +266,7 @@ public async Task ChargeFee_SizeFeeIsFree() var result = await _testContractStub.TestMethod.SendAsync(new Empty()); result.TransactionResult.Status.ShouldBe(TransactionResultStatus.Mined); var transactionFeeDic = result.TransactionResult.GetChargedTransactionFees(); - await CheckTransactionFeesMapAsync(transactionFeeDic); + await CheckTransactionFeesMapAsync(DefaultAddress, transactionFeeDic); var after = await TokenContractStub.GetBalance.CallAsync(new GetBalanceInput { @@ -294,7 +307,7 @@ public async Task ChargeFee_SpecConfigurationFee() var result = await _testContractStub.TestMethod.SendAsync(new Empty()); result.TransactionResult.Status.ShouldBe(TransactionResultStatus.Mined); var transactionFeeDic = result.TransactionResult.GetChargedTransactionFees(); - await CheckTransactionFeesMapAsync(transactionFeeDic); + await CheckTransactionFeesMapAsync(DefaultAddress, transactionFeeDic); var after = await TokenContractStub.GetBalance.CallAsync(new GetBalanceInput { @@ -356,7 +369,7 @@ private async Task Initialize() } } - private async Task CheckTransactionFeesMapAsync(Dictionary transactionFeeDic) + private async Task CheckTransactionFeesMapAsync(Address chargingAddress, Dictionary> transactionFeeDic) { var chain = await _blockchainService.GetChainAsync(); var transactionFeesMap = await _totalTransactionFeesMapProvider.GetTotalTransactionFeesMapAsync(new ChainContext @@ -365,7 +378,14 @@ private async Task CheckTransactionFeesMapAsync(Dictionary transac BlockHeight = chain.BestChainHeight }); foreach (var transactionFee in transactionFeeDic) - transactionFeesMap.Value[transactionFee.Key].ShouldBe(transactionFee.Value); + { + transactionFee.Key.ShouldBe(chargingAddress); + foreach (var value in transactionFee.Value) + { + transactionFeesMap.Value[value.Key].ShouldBe(value.Value); + } + } + } private async Task SetUserContractFeeAsync(int amount) diff --git a/test/AElf.Kernel.SmartContract.ExecutionPluginForResourceFee.Tests/ExecutionPluginForResourceFeeTest.cs b/test/AElf.Kernel.SmartContract.ExecutionPluginForResourceFee.Tests/ExecutionPluginForResourceFeeTest.cs index 6a47875b59..08d30b7593 100644 --- a/test/AElf.Kernel.SmartContract.ExecutionPluginForResourceFee.Tests/ExecutionPluginForResourceFeeTest.cs +++ b/test/AElf.Kernel.SmartContract.ExecutionPluginForResourceFee.Tests/ExecutionPluginForResourceFeeTest.cs @@ -258,8 +258,8 @@ public async Task CompareConsumptions() write.ShouldBeGreaterThan(write1); traffic.ShouldBe(traffic1); - var consumedTokens1 = txResult1.GetConsumedResourceTokens(); - var consumedTokens2 = txResult2.GetConsumedResourceTokens(); + var consumedTokens1 = txResult1.GetConsumedResourceTokens()[DefaultSender]; + var consumedTokens2 = txResult2.GetConsumedResourceTokens()[DefaultSender]; consumedTokens1["READ"].ShouldBeGreaterThan(consumedTokens2["READ"]); consumedTokens1["WRITE"].ShouldBeGreaterThan(consumedTokens2["WRITE"]); consumedTokens1["TRAFFIC"].ShouldBe(consumedTokens2["TRAFFIC"]); diff --git a/test/AElf.WebApp.Application.Chain.Tests/BlockChainAppServiceTest.cs b/test/AElf.WebApp.Application.Chain.Tests/BlockChainAppServiceTest.cs index 7aec5b5cfd..67e9db0b83 100644 --- a/test/AElf.WebApp.Application.Chain.Tests/BlockChainAppServiceTest.cs +++ b/test/AElf.WebApp.Application.Chain.Tests/BlockChainAppServiceTest.cs @@ -1688,6 +1688,12 @@ public async Task CalculateTransactionFee_Success_Test() }; var response = await PostResponseAsObjectAsync("/api/blockChain/CalculateTransactionFee", parameters); response.Success.ShouldBe(true); + response.TransactionFees.ChargingAddress.ShouldBe(transaction.From.ToBase58()); + response.TransactionFees.Fee.First().Key.ShouldBe("ELF"); + response.TransactionFees.Fee.First().Value.ShouldBeGreaterThan(10000000L); + response.TransactionFee.First().Key.ShouldBe("ELF"); + response.TransactionFee.First().Value.ShouldBeGreaterThan(10000000L); + } [Fact]