Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add incentive for voter #1848

Merged
merged 88 commits into from
Nov 2, 2020
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
88 commits
Select commit Hold shift + click to select a range
bc785f3
add reward key
Aug 18, 2020
1e8966a
Merge branch 'master' into incentive_for_voter
erikzhang Aug 21, 2020
25908bc
Merge branch 'master' into incentive_for_voter
erikzhang Aug 22, 2020
a6e438e
incentive for voter
Aug 22, 2020
4249d07
optimize
Aug 22, 2020
422f20a
optimize
Aug 23, 2020
df0062b
optimize
Aug 23, 2020
7c72d41
fix CalculateBonus and add ut
Aug 23, 2020
0a3d09e
format
Aug 23, 2020
723f60c
Item2 -> Votes
vncoelho Aug 23, 2020
e99a806
Merge branch 'master' into incentive_for_voter
erikzhang Aug 25, 2020
5b92e05
Move fields
erikzhang Aug 25, 2020
addfebf
Fix GetCommitteeVotes()
erikzhang Aug 25, 2020
4b753d2
specify variable name
Aug 27, 2020
eb9b728
Remove double ToArray
shargon Aug 28, 2020
bf1ec79
fix
Sep 7, 2020
ba05800
fix ut
Sep 7, 2020
5e4abe7
remove
Sep 8, 2020
03859d8
Merge branch 'master' into incentive_for_voter
erikzhang Sep 11, 2020
77fe460
fix
Sep 11, 2020
2a806ef
fix
Sep 11, 2020
67aafdc
remove useless code
Sep 11, 2020
675fe7a
optimize
Sep 11, 2020
e062344
Merge branch 'master' into incentive_for_voter
shargon Sep 12, 2020
ccd4151
Merge branch 'master' into incentive_for_voter
erikzhang Sep 13, 2020
6ffc601
fix
Sep 14, 2020
b0db8f4
Merge branch 'master' into incentive_for_voter
Tommo-L Sep 14, 2020
5d19387
Merge branch 'master' into incentive_for_voter
erikzhang Sep 15, 2020
ed13ad5
resolve
Sep 15, 2020
8b06824
format
Sep 15, 2020
62baa9f
Merge branch 'master' into incentive_for_voter
shargon Sep 15, 2020
c49a809
Add using to dispose enumerators
erikzhang Sep 16, 2020
bf00ca1
Update NeoToken.cs
erikzhang Sep 17, 2020
fed12df
Update NeoToken.cs
erikzhang Sep 17, 2020
87e1d9d
fix
Sep 17, 2020
0570997
fix CalculateBonus
Sep 18, 2020
4a12748
Usng asending order
Sep 18, 2020
e656049
Merge branch 'master' into incentive_for_voter
erikzhang Sep 18, 2020
1dd9ad4
Merge branch 'master' into incentive_for_voter
Tommo-L Sep 21, 2020
3d9b2ff
Merge branch 'master' into incentive_for_voter
shargon Sep 21, 2020
321f5f8
Update NeoToken.cs
erikzhang Sep 22, 2020
57b6e32
fix
Sep 22, 2020
3124024
Merge branch 'master' into incentive_for_voter
Tommo-L Sep 24, 2020
9af4bb3
optimize findrange
Sep 24, 2020
40e73de
format
Sep 24, 2020
c5f1458
Merge branch 'master' into incentive_for_voter
Tommo-L Sep 25, 2020
53950b4
optimize
Sep 25, 2020
849be7c
Update NeoToken.cs
erikzhang Sep 25, 2020
94caa53
fix
Sep 27, 2020
c95ff95
format
Sep 27, 2020
6693760
Merge branch 'master' into incentive_for_voter
Tommo-L Sep 27, 2020
d811ce8
Use GetCommitteeFromCache
erikzhang Sep 27, 2020
f5f9259
Ensure registered
erikzhang Sep 27, 2020
36ec672
fix PostPersist
Sep 27, 2020
251d633
Update NeoToken.cs
erikzhang Sep 28, 2020
268bc86
cache committee votes
Sep 28, 2020
811292b
Merge branch 'incentive_for_voter' of https://github.com/Tommo-L/neo …
Sep 28, 2020
f5c17fd
cache committee
Sep 29, 2020
29e83ad
Update src/neo/Ledger/StorageItem.cs
shargon Sep 30, 2020
81cd44a
Merge branch 'master' into incentive_for_voter
Tommo-L Oct 9, 2020
e3b2ee6
fix PostPersist
Oct 9, 2020
22f1af9
fix ShouldRefreshCommittee & optimize PostPersist
Oct 9, 2020
3d5065d
add CommitteeEpoch
Oct 9, 2020
50e1663
fix CalculateBonus
Oct 9, 2020
aa688ca
fix PostPersist
Oct 9, 2020
6218108
optimize CalculateBonus
Oct 9, 2020
4253502
optimize PostPersist
Oct 9, 2020
d06c1cb
Update NeoToken.cs
erikzhang Oct 10, 2020
cd37075
Merge branch 'master' into incentive_for_voter
shargon Oct 10, 2020
083412f
fix conflicts
Oct 19, 2020
875dc76
Merge branch 'incentive_for_voter' of https://github.com/Tommo-L/neo …
Oct 19, 2020
7ba5e24
add comments for trigger github action
Oct 19, 2020
e8c8044
Allow standby can be voted
Oct 20, 2020
95acd30
optimize
Oct 27, 2020
5d5837e
rename local variable
shargon Oct 27, 2020
990d8d5
Optimize Unregister Candidate
shargon Oct 27, 2020
898f21c
Revert
shargon Oct 27, 2020
5173eeb
Merge branch 'master' into incentive_for_voter
shargon Oct 27, 2020
15792c2
Merge branch 'master' into incentive_for_voter
shargon Oct 28, 2020
44bdd5c
add ut
Oct 29, 2020
4d4986d
fix ut
Oct 29, 2020
ca174f0
format
Oct 29, 2020
a96ca80
Merge branch 'master' into incentive_for_voter
shargon Oct 29, 2020
c23c3ba
Conflicts
shargon Oct 29, 2020
387565f
Merge branch 'master' into incentive_for_voter
Tommo-L Oct 31, 2020
5e18673
Update src/neo/SmartContract/Native/Tokens/NeoToken.cs
Tommo-L Nov 1, 2020
947882c
Merge branch 'master' into incentive_for_voter
shargon Nov 1, 2020
99522a1
Merge branch 'master' into incentive_for_voter
shargon Nov 2, 2020
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
94 changes: 89 additions & 5 deletions src/neo/SmartContract/Native/Tokens/NeoToken.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ public sealed class NeoToken : Nep5Token<NeoToken.NeoAccountState>
private const byte Prefix_Candidate = 33;
private const byte Prefix_NextValidators = 14;
private const byte Prefix_GasPerBlock = 29;
private const byte Prefix_VoterRewardPerCommittee = 23;

private const byte NeoHolderRewardRatio = 10;
private const byte CommitteeRewardRatio = 5;
Expand All @@ -49,23 +50,49 @@ protected override void OnBalanceChanging(ApplicationEngine engine, UInt160 acco
if (amount.IsZero) return;
if (state.VoteTo != null)
vncoelho marked this conversation as resolved.
Show resolved Hide resolved
{
engine.Snapshot.Storages.GetAndChange(CreateStorageKey(Prefix_Candidate).Add(state.VoteTo)).GetInteroperable<CandidateState>().Votes += amount;
CandidateState state_validator = engine.Snapshot.Storages.GetAndChange(CreateStorageKey(Prefix_Candidate).Add(state.VoteTo)).GetInteroperable<CandidateState>();
state_validator.Votes += amount;
engine.Snapshot.Storages.GetAndChange(CreateStorageKey(Prefix_VotersCount)).Add(amount);

if (state_validator.Votes == 0)
{
foreach (var (key, _) in engine.Snapshot.Storages.Find(CreateStorageKey(Prefix_VoterRewardPerCommittee).Add(state.VoteTo).ToArray()))
engine.Snapshot.Storages.Delete(key);
}
}
}

private void DistributeGas(ApplicationEngine engine, UInt160 account, NeoAccountState state)
{
BigInteger gas = CalculateBonus(engine.Snapshot, state.Balance, state.BalanceHeight, engine.Snapshot.PersistingBlock.Index);
BigInteger gas = CalculateBonus(engine.Snapshot, state.VoteTo, state.Balance, state.BalanceHeight, engine.Snapshot.PersistingBlock.Index);
state.BalanceHeight = engine.Snapshot.PersistingBlock.Index;
GAS.Mint(engine, account, gas);
}

private BigInteger CalculateBonus(StoreView snapshot, BigInteger value, uint start, uint end)
private BigInteger CalculateBonus(StoreView snapshot, ECPoint vote, BigInteger value, uint start, 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;

StorageKey keyStart = CreateStorageKey(Prefix_VoterRewardPerCommittee).Add(vote).AddBigEndian(uint.MaxValue - end);
StorageKey keyEnd = CreateStorageKey(Prefix_VoterRewardPerCommittee).Add(vote).AddBigEndian(uint.MaxValue - start);
IEnumerator<(StorageKey Key, StorageItem Value)> enumerator = snapshot.Storages.FindRange(keyStart, keyEnd).GetEnumerator();
if (!enumerator.MoveNext()) return neoHolderReward;
var endRewardPerNeo = new BigInteger(enumerator.Current.Value.Value);
var startRewardPerNeo = BigInteger.Zero;
StorageKey keyMax = CreateStorageKey(Prefix_VoterRewardPerCommittee).Add(vote).AddBigEndian(uint.MaxValue);
enumerator = snapshot.Storages.FindRange(keyEnd, keyMax).GetEnumerator();
if (enumerator.MoveNext())
startRewardPerNeo = new BigInteger(enumerator.Current.Value.Value);

return neoHolderReward + value * (endRewardPerNeo - startRewardPerNeo) / 10000L;
}

private BigInteger CalculateNeoHolderReward(StoreView snapshot, BigInteger value, uint start, uint end)
{
GasRecord gasRecord = snapshot.Storages[CreateStorageKey(Prefix_GasPerBlock)].GetInteroperable<GasRecord>();
BigInteger sum = 0;
for (var i = gasRecord.Count - 1; i >= 0; i--)
Expand Down Expand Up @@ -112,6 +139,24 @@ protected override void OnPersist(ApplicationEngine engine)
var account = Contract.CreateSignatureRedeemScript(pubkey).ToScriptHash();
GAS.Mint(engine, account, gasPerBlock * CommitteeRewardRatio / 100);

// Record the cumulative reward of the voters of each committee

var voterRewardPerCommittee = gasPerBlock * VoterRewardRatio / 100 / (ProtocolSettings.Default.CommitteeMembersCount + ProtocolSettings.Default.ValidatorsCount);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Validators are included inside the Committee, they will be rewarded twice as committee and as validator?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think so, as dictated in the description:

The voters who voted for a consensus node are assigned 2/28 of this part according to the voting weight, and the voters who voted for a committee member (non-consensus node) are assigned 1/28 of this part according to the voting weight. (Implemented by this PR)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is mainly used to ensure that the validators' votes are concentrated and not too scattered.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct me if I'm wrong, but we've got rounding error even here:

> 500000000.0*85/100/(21+7)
=> 15178571.42857143

Then it only gets worse with votes/neo amount.

var committeeVotes = GetCommitteeVotes(engine.Snapshot).ToArray();
for (var i = 0; i < committeeVotes.Length; i++)
{
if (committeeVotes[i].Votes > 0)
{
BigInteger voterSumRewardPerNEO = (i < ProtocolSettings.Default.ValidatorsCount ? 2 : 1) * voterRewardPerCommittee * 10000L / committeeVotes[i].Votes; // Zoom in 10000 times, and the final calculation should be divided 10000L
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

when votes is more than 1000,0000 and there is only 1 person votes, the result is incorrect. the data need to be more accurate.

var voterRewardKeyPrefix = CreateStorageKey(Prefix_VoterRewardPerCommittee).Add(committeeVotes[i].PublicKey);
var enumerator = engine.Snapshot.Storages.Find(voterRewardKeyPrefix.ToArray()).GetEnumerator();
if (enumerator.MoveNext())
voterSumRewardPerNEO += new BigInteger(enumerator.Current.Value.Value);
var voterRewardKey = CreateStorageKey(Prefix_VoterRewardPerCommittee).Add(committeeVotes[i].PublicKey).AddBigEndian(uint.MaxValue - engine.Snapshot.PersistingBlock.Index - 1);
engine.Snapshot.Storages.Add(voterRewardKey, new StorageItem() { Value = voterSumRewardPerNEO.ToByteArray() });
}
}
erikzhang marked this conversation as resolved.
Show resolved Hide resolved

// Set next validators

StorageItem storage = engine.Snapshot.Storages.GetAndChange(CreateStorageKey(Prefix_NextValidators), () => new StorageItem());
Expand Down Expand Up @@ -152,7 +197,7 @@ public BigInteger UnclaimedGas(StoreView snapshot, UInt160 account, uint end)
StorageItem storage = snapshot.Storages.TryGet(CreateStorageKey(Prefix_Account).Add(account));
if (storage is null) return BigInteger.Zero;
NeoAccountState state = storage.GetInteroperable<NeoAccountState>();
return CalculateBonus(snapshot, state.Balance, state.BalanceHeight, end);
return CalculateBonus(snapshot, state.VoteTo, state.Balance, state.BalanceHeight, end);
}

[ContractMethod(0_05000000, CallFlags.AllowModifyStates)]
Expand Down Expand Up @@ -211,7 +256,14 @@ private bool Vote(ApplicationEngine engine, UInt160 account, ECPoint voteTo)
CandidateState state_validator = storage_validator.GetInteroperable<CandidateState>();
state_validator.Votes -= state_account.Balance;
if (!state_validator.Registered && state_validator.Votes.IsZero)
{
DistributeGas(engine, account, state_account);
UInt160 voteeAddr = Contract.CreateSignatureContract(state_account.VoteTo).ScriptHash;
foreach (var (rewardKey, _) in engine.Snapshot.Storages.Find(CreateStorageKey(Prefix_VoterRewardPerCommittee).Add(voteeAddr).ToArray()))
engine.Snapshot.Storages.Delete(rewardKey);

engine.Snapshot.Storages.Delete(key);
}
}
state_account.VoteTo = voteTo;
erikzhang marked this conversation as resolved.
Show resolved Hide resolved
if (validator_new != null)
Expand All @@ -221,15 +273,29 @@ private bool Vote(ApplicationEngine engine, UInt160 account, ECPoint voteTo)
return true;
}

private CandidateState GetCandidate(StoreView snapshot, ECPoint pubkey)
{
StorageKey key = CreateStorageKey(Prefix_Candidate).Add(pubkey);
return snapshot.Storages.TryGet(key)?.GetInteroperable<CandidateState>();
}

[ContractMethod(1_00000000, CallFlags.AllowStates)]
public (ECPoint PublicKey, BigInteger Votes)[] GetCandidates(StoreView snapshot)
{
return GetCandidatesInternal(snapshot)
.Where(p => p.State.Registered)
.Select(p => (p.PublicKey, p.State.Votes))
.ToArray();
}

private IEnumerable<(ECPoint PublicKey, CandidateState State)> GetCandidatesInternal(StoreView snapshot)
{
byte[] prefix_key = CreateStorageKey(Prefix_Candidate).ToArray();
return snapshot.Storages.Find(prefix_key).Select(p =>
(
p.Key.Key.AsSerializable<ECPoint>(1),
p.Value.GetInteroperable<CandidateState>()
)).Where(p => p.Item2.Registered).Select(p => (p.Item1, p.Item2.Votes)).ToArray();
));
}

[ContractMethod(1_00000000, CallFlags.AllowStates)]
Expand Down Expand Up @@ -262,6 +328,24 @@ private IEnumerable<ECPoint> GetCommitteeMembers(StoreView snapshot)
return candidates.OrderByDescending(p => p.Votes).ThenBy(p => p.PublicKey).Select(p => p.PublicKey).Take(ProtocolSettings.Default.CommitteeMembersCount);
}

private IEnumerable<(ECPoint PublicKey, BigInteger Votes)> GetCommitteeVotes(StoreView snapshot)
{
decimal votersCount = (decimal)(BigInteger)snapshot.Storages[CreateStorageKey(Prefix_VotersCount)];
decimal VoterTurnout = votersCount / (decimal)TotalAmount;
if (VoterTurnout < EffectiveVoterTurnout)
return Blockchain.StandbyCommittee.Select(p => (p, GetCandidate(snapshot, p)?.Votes ?? BigInteger.Zero));
var candidates = GetCandidatesInternal(snapshot).ToDictionary(p => p.PublicKey, p => p.State);
if (candidates.Count(p => p.Value.Registered) < ProtocolSettings.Default.CommitteeMembersCount)
return Blockchain.StandbyCommittee.Select(p =>
{
if (candidates.TryGetValue(p, out CandidateState candidate))
return (p, candidate.Votes);
else
return (p, BigInteger.Zero);
});
return candidates.OrderByDescending(p => p.Value.Votes).ThenBy(p => p.Key).Select(p => (p.Key, p.Value.Votes)).Take(ProtocolSettings.Default.CommitteeMembersCount);
}

[ContractMethod(1_00000000, CallFlags.AllowStates)]
public ECPoint[] GetNextBlockValidators(StoreView snapshot)
{
Expand Down
127 changes: 127 additions & 0 deletions tests/neo.UnitTests/SmartContract/Native/Tokens/UT_NeoToken.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using FluentAssertions;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Neo.Consensus;
using Neo.Cryptography;
using Neo.Cryptography.ECC;
using Neo.IO;
Expand All @@ -11,6 +12,7 @@
using Neo.SmartContract.Native;
using Neo.UnitTests.Extensions;
using Neo.VM;
using Neo.Wallets;
using System;
using System.Linq;
using System.Numerics;
Expand Down Expand Up @@ -460,6 +462,16 @@ public void TestCalculateBonus()
action = () => NativeContract.NEO.UnclaimedGas(snapshot, UInt160.Zero, 10).Should().Be(new BigInteger(0));
snapshot.Storages.Delete(key);

// Fault range: start >= end

snapshot.Storages.GetAndChange(key, () => new StorageItem(new NeoAccountState
{
Balance = 100,
BalanceHeight = 100
}));
action = () => NativeContract.NEO.UnclaimedGas(snapshot, UInt160.Zero, 10).Should().Be(new BigInteger(0));
snapshot.Storages.Delete(key);

// Normal 1) votee is non exist

snapshot.Storages.GetAndChange(key, () => new StorageItem(new NeoAccountState
Expand All @@ -468,6 +480,27 @@ public void TestCalculateBonus()
}));
NativeContract.NEO.UnclaimedGas(snapshot, UInt160.Zero, 100).Should().Be(new BigInteger(0.5 * 100 * 100));
snapshot.Storages.Delete(key);

// Normal 2) votee is not committee

snapshot.Storages.GetAndChange(key, () => new StorageItem(new NeoAccountState
{
Balance = 100,
VoteTo = ECCurve.Secp256r1.G
}));
NativeContract.NEO.UnclaimedGas(snapshot, UInt160.Zero, 100).Should().Be(new BigInteger(0.5 * 100 * 100));
snapshot.Storages.Delete(key);

// Normal 3) votee is committee

snapshot.Storages.GetAndChange(key, () => new StorageItem(new NeoAccountState
{
Balance = 100,
VoteTo = Blockchain.StandbyCommittee[0]
}));
snapshot.Storages.Add(new KeyBuilder(-1, 23).Add(Blockchain.StandbyCommittee[0]).AddBigEndian(uint.MaxValue - 50), new StorageItem() { Value = new BigInteger(50 * 10000L).ToByteArray() });
NativeContract.NEO.UnclaimedGas(snapshot, UInt160.Zero, 100).Should().Be(new BigInteger(100 * 100));
snapshot.Storages.Delete(key);
}

[TestMethod]
Expand Down Expand Up @@ -602,6 +635,82 @@ public void TestEconomicParameter()
result1.Item1.GetBoolean().Should().BeTrue();
}

[TestMethod]
public void TestClaimGas()
{
var snapshot = Blockchain.Singleton.GetSnapshot();

// Initialize block

for (var i = 0; i < ProtocolSettings.Default.CommitteeMembersCount; i++)
{
ECPoint member = Blockchain.StandbyCommittee[i];
var moreVotes = 0;
if (i < ProtocolSettings.Default.ValidatorsCount)
moreVotes += 10;
snapshot.Storages.Add(new KeyBuilder(-1, 33).Add(member), new StorageItem(new CandidateState()
{
Registered = true,
Votes = 100 * 10000 + moreVotes
}));
}
var item = snapshot.Storages.GetAndChange(new KeyBuilder(-1, 1), () => new StorageItem());
item.Value = ((BigInteger)2100 * 10000L).ToByteArray();

snapshot.PersistingBlock = new Block { Index = 0 };
Check_OnPersist(snapshot).Should().BeTrue();

for (var i = 0; i < ProtocolSettings.Default.CommitteeMembersCount; i++)
{
ECPoint member = Blockchain.StandbyCommittee[i];
StorageItem storageItem = snapshot.Storages.TryGet(new KeyBuilder(-1, 23).Add(member).AddBigEndian(uint.MaxValue - 1));
if (i < ProtocolSettings.Default.ValidatorsCount)
new BigInteger(storageItem.Value).Should().Be(303568);
else
new BigInteger(storageItem.Value).Should().Be(151785);
}

// Next block

snapshot.PersistingBlock = new Block { Index = 1 };
Check_OnPersist(snapshot).Should().BeTrue();
for (var i = 0; i < ProtocolSettings.Default.CommitteeMembersCount; i++)
{
ECPoint member = Blockchain.StandbyCommittee[i];
StorageItem storageItem = snapshot.Storages.TryGet(new KeyBuilder(-1, 23).Add(member).AddBigEndian(uint.MaxValue - 1 - 1));
if (i < ProtocolSettings.Default.ValidatorsCount)
new BigInteger(storageItem.Value).Should().Be(303568 * 2);
else
new BigInteger(storageItem.Value).Should().Be(151785 * 2);
}

// Next block

snapshot.PersistingBlock = new Block { Index = 2 };
Check_OnPersist(snapshot).Should().BeTrue();
for (var i = 0; i < ProtocolSettings.Default.CommitteeMembersCount; i++)
{
ECPoint member = Blockchain.StandbyCommittee[i];
StorageItem storageItem = snapshot.Storages.TryGet(new KeyBuilder(-1, 23).Add(member).AddBigEndian(uint.MaxValue - 1 - 2));
if (i < ProtocolSettings.Default.ValidatorsCount)
new BigInteger(storageItem.Value).Should().Be(303568 * 3);
else
new BigInteger(storageItem.Value).Should().Be(151785 * 3);
}

// Claim GAS

var account = Contract.CreateSignatureContract(Blockchain.StandbyCommittee[0]).ScriptHash;
snapshot.Storages.Add(new KeyBuilder(-1, 20).Add(account), new StorageItem(new NeoAccountState
{
BalanceHeight = 1,
Balance = 100,
VoteTo = Blockchain.StandbyCommittee[0]
}));
BigInteger value = NativeContract.NEO.UnclaimedGas(snapshot, account, 2);
value.Should().Be(3085);
}

[TestMethod]
public void TestUnclaimedGas()
{
Expand Down Expand Up @@ -675,6 +784,24 @@ public void TestVote()
return (true, result.GetBoolean());
}

internal static bool Check_OnPersist(StoreView snapshot)
{
ECPoint[] committees = NativeContract.NEO.GetCommittee(snapshot);
UInt160 committeesMultisign = Contract.CreateMultiSigRedeemScript(committees.Length - (committees.Length - 1) / 2, committees).ToScriptHash();
var engine = ApplicationEngine.Create(TriggerType.System,
new Nep5NativeContractExtensions.ManualWitness(committeesMultisign), snapshot);

engine.LoadScript(NativeContract.NEO.Script);

var script = new ScriptBuilder();
script.EmitPush(0);
script.Emit(OpCode.PACK);
script.EmitPush("onPersist");
engine.LoadScript(script.ToArray());

return engine.Execute() == VMState.HALT;
}

internal static (BigInteger Value, bool State) Check_GetGasPerBlock(StoreView snapshot)
{
var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshot);
Expand Down