Skip to content

Commit

Permalink
Optimize vote reward data (#2841)
Browse files Browse the repository at this point in the history
* optimize vote reward

* rm end when calculate bonus

* fix ut

* revert UnclaimGas interface, check end instead, fix ut
  • Loading branch information
ZhangTao authored Mar 15, 2023
1 parent ec7ac2b commit df534f6
Show file tree
Hide file tree
Showing 3 changed files with 92 additions and 53 deletions.
75 changes: 43 additions & 32 deletions src/Neo/SmartContract/Native/NeoToken.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,18 @@

#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;
using Neo.SmartContract.Iterators;
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
{
Expand Down Expand Up @@ -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
{
Expand All @@ -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)
Expand All @@ -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));
}
}
Expand Down Expand Up @@ -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);
}
}
}
Expand Down Expand Up @@ -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<NeoAccountState>();
return CalculateBonus(snapshot, state.VoteTo, state.Balance, state.BalanceHeight, end);
return CalculateBonus(snapshot, state, end);
}

[ContractMethod(RequiredCallFlags = CallFlags.States)]
Expand Down Expand Up @@ -406,8 +406,15 @@ private async ContractTask<bool> 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;
Expand Down Expand Up @@ -572,19 +579,23 @@ public class NeoAccountState : AccountState
/// </summary>
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)
{
Struct @struct = (Struct)base.ToStackItem(referenceCounter);
@struct.Add(BalanceHeight);
@struct.Add(VoteTo?.ToArray() ?? StackItem.Null);
@struct.Add(LastGasPerVote);
return @struct;
}
}
Expand Down
15 changes: 8 additions & 7 deletions tests/Neo.UnitTests/SmartContract/Native/UT_GasToken.cs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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
{
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -111,10 +113,9 @@ await Assert.ThrowsExceptionAsync<InvalidOperationException>(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

Expand Down
Loading

0 comments on commit df534f6

Please sign in to comment.