Skip to content

Commit d0980a8

Browse files
authored
Fix duplicate vote/withdraw checks, profit claim period tracking, and resource token validation (#3642)
* fix: reject non-positive amounts in resource token advance/take-back * fix: prevent VoteId overwrite and repeated withdraw * fix: prevent LastProfitPeriod from regressing across symbols * fix: clarify VoteId collision errors and stabilize time-based tests
1 parent 9f729e2 commit d0980a8

8 files changed

Lines changed: 466 additions & 2 deletions

File tree

contract/AElf.Contracts.MultiToken/TokenContract_Actions.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -411,6 +411,7 @@ public override Empty TransferToContract(TransferToContractInput input)
411411
public override Empty AdvanceResourceToken(AdvanceResourceTokenInput input)
412412
{
413413
AssertValidInputAddress(input.ContractAddress);
414+
AssertValidSymbolAndAmount(input.ResourceTokenSymbol, input.Amount);
414415
Assert(
415416
Context.Variables.GetStringArray(TokenContractConstants.PayTxFeeSymbolListName)
416417
.Contains(input.ResourceTokenSymbol),
@@ -426,6 +427,7 @@ public override Empty TakeResourceTokenBack(TakeResourceTokenBackInput input)
426427
{
427428
Assert(!string.IsNullOrWhiteSpace(input.ResourceTokenSymbol), "Invalid input resource token symbol.");
428429
AssertValidInputAddress(input.ContractAddress);
430+
Assert(input.Amount > 0, "Invalid amount.");
429431
var advancedAmount =
430432
State.AdvancedResourceToken[input.ContractAddress][Context.Sender][input.ResourceTokenSymbol];
431433
Assert(advancedAmount >= input.Amount, "Can't take back that more.");

contract/AElf.Contracts.Profit/ProfitContract.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -905,7 +905,7 @@ private Dictionary<string, long> ProfitAllPeriods(Scheme scheme, ProfitDetail pr
905905
});
906906
}
907907

908-
lastProfitPeriod = period + 1;
908+
lastProfitPeriod = Math.Max(lastProfitPeriod, period + 1);
909909
}
910910

911911
totalAmount = totalAmount.Add(amount);

contract/AElf.Contracts.Vote/VoteContract.cs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,17 @@ public override Empty Vote(VoteInput input)
102102
amount = votingItem.TicketCost.Mul(currentVotesCount);
103103
}
104104

105+
Assert(amount > 0, "Invalid amount.");
106+
107+
var existingRecord = State.VotingRecords[input.VoteId];
108+
if (existingRecord != null)
109+
{
110+
Assert(input.IsChangeTarget, "VoteId already exists.");
111+
Assert(existingRecord.IsWithdrawn, "VoteId already exists and not withdrawn.");
112+
Assert(existingRecord.VotingItemId == input.VotingItemId, "VoteId belongs to a different voting item.");
113+
Assert(existingRecord.Voter == input.Voter, "VoteId belongs to a different voter.");
114+
}
115+
105116
var votingRecord = new VotingRecord
106117
{
107118
VotingItemId = input.VotingItemId,
@@ -199,6 +210,8 @@ public override Empty Withdraw(WithdrawInput input)
199210
else
200211
Assert(votingItem.Sponsor == Context.Sender, "No permission to withdraw votes of others.");
201212

213+
Assert(!votingRecord.IsWithdrawn, "Vote already withdrawn.");
214+
202215
// Update VotingRecord.
203216
votingRecord.IsWithdrawn = true;
204217
votingRecord.WithdrawTimestamp = Context.CurrentBlockTime;

test/AElf.Contracts.EconomicSystem.Tests/BVT/AdvanceResourceTokenTests.cs

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,58 @@ await TokenContractStub.AdvanceResourceToken.SendAsync(new AdvanceResourceTokenI
6666
return contractAddress;
6767
}
6868

69+
[Fact]
70+
public async Task TokenContract_AdvanceResourceToken_Negative_Amount_Test()
71+
{
72+
var contractAddress = await TokenContract_AdvanceResourceToken_Test();
73+
74+
await TokenContractStub.Transfer.SendAsync(new TransferInput
75+
{
76+
Symbol = "ELF",
77+
Amount = 100000000,
78+
To = OtherAddress
79+
});
80+
81+
// Check balance of other address.
82+
{
83+
var balance = await OtherTokenContractStub.GetBalance.CallAsync(new GetBalanceInput
84+
{
85+
Owner = OtherAddress,
86+
Symbol = ResourceTokenSymbol
87+
});
88+
balance.Balance.ShouldBe(0);
89+
}
90+
91+
var executionResult = await OtherTokenContractStub.AdvanceResourceToken.SendWithExceptionAsync(new AdvanceResourceTokenInput
92+
{
93+
ContractAddress = contractAddress,
94+
Amount = -Amount,
95+
ResourceTokenSymbol = ResourceTokenSymbol
96+
});
97+
98+
executionResult.TransactionResult.Status.ShouldBe(TransactionResultStatus.Failed);
99+
executionResult.TransactionResult.Error.ShouldContain("Invalid amount");
100+
// Check balance of contract address.
101+
{
102+
var balance = await OtherTokenContractStub.GetBalance.CallAsync(new GetBalanceInput
103+
{
104+
Owner = contractAddress,
105+
Symbol = ResourceTokenSymbol
106+
});
107+
balance.Balance.ShouldBe(Amount);
108+
}
109+
110+
// Check balance of developer.
111+
{
112+
var balance = await OtherTokenContractStub.GetBalance.CallAsync(new GetBalanceInput
113+
{
114+
Owner = OtherAddress,
115+
Symbol = ResourceTokenSymbol
116+
});
117+
balance.Balance.ShouldBe(0);
118+
}
119+
}
120+
69121
[Fact]
70122
public async Task TokenContract_TakeResourceTokenBack_Test()
71123
{
@@ -163,6 +215,22 @@ public async Task TokenContract_TakeResourceTokenBack_Exceed_Test()
163215
result.TransactionResult.Status.ShouldBe(TransactionResultStatus.Failed);
164216
result.TransactionResult.Error.ShouldContain("Can't take back that more.");
165217
}
218+
219+
[Fact]
220+
public async Task TokenContract_TakeResourceTokenBack_Negative_Amount_Test()
221+
{
222+
var contractAddress = await TokenContract_AdvanceResourceToken_Test();
223+
224+
var result = await TokenContractStub.TakeResourceTokenBack.SendWithExceptionAsync(new TakeResourceTokenBackInput
225+
{
226+
ContractAddress = contractAddress,
227+
Amount = -Amount,
228+
ResourceTokenSymbol = ResourceTokenSymbol
229+
});
230+
231+
result.TransactionResult.Status.ShouldBe(TransactionResultStatus.Failed);
232+
result.TransactionResult.Error.ShouldContain("Invalid amount");
233+
}
166234

167235
[Fact]
168236
public async Task SetControllerForManageConnector_Test()

test/AElf.Contracts.EconomicSystem.Tests/EconomicSystemTestBase.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
using AElf.Contracts.Treasury;
1515
using AElf.Contracts.Vote;
1616
using AElf.Cryptography.ECDSA;
17+
using AElf.Types;
1718
using Volo.Abp.Threading;
1819

1920
namespace AElf.Contracts.EconomicSystem.Tests;
@@ -30,6 +31,10 @@ public class EconomicSystemTestBase : EconomicContractsTestBase
3031
internal TokenContractImplContainer.TokenContractImplStub TokenContractImplStub =>
3132
GetTokenContractImplTester(BootMinerKeyPair);
3233

34+
protected Address OtherAddress => Address.FromPublicKey(Accounts[1].KeyPair.PublicKey);
35+
internal TokenContractImplContainer.TokenContractImplStub OtherTokenContractStub =>
36+
GetTokenContractTester(Accounts[1].KeyPair);
37+
3338
internal TokenHolderContractImplContainer.TokenHolderContractImplStub TokenHolderStub =>
3439
GetTokenHolderTester(BootMinerKeyPair);
3540

test/AElf.Contracts.Election.Tests/BVT/ElectionTests.cs

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1241,6 +1241,80 @@ await ElectionContractStub.GetElectorVoteWithRecords.CallAsync(
12411241
}
12421242
}
12431243

1244+
[Fact]
1245+
public async Task ElectionContract_Withdraw_Succeeds_When_Delegated_VoteId_Collision_Is_Rejected_Test()
1246+
{
1247+
const int amount = 100;
1248+
const int lockTime = 7 * 60 * 60 * 24;
1249+
1250+
var candidateKeyPair = ValidationDataCenterKeyPairs[0];
1251+
await AnnounceElectionAsync(candidateKeyPair);
1252+
var candidatePubkey = candidateKeyPair.PublicKey.ToHex();
1253+
1254+
var voterKeyPair = VoterKeyPairs[0];
1255+
var otherVoterKeyPair = VoterKeyPairs[1];
1256+
var voterAddress = Address.FromPublicKey(voterKeyPair.PublicKey);
1257+
1258+
var voteResult = await VoteToCandidateAsync(voterKeyPair, candidatePubkey, lockTime, amount);
1259+
voteResult.Status.ShouldBe(TransactionResultStatus.Mined);
1260+
var voteId = Hash.Parser.ParseFrom(voteResult.ReturnValue);
1261+
1262+
var originalRecord = await VoteContractStub.GetVotingRecord.CallAsync(voteId);
1263+
originalRecord.VotingItemId.ShouldBe(MinerElectionVotingItemId);
1264+
originalRecord.Voter.ShouldBe(voterAddress);
1265+
originalRecord.Option.ShouldBe(candidatePubkey);
1266+
1267+
var otherVoteStub = GetVoteContractTester(otherVoterKeyPair);
1268+
var otherRegisterTime = TimestampHelper.GetUtcNow();
1269+
var otherRegisterInput = new VotingRegisterInput
1270+
{
1271+
AcceptedCurrency = ElectionContractTestConstants.NativeTokenSymbol,
1272+
IsLockToken = false,
1273+
StartTimestamp = otherRegisterTime,
1274+
EndTimestamp = otherRegisterTime.AddDays(1),
1275+
TotalSnapshotNumber = 1,
1276+
Options = { candidatePubkey }
1277+
};
1278+
var registerResult = await otherVoteStub.Register.SendAsync(otherRegisterInput);
1279+
registerResult.TransactionResult.Status.ShouldBe(TransactionResultStatus.Mined);
1280+
1281+
var otherRegisterInputForHash = otherRegisterInput.Clone();
1282+
otherRegisterInputForHash.Options.Clear();
1283+
var otherVotingItemId = HashHelper.ConcatAndCompute(HashHelper.ComputeFrom(otherRegisterInputForHash),
1284+
HashHelper.ComputeFrom(Address.FromPublicKey(otherVoterKeyPair.PublicKey)));
1285+
1286+
var collisionResult = await otherVoteStub.Vote.SendWithExceptionAsync(new VoteInput
1287+
{
1288+
VotingItemId = otherVotingItemId,
1289+
VoteId = voteId,
1290+
Voter = voterAddress,
1291+
Option = candidatePubkey,
1292+
Amount = 1
1293+
});
1294+
collisionResult.TransactionResult.Status.ShouldBe(TransactionResultStatus.Failed);
1295+
collisionResult.TransactionResult.Error.ShouldContain("VoteId already exists.");
1296+
1297+
var recordAfterRejectedCollision = await VoteContractStub.GetVotingRecord.CallAsync(voteId);
1298+
recordAfterRejectedCollision.VotingItemId.ShouldBe(MinerElectionVotingItemId);
1299+
recordAfterRejectedCollision.Voter.ShouldBe(voterAddress);
1300+
recordAfterRejectedCollision.Amount.ShouldBe(amount);
1301+
recordAfterRejectedCollision.Option.ShouldBe(candidatePubkey);
1302+
recordAfterRejectedCollision.IsWithdrawn.ShouldBeFalse();
1303+
1304+
BlockTimeProvider.SetBlockTime(recordAfterRejectedCollision.VoteTimestamp.AddSeconds(lockTime + 1000));
1305+
1306+
var withdrawResult = await WithdrawVotes(voterKeyPair, voteId);
1307+
withdrawResult.Error.ShouldBeEmpty();
1308+
withdrawResult.Status.ShouldBe(TransactionResultStatus.Mined);
1309+
1310+
var electorVote = await ElectionContractStub.GetElectorVoteWithAllRecords.CallAsync(new StringValue
1311+
{
1312+
Value = voterKeyPair.PublicKey.ToHex()
1313+
});
1314+
electorVote.ActiveVotingRecords.Select(record => record.VoteId).ShouldNotContain(voteId);
1315+
electorVote.WithdrawnVotesRecords.Select(record => record.VoteId).ShouldContain(voteId);
1316+
}
1317+
12441318
[Fact]
12451319
public async Task ElectionContract_GetCandidates_Test()
12461320
{

0 commit comments

Comments
 (0)