Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
2 changes: 2 additions & 0 deletions contract/AElf.Contracts.MultiToken/TokenContractState.cs
Original file line number Diff line number Diff line change
Expand Up @@ -65,4 +65,6 @@ public partial class TokenContractState : ContractState
public SingletonState<Address> VoteContractAddress { get; set; }

public SingletonState<bool> TokenIssuerAndOwnerModificationDisabled { get; set; }

public SingletonState<int> MaxBatchApproveCount { get; set; }
}
52 changes: 48 additions & 4 deletions contract/AElf.Contracts.MultiToken/TokenContract_Actions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}

Expand Down Expand Up @@ -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;
}
}
8 changes: 7 additions & 1 deletion protobuf/token_contract.proto
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
}
Expand Down Expand Up @@ -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.
Expand Down
9 changes: 9 additions & 0 deletions protobuf/token_contract_impl.proto
Original file line number Diff line number Diff line change
Expand Up @@ -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){
}
Expand Down Expand Up @@ -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 {
Expand Down
139 changes: 139 additions & 0 deletions test/AElf.Contracts.MultiToken.Tests/BVT/TokenApplicationTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
{
Expand Down