diff --git a/contract/AElf.Contracts.MultiToken/TokenContract_Actions.cs b/contract/AElf.Contracts.MultiToken/TokenContract_Actions.cs index 71913eae4b..d4b33bc98e 100644 --- a/contract/AElf.Contracts.MultiToken/TokenContract_Actions.cs +++ b/contract/AElf.Contracts.MultiToken/TokenContract_Actions.cs @@ -411,6 +411,7 @@ public override Empty TransferToContract(TransferToContractInput input) public override Empty AdvanceResourceToken(AdvanceResourceTokenInput input) { AssertValidInputAddress(input.ContractAddress); + AssertValidSymbolAndAmount(input.ResourceTokenSymbol, input.Amount); Assert( Context.Variables.GetStringArray(TokenContractConstants.PayTxFeeSymbolListName) .Contains(input.ResourceTokenSymbol), @@ -426,6 +427,7 @@ public override Empty TakeResourceTokenBack(TakeResourceTokenBackInput input) { Assert(!string.IsNullOrWhiteSpace(input.ResourceTokenSymbol), "Invalid input resource token symbol."); AssertValidInputAddress(input.ContractAddress); + Assert(input.Amount > 0, "Invalid amount."); var advancedAmount = State.AdvancedResourceToken[input.ContractAddress][Context.Sender][input.ResourceTokenSymbol]; Assert(advancedAmount >= input.Amount, "Can't take back that more."); diff --git a/contract/AElf.Contracts.Profit/ProfitContract.cs b/contract/AElf.Contracts.Profit/ProfitContract.cs index a1f43d1af4..98d3bf1288 100644 --- a/contract/AElf.Contracts.Profit/ProfitContract.cs +++ b/contract/AElf.Contracts.Profit/ProfitContract.cs @@ -905,7 +905,7 @@ private Dictionary ProfitAllPeriods(Scheme scheme, ProfitDetail pr }); } - lastProfitPeriod = period + 1; + lastProfitPeriod = Math.Max(lastProfitPeriod, period + 1); } totalAmount = totalAmount.Add(amount); diff --git a/contract/AElf.Contracts.Vote/VoteContract.cs b/contract/AElf.Contracts.Vote/VoteContract.cs index a0d45e61d0..b29ed7a6e9 100644 --- a/contract/AElf.Contracts.Vote/VoteContract.cs +++ b/contract/AElf.Contracts.Vote/VoteContract.cs @@ -102,6 +102,17 @@ public override Empty Vote(VoteInput input) amount = votingItem.TicketCost.Mul(currentVotesCount); } + Assert(amount > 0, "Invalid amount."); + + var existingRecord = State.VotingRecords[input.VoteId]; + if (existingRecord != null) + { + Assert(input.IsChangeTarget, "VoteId already exists."); + Assert(existingRecord.IsWithdrawn, "VoteId already exists and not withdrawn."); + Assert(existingRecord.VotingItemId == input.VotingItemId, "VoteId belongs to a different voting item."); + Assert(existingRecord.Voter == input.Voter, "VoteId belongs to a different voter."); + } + var votingRecord = new VotingRecord { VotingItemId = input.VotingItemId, @@ -199,6 +210,8 @@ public override Empty Withdraw(WithdrawInput input) else Assert(votingItem.Sponsor == Context.Sender, "No permission to withdraw votes of others."); + Assert(!votingRecord.IsWithdrawn, "Vote already withdrawn."); + // Update VotingRecord. votingRecord.IsWithdrawn = true; votingRecord.WithdrawTimestamp = Context.CurrentBlockTime; diff --git a/test/AElf.Contracts.EconomicSystem.Tests/BVT/AdvanceResourceTokenTests.cs b/test/AElf.Contracts.EconomicSystem.Tests/BVT/AdvanceResourceTokenTests.cs index aefdd9b864..23e18991c3 100644 --- a/test/AElf.Contracts.EconomicSystem.Tests/BVT/AdvanceResourceTokenTests.cs +++ b/test/AElf.Contracts.EconomicSystem.Tests/BVT/AdvanceResourceTokenTests.cs @@ -66,6 +66,58 @@ await TokenContractStub.AdvanceResourceToken.SendAsync(new AdvanceResourceTokenI return contractAddress; } + [Fact] + public async Task TokenContract_AdvanceResourceToken_Negative_Amount_Test() + { + var contractAddress = await TokenContract_AdvanceResourceToken_Test(); + + await TokenContractStub.Transfer.SendAsync(new TransferInput + { + Symbol = "ELF", + Amount = 100000000, + To = OtherAddress + }); + + // Check balance of other address. + { + var balance = await OtherTokenContractStub.GetBalance.CallAsync(new GetBalanceInput + { + Owner = OtherAddress, + Symbol = ResourceTokenSymbol + }); + balance.Balance.ShouldBe(0); + } + + var executionResult = await OtherTokenContractStub.AdvanceResourceToken.SendWithExceptionAsync(new AdvanceResourceTokenInput + { + ContractAddress = contractAddress, + Amount = -Amount, + ResourceTokenSymbol = ResourceTokenSymbol + }); + + executionResult.TransactionResult.Status.ShouldBe(TransactionResultStatus.Failed); + executionResult.TransactionResult.Error.ShouldContain("Invalid amount"); + // Check balance of contract address. + { + var balance = await OtherTokenContractStub.GetBalance.CallAsync(new GetBalanceInput + { + Owner = contractAddress, + Symbol = ResourceTokenSymbol + }); + balance.Balance.ShouldBe(Amount); + } + + // Check balance of developer. + { + var balance = await OtherTokenContractStub.GetBalance.CallAsync(new GetBalanceInput + { + Owner = OtherAddress, + Symbol = ResourceTokenSymbol + }); + balance.Balance.ShouldBe(0); + } + } + [Fact] public async Task TokenContract_TakeResourceTokenBack_Test() { @@ -163,6 +215,22 @@ public async Task TokenContract_TakeResourceTokenBack_Exceed_Test() result.TransactionResult.Status.ShouldBe(TransactionResultStatus.Failed); result.TransactionResult.Error.ShouldContain("Can't take back that more."); } + + [Fact] + public async Task TokenContract_TakeResourceTokenBack_Negative_Amount_Test() + { + var contractAddress = await TokenContract_AdvanceResourceToken_Test(); + + var result = await TokenContractStub.TakeResourceTokenBack.SendWithExceptionAsync(new TakeResourceTokenBackInput + { + ContractAddress = contractAddress, + Amount = -Amount, + ResourceTokenSymbol = ResourceTokenSymbol + }); + + result.TransactionResult.Status.ShouldBe(TransactionResultStatus.Failed); + result.TransactionResult.Error.ShouldContain("Invalid amount"); + } [Fact] public async Task SetControllerForManageConnector_Test() diff --git a/test/AElf.Contracts.EconomicSystem.Tests/EconomicSystemTestBase.cs b/test/AElf.Contracts.EconomicSystem.Tests/EconomicSystemTestBase.cs index 3947cb09dc..eab73fa369 100644 --- a/test/AElf.Contracts.EconomicSystem.Tests/EconomicSystemTestBase.cs +++ b/test/AElf.Contracts.EconomicSystem.Tests/EconomicSystemTestBase.cs @@ -14,6 +14,7 @@ using AElf.Contracts.Treasury; using AElf.Contracts.Vote; using AElf.Cryptography.ECDSA; +using AElf.Types; using Volo.Abp.Threading; namespace AElf.Contracts.EconomicSystem.Tests; @@ -30,6 +31,10 @@ public class EconomicSystemTestBase : EconomicContractsTestBase internal TokenContractImplContainer.TokenContractImplStub TokenContractImplStub => GetTokenContractImplTester(BootMinerKeyPair); + protected Address OtherAddress => Address.FromPublicKey(Accounts[1].KeyPair.PublicKey); + internal TokenContractImplContainer.TokenContractImplStub OtherTokenContractStub => + GetTokenContractTester(Accounts[1].KeyPair); + internal TokenHolderContractImplContainer.TokenHolderContractImplStub TokenHolderStub => GetTokenHolderTester(BootMinerKeyPair); diff --git a/test/AElf.Contracts.Election.Tests/BVT/ElectionTests.cs b/test/AElf.Contracts.Election.Tests/BVT/ElectionTests.cs index 2ba363f220..9e4e6f8128 100644 --- a/test/AElf.Contracts.Election.Tests/BVT/ElectionTests.cs +++ b/test/AElf.Contracts.Election.Tests/BVT/ElectionTests.cs @@ -1241,6 +1241,80 @@ await ElectionContractStub.GetElectorVoteWithRecords.CallAsync( } } + [Fact] + public async Task ElectionContract_Withdraw_Succeeds_When_Delegated_VoteId_Collision_Is_Rejected_Test() + { + const int amount = 100; + const int lockTime = 7 * 60 * 60 * 24; + + var candidateKeyPair = ValidationDataCenterKeyPairs[0]; + await AnnounceElectionAsync(candidateKeyPair); + var candidatePubkey = candidateKeyPair.PublicKey.ToHex(); + + var voterKeyPair = VoterKeyPairs[0]; + var otherVoterKeyPair = VoterKeyPairs[1]; + var voterAddress = Address.FromPublicKey(voterKeyPair.PublicKey); + + var voteResult = await VoteToCandidateAsync(voterKeyPair, candidatePubkey, lockTime, amount); + voteResult.Status.ShouldBe(TransactionResultStatus.Mined); + var voteId = Hash.Parser.ParseFrom(voteResult.ReturnValue); + + var originalRecord = await VoteContractStub.GetVotingRecord.CallAsync(voteId); + originalRecord.VotingItemId.ShouldBe(MinerElectionVotingItemId); + originalRecord.Voter.ShouldBe(voterAddress); + originalRecord.Option.ShouldBe(candidatePubkey); + + var otherVoteStub = GetVoteContractTester(otherVoterKeyPair); + var otherRegisterTime = TimestampHelper.GetUtcNow(); + var otherRegisterInput = new VotingRegisterInput + { + AcceptedCurrency = ElectionContractTestConstants.NativeTokenSymbol, + IsLockToken = false, + StartTimestamp = otherRegisterTime, + EndTimestamp = otherRegisterTime.AddDays(1), + TotalSnapshotNumber = 1, + Options = { candidatePubkey } + }; + var registerResult = await otherVoteStub.Register.SendAsync(otherRegisterInput); + registerResult.TransactionResult.Status.ShouldBe(TransactionResultStatus.Mined); + + var otherRegisterInputForHash = otherRegisterInput.Clone(); + otherRegisterInputForHash.Options.Clear(); + var otherVotingItemId = HashHelper.ConcatAndCompute(HashHelper.ComputeFrom(otherRegisterInputForHash), + HashHelper.ComputeFrom(Address.FromPublicKey(otherVoterKeyPair.PublicKey))); + + var collisionResult = await otherVoteStub.Vote.SendWithExceptionAsync(new VoteInput + { + VotingItemId = otherVotingItemId, + VoteId = voteId, + Voter = voterAddress, + Option = candidatePubkey, + Amount = 1 + }); + collisionResult.TransactionResult.Status.ShouldBe(TransactionResultStatus.Failed); + collisionResult.TransactionResult.Error.ShouldContain("VoteId already exists."); + + var recordAfterRejectedCollision = await VoteContractStub.GetVotingRecord.CallAsync(voteId); + recordAfterRejectedCollision.VotingItemId.ShouldBe(MinerElectionVotingItemId); + recordAfterRejectedCollision.Voter.ShouldBe(voterAddress); + recordAfterRejectedCollision.Amount.ShouldBe(amount); + recordAfterRejectedCollision.Option.ShouldBe(candidatePubkey); + recordAfterRejectedCollision.IsWithdrawn.ShouldBeFalse(); + + BlockTimeProvider.SetBlockTime(recordAfterRejectedCollision.VoteTimestamp.AddSeconds(lockTime + 1000)); + + var withdrawResult = await WithdrawVotes(voterKeyPair, voteId); + withdrawResult.Error.ShouldBeEmpty(); + withdrawResult.Status.ShouldBe(TransactionResultStatus.Mined); + + var electorVote = await ElectionContractStub.GetElectorVoteWithAllRecords.CallAsync(new StringValue + { + Value = voterKeyPair.PublicKey.ToHex() + }); + electorVote.ActiveVotingRecords.Select(record => record.VoteId).ShouldNotContain(voteId); + electorVote.WithdrawnVotesRecords.Select(record => record.VoteId).ShouldContain(voteId); + } + [Fact] public async Task ElectionContract_GetCandidates_Test() { diff --git a/test/AElf.Contracts.Profit.Tests/ProfitTests.cs b/test/AElf.Contracts.Profit.Tests/ProfitTests.cs index 601fd7ccf6..bd776871fa 100644 --- a/test/AElf.Contracts.Profit.Tests/ProfitTests.cs +++ b/test/AElf.Contracts.Profit.Tests/ProfitTests.cs @@ -2,6 +2,8 @@ using System.Threading.Tasks; using AElf.Contracts.MultiToken; using AElf.CSharp.Core; +using AElf.CSharp.Core.Extension; +using AElf.Kernel; using AElf.Types; using Google.Protobuf.WellKnownTypes; using Shouldly; @@ -1775,6 +1777,131 @@ public async Task MaximumProfitReceivingPeriodCount_Test() maximumProfitReceivingPeriodCount.Value.ShouldBe(maxPeriodCount); } + [Fact] + public async Task ClaimProfits_OtherSymbol_Should_Not_Regress_LastProfitPeriod_Test() + { + const long shares = 1; + const long elfAmountPerPeriod = 100; + const long otherSymbolAmount = 1; + const string otherSymbol = "CPU"; + + var creator = Creators[0]; + var beneficiary1 = Normal[0]; + var beneficiary1TokenStub = GetTokenContractTester(NormalKeyPair[0]); + var beneficiaryAddress1 = Address.FromPublicKey(NormalKeyPair[0].PublicKey); + var beneficiaryAddress2 = Address.FromPublicKey(NormalKeyPair[1].PublicKey); + + var initialElfBalance = (await TokenContractStub.GetBalance.CallAsync(new GetBalanceInput + { + Owner = beneficiaryAddress1, + Symbol = ProfitContractTestConstants.NativeTokenSymbol + })).Balance; + + var schemeId = await CreateSchemeAsync(); + + await creator.AddBeneficiary.SendAsync(new AddBeneficiaryInput + { + SchemeId = schemeId, + BeneficiaryShare = new BeneficiaryShare + { + Beneficiary = beneficiaryAddress1, + Shares = shares + } + }); + + await creator.AddBeneficiary.SendAsync(new AddBeneficiaryInput + { + SchemeId = schemeId, + BeneficiaryShare = new BeneficiaryShare + { + Beneficiary = beneficiaryAddress2, + Shares = shares + } + }); + + // Make ELF appear first in ReceivedTokenSymbols, then append a short-lived symbol. + await ContributeProfits(schemeId, elfAmountPerPeriod * 2); + await CreateTestTokenAsync(otherSymbol, beneficiaryAddress1, otherSymbolAmount); + await beneficiary1TokenStub.Approve.SendAsync(new ApproveInput + { + Spender = ProfitContractAddress, + Symbol = otherSymbol, + Amount = otherSymbolAmount + }); + await beneficiary1.ContributeProfits.SendAsync(new ContributeProfitsInput + { + SchemeId = schemeId, + Symbol = otherSymbol, + Amount = otherSymbolAmount, + Period = 1 + }); + + var scheme = await creator.GetScheme.CallAsync(schemeId); + scheme.ReceivedTokenSymbols.Count.ShouldBe(2); + scheme.ReceivedTokenSymbols[0].ShouldBe(ProfitContractTestConstants.NativeTokenSymbol); + scheme.ReceivedTokenSymbols[1].ShouldBe(otherSymbol); + + await creator.DistributeProfits.SendAsync(new DistributeProfitsInput + { + SchemeId = schemeId, + Period = 1, + AmountsMap = + { + { ProfitContractTestConstants.NativeTokenSymbol, elfAmountPerPeriod } + } + }); + + await creator.DistributeProfits.SendAsync(new DistributeProfitsInput + { + SchemeId = schemeId, + Period = 2, + AmountsMap = + { + { ProfitContractTestConstants.NativeTokenSymbol, elfAmountPerPeriod } + } + }); + + await beneficiary1.ClaimProfits.SendAsync(new ClaimProfitsInput + { + SchemeId = schemeId + }); + + var detailsAfterFirstClaim = await creator.GetProfitDetails.CallAsync(new GetProfitDetailsInput + { + SchemeId = schemeId, + Beneficiary = beneficiaryAddress1 + }); + detailsAfterFirstClaim.Details.Count.ShouldBe(1); + detailsAfterFirstClaim.Details.First().LastProfitPeriod.ShouldBe(3); + + var balanceAfterFirstClaim = (await TokenContractStub.GetBalance.CallAsync(new GetBalanceInput + { + Owner = beneficiaryAddress1, + Symbol = ProfitContractTestConstants.NativeTokenSymbol + })).Balance; + balanceAfterFirstClaim.ShouldBe(initialElfBalance + elfAmountPerPeriod); + + await beneficiary1.ClaimProfits.SendAsync(new ClaimProfitsInput + { + SchemeId = schemeId + }); + + var balanceAfterSecondClaim = (await TokenContractStub.GetBalance.CallAsync(new GetBalanceInput + { + Owner = beneficiaryAddress1, + Symbol = ProfitContractTestConstants.NativeTokenSymbol + })).Balance; + balanceAfterSecondClaim.ShouldBe(initialElfBalance + elfAmountPerPeriod); + + var detailsAfterSecondClaim = await creator.GetProfitDetails.CallAsync(new GetProfitDetailsInput + { + SchemeId = schemeId, + Beneficiary = beneficiaryAddress1 + }); + detailsAfterSecondClaim.Details.Count.ShouldBe(1); + detailsAfterSecondClaim.Details.First().LastProfitPeriod.ShouldBe(3); + } + private async Task ContributeProfits(Hash schemeId, long amount = 100) { await ProfitContractStub.ContributeProfits.SendAsync(new ContributeProfitsInput @@ -1784,4 +1911,79 @@ await ProfitContractStub.ContributeProfits.SendAsync(new ContributeProfitsInput Amount = amount }); } -} \ No newline at end of file + + private async Task CreateTestTokenAsync(string symbol, Address to, long amount) + { + const string seedCollectionSymbol = "SEED-0"; + const string seedSymbol = "SEED-1"; + + var seedCollectionInfo = await TokenContractStub.GetTokenInfo.CallAsync(new GetTokenInfoInput + { + Symbol = seedCollectionSymbol + }); + if (string.IsNullOrEmpty(seedCollectionInfo.Symbol)) + { + await TokenContractStub.Create.SendAsync(new CreateInput + { + Symbol = seedCollectionSymbol, + Decimals = 0, + IsBurnable = true, + TokenName = "seed Collection", + TotalSupply = 1, + Issuer = Starter, + Owner = Starter, + ExternalInfo = new ExternalInfo() + }); + } + + await TokenContractStub.Create.SendAsync(new CreateInput + { + Symbol = seedSymbol, + Decimals = 0, + IsBurnable = true, + TokenName = "seed token 1", + TotalSupply = 1, + Issuer = Starter, + Owner = Starter, + ExternalInfo = new ExternalInfo + { + Value = + { + { "__seed_owned_symbol", symbol }, + { + "__seed_exp_time", + TimestampHelper.GetUtcNow().AddHours(1).Seconds.ToString() + } + } + }, + LockWhiteList = { TokenContractAddress } + }); + + await TokenContractStub.Issue.SendAsync(new IssueInput + { + Symbol = seedSymbol, + Amount = 1, + Memo = "Issue seed NFT.", + To = Starter + }); + + await TokenContractStub.Create.SendAsync(new CreateInput + { + Symbol = symbol, + Decimals = 2, + IsBurnable = true, + TokenName = $"{symbol} token", + TotalSupply = amount, + Issuer = Starter, + Owner = Starter + }); + + await TokenContractStub.Issue.SendAsync(new IssueInput + { + Symbol = symbol, + Amount = amount, + Memo = $"Issue {symbol}.", + To = to + }); + } +} diff --git a/test/AElf.Contracts.Vote.Tests/BVT/BasicTests.cs b/test/AElf.Contracts.Vote.Tests/BVT/BasicTests.cs index c7ca327187..8238dd54f5 100644 --- a/test/AElf.Contracts.Vote.Tests/BVT/BasicTests.cs +++ b/test/AElf.Contracts.Vote.Tests/BVT/BasicTests.cs @@ -146,6 +146,38 @@ public async Task Vote_Sender_Test() voteRet.TransactionResult.Error.ShouldContain("Sender of delegated voting event must be the Sponsor."); } + [Fact] + public async Task Vote_With_Negative_Amount_Should_Fail_Test() + { + var votingItem = await RegisterVotingItemAsync(10, 4, false, DefaultSender, 10); + var voter = Accounts[11].Address; + var voteId = HashHelper.ComputeFrom("negative-delegated-vote"); + + var voteRet = await VoteContractStub.Vote.SendWithExceptionAsync(new VoteInput + { + VotingItemId = votingItem.VotingItemId, + Voter = voter, + VoteId = voteId, + Option = votingItem.Options[1], + Amount = -100 + }); + + voteRet.TransactionResult.Status.ShouldBe(TransactionResultStatus.Failed); + voteRet.TransactionResult.Error.ShouldContain("Invalid amount."); + + var votingResult = await VoteContractStub.GetVotingResult.CallAsync(new GetVotingResultInput + { + VotingItemId = votingItem.VotingItemId, + SnapshotNumber = 1 + }); + votingResult.Results.Count.ShouldBe(0); + votingResult.VotersCount.ShouldBe(0); + votingResult.VotesAmount.ShouldBe(0); + + var votedItems = await VoteContractStub.GetVotedItems.CallAsync(voter); + votedItems.VotedItemVoteIds.Count.ShouldBe(0); + } + [Fact] public async Task Vote_Success() { @@ -280,6 +312,74 @@ public async Task VoteContract_Withdraw_Success_Test() beforeBalance.ShouldBe(afterBalance - 100); } + [Fact] + public async Task VoteContract_Delegated_Withdraw_Cannot_Be_Executed_Twice_Test() + { + var registerItem = await RegisterVotingItemAsync(100, 3, false, DefaultSender, 1); + var option = registerItem.Options[1]; + const long voteAmount = 100; + + var firstVoteId = HashHelper.ComputeFrom("delegated-vote-1"); + var secondVoteId = HashHelper.ComputeFrom("delegated-vote-2"); + var firstVoterAddress = Accounts[1].Address; + var secondVoterAddress = Accounts[2].Address; + + var result = await VoteContractStub.Vote.SendAsync(new VoteInput + { + VotingItemId = registerItem.VotingItemId, + Voter = firstVoterAddress, + VoteId = firstVoteId, + Option = option, + Amount = voteAmount + }); + await VoteContractStub.Vote.SendAsync(new VoteInput + { + VotingItemId = registerItem.VotingItemId, + Voter = secondVoterAddress, + VoteId = secondVoteId, + Option = option, + Amount = voteAmount + }); + + var votingResultBeforeWithdraw = await GetVotingResult(registerItem.VotingItemId, 1); + votingResultBeforeWithdraw.VotesAmount.ShouldBe(voteAmount * 2); + votingResultBeforeWithdraw.Results[option].ShouldBe(voteAmount * 2); + votingResultBeforeWithdraw.VotersCount.ShouldBe(2); + + var firstWithdrawResult = await Withdraw(DefaultSenderKeyPair, firstVoteId); + firstWithdrawResult.Status.ShouldBe(TransactionResultStatus.Mined); + + var votingResultAfterFirstWithdraw = await GetVotingResult(registerItem.VotingItemId, 1); + votingResultAfterFirstWithdraw.VotesAmount.ShouldBe(voteAmount); + votingResultAfterFirstWithdraw.Results[option].ShouldBe(voteAmount); + votingResultAfterFirstWithdraw.VotersCount.ShouldBe(1); + + var voteRecordAfterFirstWithdraw = await GetVotingRecord(firstVoteId); + voteRecordAfterFirstWithdraw.IsWithdrawn.ShouldBeTrue(); + voteRecordAfterFirstWithdraw.WithdrawTimestamp.ShouldNotBeNull(); + + var secondWithdrawResult = await WithdrawWithException(DefaultSenderKeyPair, firstVoteId); + secondWithdrawResult.Status.ShouldBe(TransactionResultStatus.Failed); + secondWithdrawResult.Error.ShouldContain("Vote already withdrawn."); + + var votingRecordAfterSecondWithdraw = await GetVotingRecord(firstVoteId); + votingRecordAfterSecondWithdraw.IsWithdrawn.ShouldBeTrue(); + votingRecordAfterSecondWithdraw.WithdrawTimestamp.ShouldBe(voteRecordAfterFirstWithdraw.WithdrawTimestamp); + + var firstVoterVoteIds = await GetVoteIds(Accounts[1].KeyPair, registerItem.VotingItemId); + firstVoterVoteIds.ActiveVotes.Count.ShouldBe(0); + firstVoterVoteIds.WithdrawnVotes.Count(id => id == firstVoteId).ShouldBe(1); + + var secondVoterVoteIds = await GetVoteIds(Accounts[2].KeyPair, registerItem.VotingItemId); + secondVoterVoteIds.ActiveVotes.Count.ShouldBe(1); + secondVoterVoteIds.ActiveVotes.First().ShouldBe(secondVoteId); + + var votingResultAfterSecondWithdraw = await GetVotingResult(registerItem.VotingItemId, 1); + votingResultAfterSecondWithdraw.VotesAmount.ShouldBe(voteAmount); + votingResultAfterSecondWithdraw.Results[option].ShouldBe(voteAmount); + votingResultAfterSecondWithdraw.VotersCount.ShouldBe(1); + } + [Fact] public async Task VoteContract_AddOption_Fail_Test() {