diff --git a/contract/AElf.Contracts.MultiToken/TokenContractConstants.cs b/contract/AElf.Contracts.MultiToken/TokenContractConstants.cs
index a14a4e9496..59eae99a8c 100644
--- a/contract/AElf.Contracts.MultiToken/TokenContractConstants.cs
+++ b/contract/AElf.Contracts.MultiToken/TokenContractConstants.cs
@@ -22,4 +22,5 @@ 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 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 0159b7002c..2a477cafe7 100644
--- a/contract/AElf.Contracts.MultiToken/TokenContract_Actions.cs
+++ b/contract/AElf.Contracts.MultiToken/TokenContract_Actions.cs
@@ -254,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();
}
@@ -627,4 +648,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/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/test/AElf.Contracts.MultiToken.Tests/BVT/TokenApplicationTests.cs b/test/AElf.Contracts.MultiToken.Tests/BVT/TokenApplicationTests.cs
index 0bb4ade958..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()
{