diff --git a/src/Neo/SmartContract/Native/NeoToken.cs b/src/Neo/SmartContract/Native/NeoToken.cs index b24573ad84..74fe70db37 100644 --- a/src/Neo/SmartContract/Native/NeoToken.cs +++ b/src/Neo/SmartContract/Native/NeoToken.cs @@ -10,6 +10,11 @@ #pragma warning disable IDE0051 +using System; +using System.Buffers.Binary; +using System.Collections.Generic; +using System.Linq; +using System.Numerics; using Neo.Cryptography.ECC; using Neo.IO; using Neo.Persistence; @@ -17,11 +22,6 @@ using Neo.SmartContract.Manifest; using Neo.VM; using Neo.VM.Types; -using System; -using System.Buffers.Binary; -using System.Collections.Generic; -using System.Linq; -using System.Numerics; namespace Neo.SmartContract.Native { @@ -149,9 +149,14 @@ private GasDistribution DistributeGas(ApplicationEngine engine, UInt160 account, // PersistingBlock is null when running under the debugger if (engine.PersistingBlock is null) return null; - BigInteger gas = CalculateBonus(engine.Snapshot, state.VoteTo, state.Balance, state.BalanceHeight, engine.PersistingBlock.Index); + BigInteger gas = CalculateBonus(engine.Snapshot, state, engine.PersistingBlock.Index); state.BalanceHeight = engine.PersistingBlock.Index; - + if (state.VoteTo is not null) + { + var keyLastest = CreateStorageKey(Prefix_VoterRewardPerCommittee).Add(state.VoteTo); + var latestGasPerVote = engine.Snapshot.TryGet(keyLastest) ?? BigInteger.Zero; + state.LastGasPerVote = latestGasPerVote; + } if (gas == 0) return null; return new GasDistribution { @@ -160,24 +165,22 @@ private GasDistribution DistributeGas(ApplicationEngine engine, UInt160 account, }; } - private BigInteger CalculateBonus(DataCache snapshot, ECPoint vote, BigInteger value, uint start, uint end) + private BigInteger CalculateBonus(DataCache snapshot, NeoAccountState state, uint end) { - if (value.IsZero || start >= end) return BigInteger.Zero; - if (value.Sign < 0) throw new ArgumentOutOfRangeException(nameof(value)); - - BigInteger neoHolderReward = CalculateNeoHolderReward(snapshot, value, start, end); - if (vote is null) return neoHolderReward; + if (state.Balance.IsZero) return BigInteger.Zero; + if (state.Balance.Sign < 0) throw new ArgumentOutOfRangeException(nameof(state.Balance)); - byte[] border = CreateStorageKey(Prefix_VoterRewardPerCommittee).Add(vote).ToArray(); - byte[] keyStart = CreateStorageKey(Prefix_VoterRewardPerCommittee).Add(vote).AddBigEndian(start).ToArray(); - (_, var item) = snapshot.FindRange(keyStart, border, SeekDirection.Backward).FirstOrDefault(); - BigInteger startRewardPerNeo = item ?? BigInteger.Zero; + var expectEnd = Ledger.CurrentIndex(snapshot) + 1; + if (expectEnd != end) throw new ArgumentOutOfRangeException(nameof(end)); + if (state.BalanceHeight >= end) return BigInteger.Zero; + BigInteger neoHolderReward = CalculateNeoHolderReward(snapshot, state.Balance, state.BalanceHeight, end); + if (state.VoteTo is null) return neoHolderReward; - byte[] keyEnd = CreateStorageKey(Prefix_VoterRewardPerCommittee).Add(vote).AddBigEndian(end).ToArray(); - (_, item) = snapshot.FindRange(keyEnd, border, SeekDirection.Backward).FirstOrDefault(); - BigInteger endRewardPerNeo = item ?? BigInteger.Zero; + var keyLastest = CreateStorageKey(Prefix_VoterRewardPerCommittee).Add(state.VoteTo); + var latestGasPerVote = snapshot.TryGet(keyLastest) ?? BigInteger.Zero; + var voteReward = state.Balance * (latestGasPerVote - state.LastGasPerVote) / 100000000L; - return neoHolderReward + value * (endRewardPerNeo - startRewardPerNeo) / 100000000L; + return neoHolderReward + voteReward; } private BigInteger CalculateNeoHolderReward(DataCache snapshot, BigInteger value, uint start, uint end) @@ -203,8 +206,7 @@ private void CheckCandidate(DataCache snapshot, ECPoint pubkey, CandidateState c { if (!candidate.Registered && candidate.Votes.IsZero) { - foreach (var (rewardKey, _) in snapshot.Find(CreateStorageKey(Prefix_VoterRewardPerCommittee).Add(pubkey).ToArray()).ToArray()) - snapshot.Delete(rewardKey); + snapshot.Delete(CreateStorageKey(Prefix_VoterRewardPerCommittee).Add(pubkey)); snapshot.Delete(CreateStorageKey(Prefix_Candidate).Add(pubkey)); } } @@ -260,16 +262,14 @@ internal override async ContractTask PostPersist(ApplicationEngine engine) BigInteger voterRewardOfEachCommittee = gasPerBlock * VoterRewardRatio * 100000000L * m / (m + n) / 100; // Zoom in 100000000 times, and the final calculation should be divided 100000000L for (index = 0; index < committee.Count; index++) { - var member = committee[index]; + var (PublicKey, Votes) = committee[index]; var factor = index < n ? 2 : 1; // The `voter` rewards of validator will double than other committee's - if (member.Votes > 0) + if (Votes > 0) { - BigInteger voterSumRewardPerNEO = factor * voterRewardOfEachCommittee / member.Votes; - StorageKey voterRewardKey = CreateStorageKey(Prefix_VoterRewardPerCommittee).Add(member.PublicKey).AddBigEndian(engine.PersistingBlock.Index + 1); - byte[] border = CreateStorageKey(Prefix_VoterRewardPerCommittee).Add(member.PublicKey).ToArray(); - (_, var item) = engine.Snapshot.FindRange(voterRewardKey.ToArray(), border, SeekDirection.Backward).FirstOrDefault(); - voterSumRewardPerNEO += (item ?? BigInteger.Zero); - engine.Snapshot.Add(voterRewardKey, new StorageItem(voterSumRewardPerNEO)); + BigInteger voterSumRewardPerNEO = factor * voterRewardOfEachCommittee / Votes; + StorageKey voterRewardKey = CreateStorageKey(Prefix_VoterRewardPerCommittee).Add(PublicKey); + StorageItem lastRewardPerNeo = engine.Snapshot.GetAndChange(voterRewardKey, () => new StorageItem(BigInteger.Zero)); + lastRewardPerNeo.Add(voterSumRewardPerNEO); } } } @@ -339,7 +339,7 @@ public BigInteger UnclaimedGas(DataCache snapshot, UInt160 account, uint end) StorageItem storage = snapshot.TryGet(CreateStorageKey(Prefix_Account).Add(account)); if (storage is null) return BigInteger.Zero; NeoAccountState state = storage.GetInteroperable(); - return CalculateBonus(snapshot, state.VoteTo, state.Balance, state.BalanceHeight, end); + return CalculateBonus(snapshot, state, end); } [ContractMethod(RequiredCallFlags = CallFlags.States)] @@ -406,8 +406,15 @@ private async ContractTask Vote(ApplicationEngine engine, UInt160 account, state_validator.Votes -= state_account.Balance; CheckCandidate(engine.Snapshot, state_account.VoteTo, state_validator); } + if (voteTo != null && voteTo != state_account.VoteTo) + { + StorageKey voterRewardKey = CreateStorageKey(Prefix_VoterRewardPerCommittee).Add(voteTo); + var latestGasPerVote = engine.Snapshot.TryGet(voterRewardKey) ?? BigInteger.Zero; + state_account.LastGasPerVote = latestGasPerVote; + } ECPoint from = state_account.VoteTo; state_account.VoteTo = voteTo; + if (validator_new != null) { validator_new.Votes += state_account.Balance; @@ -572,12 +579,15 @@ public class NeoAccountState : AccountState /// public ECPoint VoteTo; + public BigInteger LastGasPerVote; + public override void FromStackItem(StackItem stackItem) { base.FromStackItem(stackItem); Struct @struct = (Struct)stackItem; BalanceHeight = (uint)@struct[1].GetInteger(); VoteTo = @struct[2].IsNull ? null : ECPoint.DecodePoint(@struct[2].GetSpan(), ECCurve.Secp256r1); + LastGasPerVote = @struct[3].GetInteger(); } public override StackItem ToStackItem(ReferenceCounter referenceCounter) @@ -585,6 +595,7 @@ public override StackItem ToStackItem(ReferenceCounter referenceCounter) Struct @struct = (Struct)base.ToStackItem(referenceCounter); @struct.Add(BalanceHeight); @struct.Add(VoteTo?.ToArray() ?? StackItem.Null); + @struct.Add(LastGasPerVote); return @struct; } } diff --git a/tests/Neo.UnitTests/SmartContract/Native/UT_GasToken.cs b/tests/Neo.UnitTests/SmartContract/Native/UT_GasToken.cs index 8e110680ca..997c4ce335 100644 --- a/tests/Neo.UnitTests/SmartContract/Native/UT_GasToken.cs +++ b/tests/Neo.UnitTests/SmartContract/Native/UT_GasToken.cs @@ -1,3 +1,7 @@ +using System; +using System.Linq; +using System.Numerics; +using System.Threading.Tasks; using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.IO; @@ -6,10 +10,6 @@ using Neo.SmartContract; using Neo.SmartContract.Native; using Neo.UnitTests.Extensions; -using System; -using System.Linq; -using System.Numerics; -using System.Threading.Tasks; namespace Neo.UnitTests.SmartContract.Native { @@ -42,10 +42,12 @@ public async Task Check_BalanceOfTransferAndBurn() var persistingBlock = new Block { Header = new Header { Index = 1000 } }; byte[] from = Contract.GetBFTAddress(ProtocolSettings.Default.StandbyValidators).ToArray(); byte[] to = new byte[20]; - var keyCount = snapshot.GetChangeSet().Count(); var supply = NativeContract.GAS.TotalSupply(snapshot); supply.Should().Be(5200000050000000); // 3000000000000000 + 50000000 (neo holder reward) + var storageKey = new KeyBuilder(NativeContract.Ledger.Id, 12); + snapshot.Add(storageKey, new StorageItem(new HashIndexState { Hash = UInt256.Zero, Index = persistingBlock.Index - 1 })); + var keyCount = snapshot.GetChangeSet().Count(); // Check unclaim var unclaim = UT_NeoToken.Check_UnclaimedGas(snapshot, from, persistingBlock); @@ -111,10 +113,9 @@ await Assert.ThrowsExceptionAsync(async () => engine.Snapshot.GetChangeSet().Count().Should().Be(2); // Burn all - await NativeContract.GAS.Burn(engine, new UInt160(to), new BigInteger(5200049999999999)); - (keyCount - 1).Should().Be(engine.Snapshot.GetChangeSet().Count()); + (keyCount - 2).Should().Be(engine.Snapshot.GetChangeSet().Count()); // Bad inputs diff --git a/tests/Neo.UnitTests/SmartContract/Native/UT_NeoToken.cs b/tests/Neo.UnitTests/SmartContract/Native/UT_NeoToken.cs index 76f68b5674..ee1ee4753e 100644 --- a/tests/Neo.UnitTests/SmartContract/Native/UT_NeoToken.cs +++ b/tests/Neo.UnitTests/SmartContract/Native/UT_NeoToken.cs @@ -1,3 +1,6 @@ +using System; +using System.Linq; +using System.Numerics; using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.Cryptography.ECC; @@ -9,9 +12,6 @@ using Neo.UnitTests.Extensions; using Neo.VM; using Neo.Wallets; -using System; -using System.Linq; -using System.Numerics; using static Neo.SmartContract.Native.NeoToken; namespace Neo.UnitTests.SmartContract.Native @@ -48,6 +48,9 @@ public void Check_Vote() var snapshot = _snapshot.CreateSnapshot(); var persistingBlock = new Block { Header = new Header { Index = 1000 } }; + var storageKey = new KeyBuilder(NativeContract.Ledger.Id, 12); + snapshot.Add(storageKey, new StorageItem(new HashIndexState { Hash = UInt256.Zero, Index = persistingBlock.Index - 1 })); + byte[] from = Contract.GetBFTAddress(ProtocolSettings.Default.StandbyValidators).ToArray(); // No signature @@ -103,6 +106,9 @@ public void Check_Vote_Sameaccounts() var snapshot = _snapshot.CreateSnapshot(); var persistingBlock = new Block { Header = new Header { Index = 1000 } }; + var storageKey = new KeyBuilder(NativeContract.Ledger.Id, 12); + snapshot.Add(storageKey, new StorageItem(new HashIndexState { Hash = UInt256.Zero, Index = persistingBlock.Index - 1 })); + byte[] from = Contract.GetBFTAddress(ProtocolSettings.Default.StandbyValidators).ToArray(); var accountState = snapshot.TryGet(CreateStorageKey(20, from)).GetInteroperable(); accountState.Balance = 100; @@ -132,6 +138,8 @@ public void Check_Vote_ChangeVote() { var snapshot = _snapshot.CreateSnapshot(); var persistingBlock = new Block { Header = new Header { Index = 1000 } }; + var storageKey = new KeyBuilder(NativeContract.Ledger.Id, 12); + snapshot.Add(storageKey, new StorageItem(new HashIndexState { Hash = UInt256.Zero, Index = persistingBlock.Index - 1 })); //from vote to G byte[] from = ProtocolSettings.Default.StandbyValidators[0].ToArray(); var from_Account = Contract.CreateSignatureContract(ProtocolSettings.Default.StandbyValidators[0]).ScriptHash.ToArray(); @@ -165,7 +173,8 @@ public void Check_Vote_VoteToNull() { var snapshot = _snapshot.CreateSnapshot(); var persistingBlock = new Block { Header = new Header { Index = 1000 } }; - + var storageKey = new KeyBuilder(NativeContract.Ledger.Id, 12); + snapshot.Add(storageKey, new StorageItem(new HashIndexState { Hash = UInt256.Zero, Index = persistingBlock.Index - 1 })); byte[] from = ProtocolSettings.Default.StandbyValidators[0].ToArray(); var from_Account = Contract.CreateSignatureContract(ProtocolSettings.Default.StandbyValidators[0]).ScriptHash.ToArray(); snapshot.Add(CreateStorageKey(20, from_Account), new StorageItem(new NeoAccountState())); @@ -199,6 +208,9 @@ public void Check_UnclaimedGas() var snapshot = _snapshot.CreateSnapshot(); var persistingBlock = new Block { Header = new Header { Index = 1000 } }; + var storageKey = new KeyBuilder(NativeContract.Ledger.Id, 12); + snapshot.Add(storageKey, new StorageItem(new HashIndexState { Hash = UInt256.Zero, Index = persistingBlock.Index - 1 })); + byte[] from = Contract.GetBFTAddress(ProtocolSettings.Default.StandbyValidators).ToArray(); var unclaim = Check_UnclaimedGas(snapshot, from, persistingBlock); @@ -242,7 +254,7 @@ public void Check_RegisterValidator() public void Check_UnregisterCandidate() { var snapshot = _snapshot.CreateSnapshot(); - + _persistingBlock.Header.Index = 1; var keyCount = snapshot.GetChangeSet().Count(); var point = ProtocolSettings.Default.StandbyValidators[0].EncodePoint(true); @@ -306,7 +318,7 @@ public void Check_GetCommittee() var keyCount = snapshot.GetChangeSet().Count(); var point = ProtocolSettings.Default.StandbyValidators[0].EncodePoint(true); var persistingBlock = _persistingBlock; - + persistingBlock.Header.Index = 1; //register with votes with 20000000 var G_Account = Contract.CreateSignatureContract(ECCurve.Secp256r1.G).ScriptHash.ToArray(); snapshot.Add(CreateStorageKey(20, G_Account), new StorageItem(new NeoAccountState())); @@ -368,6 +380,8 @@ public void Check_Transfer() byte[] from = Contract.GetBFTAddress(ProtocolSettings.Default.StandbyValidators).ToArray(); byte[] to = new byte[20]; + var storageKey = new KeyBuilder(NativeContract.Ledger.Id, 12); + snapshot.Add(storageKey, new StorageItem(new HashIndexState { Hash = UInt256.Zero, Index = persistingBlock.Index - 1 })); var keyCount = snapshot.GetChangeSet().Count(); // Check unclaim @@ -508,6 +522,11 @@ public void TestCalculateBonus() { Balance = 100 })); + + var storageKey = new KeyBuilder(NativeContract.Ledger.Id, 12); + var item = snapshot.GetAndChange(storageKey).GetInteroperable(); + item.Index = 99; + NativeContract.NEO.UnclaimedGas(snapshot, UInt160.Zero, 100).Should().Be(new BigInteger(0.5 * 100 * 100)); snapshot.Delete(key); @@ -591,7 +610,7 @@ public void TestCheckCandidate() var point = committee[0].EncodePoint(true); // Prepare Prefix_VoterRewardPerCommittee - var storageKey = new KeyBuilder(NativeContract.NEO.Id, 23).Add(committee[0]).AddBigEndian(20); + var storageKey = new KeyBuilder(NativeContract.NEO.Id, 23).Add(committee[0]); snapshot.Add(storageKey, new StorageItem(new BigInteger(1000))); // Prepare Candidate @@ -728,7 +747,7 @@ public void TestEconomicParameter() NeoAccountState state = storage.GetInteroperable(); state.Balance = 1000; state.BalanceHeight = 0; - height.Index = 0; // Fake Height=0 + height.Index = persistingBlock.Index + 1; NativeContract.NEO.UnclaimedGas(snapshot, UInt160.Zero, persistingBlock.Index + 2).Should().Be(6500); } @@ -776,7 +795,7 @@ public void TestClaimGas() var accountB = committee[ProtocolSettings.Default.CommitteeMembersCount - 1]; NativeContract.NEO.BalanceOf(snapshot, Contract.CreateSignatureContract(accountA).ScriptHash).Should().Be(0); - StorageItem storageItem = snapshot.TryGet(new KeyBuilder(NativeContract.NEO.Id, 23).Add(accountA).AddBigEndian(1)); + StorageItem storageItem = snapshot.TryGet(new KeyBuilder(NativeContract.NEO.Id, 23).Add(accountA)); ((BigInteger)storageItem).Should().Be(30000000000); snapshot.TryGet(new KeyBuilder(NativeContract.NEO.Id, 23).Add(accountB).AddBigEndian(uint.MaxValue - 1)).Should().BeNull(); @@ -799,7 +818,7 @@ public void TestClaimGas() NativeContract.NEO.BalanceOf(snapshot, Contract.CreateSignatureContract(committee[1]).ScriptHash).Should().Be(0); - storageItem = snapshot.TryGet(new KeyBuilder(NativeContract.NEO.Id, 23).Add(committee[1]).AddBigEndian(1)); + storageItem = snapshot.TryGet(new KeyBuilder(NativeContract.NEO.Id, 23).Add(committee[1])); ((BigInteger)storageItem).Should().Be(30000000000); // Next block @@ -821,7 +840,7 @@ public void TestClaimGas() accountA = ProtocolSettings.Default.StandbyCommittee.OrderBy(p => p).ToArray()[2]; NativeContract.NEO.BalanceOf(snapshot, Contract.CreateSignatureContract(committee[2]).ScriptHash).Should().Be(0); - storageItem = snapshot.TryGet(new KeyBuilder(NativeContract.NEO.Id, 23).Add(committee[2]).AddBigEndian(22)); + storageItem = snapshot.TryGet(new KeyBuilder(NativeContract.NEO.Id, 23).Add(committee[2])); ((BigInteger)storageItem).Should().Be(30000000000 * 2); // Claim GAS @@ -831,9 +850,12 @@ public void TestClaimGas() { BalanceHeight = 3, Balance = 200 * 10000 - 2 * 100, - VoteTo = committee[2] + VoteTo = committee[2], + LastGasPerVote = 30000000000, })); NativeContract.NEO.BalanceOf(snapshot, account).Should().Be(1999800); + var storageKey = new KeyBuilder(NativeContract.Ledger.Id, 12); + snapshot.GetAndChange(storageKey).GetInteroperable().Index = 29 + 2; BigInteger value = NativeContract.NEO.UnclaimedGas(snapshot, account, 29 + 3); value.Should().Be(1999800 * 30000000000 / 100000000L + (1999800L * 10 * 5 * 29 / 100)); } @@ -854,6 +876,7 @@ public void TestVote() UInt160 account = UInt160.Parse("01ff00ff00ff00ff00ff00ff00ff00ff00ff00a4"); StorageKey keyAccount = CreateStorageKey(20, account.ToArray()); StorageKey keyValidator = CreateStorageKey(33, ECCurve.Secp256r1.G.ToArray()); + _persistingBlock.Header.Index = 1; var ret = Check_Vote(snapshot, account.ToArray(), ECCurve.Secp256r1.G.ToArray(), false, _persistingBlock); ret.State.Should().BeTrue(); ret.Result.Should().BeFalse(); @@ -888,7 +911,8 @@ public void TestVote() internal (bool State, bool Result) Transfer4TesingOnBalanceChanging(BigInteger amount, bool addVotes) { var snapshot = _snapshot.CreateSnapshot(); - var engine = ApplicationEngine.Create(TriggerType.Application, TestBlockchain.TheNeoSystem.GenesisBlock, snapshot, TestBlockchain.TheNeoSystem.GenesisBlock, settings: TestBlockchain.TheNeoSystem.Settings); + _persistingBlock.Header.Index = 1; + var engine = ApplicationEngine.Create(TriggerType.Application, TestBlockchain.TheNeoSystem.GenesisBlock, snapshot, _persistingBlock, settings: TestBlockchain.TheNeoSystem.Settings); ScriptBuilder sb = new(); var tmp = engine.ScriptContainer.GetScriptHashesForVerifying(engine.Snapshot); UInt160 from = engine.ScriptContainer.GetScriptHashesForVerifying(engine.Snapshot)[0]; @@ -911,7 +935,8 @@ public void TestVote() sb.EmitDynamicCall(NativeContract.NEO.Hash, "transfer", from, UInt160.Zero, amount, null); engine.LoadScript(sb.ToArray()); - engine.Execute(); + var state = engine.Execute(); + Console.WriteLine($"{state} {engine.FaultException}"); var result = engine.ResultStack.Peek(); result.GetType().Should().Be(typeof(VM.Types.Boolean)); return (true, result.GetBoolean()); @@ -984,6 +1009,7 @@ internal static (bool State, bool Result) Check_Vote(DataCache snapshot, byte[] if (engine.Execute() == VMState.FAULT) { + Console.WriteLine(engine.FaultException); return (false, false); } @@ -1039,6 +1065,7 @@ internal static (BigInteger Value, bool State) Check_UnclaimedGas(DataCache snap if (engine.Execute() == VMState.FAULT) { + Console.WriteLine(engine.FaultException); return (BigInteger.Zero, false); }