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 Economic Model #1716

Closed
wants to merge 53 commits into from
Closed
Show file tree
Hide file tree
Changes from 45 commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
380efa4
add economic model
Jun 20, 2020
43d0139
Merge remote-tracking branch 'upstream/master' into add_economic_model
Jun 22, 2020
8644dbb
fix
Jun 23, 2020
29d21ac
fix ut
Jun 23, 2020
d7e212f
add mark
Jun 23, 2020
397897f
fix consensus_ut
Jun 24, 2020
8d31450
add ut
Jun 28, 2020
9c09c1d
merge
Jun 28, 2020
7bdbc4f
format
Jun 28, 2020
74f9a19
fix ut
Jun 28, 2020
64ede76
fix
Jun 28, 2020
23aa3c5
fix ut
Jun 28, 2020
88e57b7
apply recommedations
Jun 29, 2020
28d5186
Merge branch 'master' into add_economic_model
shargon Jun 29, 2020
0950567
optimize reward ratio
Jun 30, 2020
8a04041
Merge branch 'add_economic_model' of https://github.com/Tommo-L/neo i…
Jun 30, 2020
4b854e1
reset datacache
Jul 3, 2020
4e767df
Merge remote-tracking branch 'upstream/master' into add_economic_model
Jul 3, 2020
3e849f0
reset datacache
Jul 3, 2020
0b5b29a
Update src/neo/SmartContract/Native/NativeContract.cs
shargon Jul 3, 2020
c24a6e9
Merge branch 'master' into add_economic_model
erikzhang Jul 3, 2020
feb6d36
apply recommendations
Jul 3, 2020
ce7a5b1
optimize createstoragekey
Jul 5, 2020
ada8c2f
KeyBuilder.ToArray()
erikzhang Jul 6, 2020
92fa72a
Disable warning
erikzhang Jul 6, 2020
85b2a74
Merge branch 'master' into add_economic_model
shargon Jul 10, 2020
70a6291
Add blank line
shargon Jul 10, 2020
7019a0a
optimize
Jul 10, 2020
f68f5a6
resolve
Jul 10, 2020
f2d25e9
format
Jul 10, 2020
a053d2f
format
Jul 10, 2020
3860cc4
format
Jul 10, 2020
9408a11
Merge branch 'master' into add_economic_model
shargon Jul 11, 2020
eef1582
Merge branch 'master' into add_economic_model
shargon Jul 13, 2020
255382f
Merge branch 'master' into add_economic_model
erikzhang Jul 14, 2020
033205c
Fix UT
erikzhang Jul 14, 2020
3d5df3e
fix conflicts
Jul 15, 2020
ee5e874
format
Jul 15, 2020
649be9f
Merge branch 'master' into add_economic_model
shargon Jul 16, 2020
32db697
fix vote
Jul 16, 2020
66db3ea
Merge branch 'add_economic_model' of https://github.com/Tommo-L/neo i…
Jul 16, 2020
2e24381
fix conflicts
Jul 27, 2020
a2a372f
fix ut later
Jul 28, 2020
b6da509
resolve
Aug 6, 2020
5465f4d
fix ut
Aug 6, 2020
5ece87f
Change to pubKey
shargon Aug 7, 2020
f035479
Merge remote-tracking branch 'neo-project/master' into add_economic_m…
shargon Aug 7, 2020
96f8891
Fix Merge
shargon Aug 7, 2020
174e3f0
Fix merge
shargon Aug 7, 2020
17b0d05
Fix keys
shargon Aug 7, 2020
d851966
combine economic & governance
Aug 11, 2020
a6198d5
Fix null
shargon Aug 11, 2020
c44d7df
Merge branch 'master' into add_economic_model
erikzhang Aug 16, 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
165 changes: 165 additions & 0 deletions src/neo/SmartContract/Native/Tokens/NeoToken.Economic.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
#pragma warning disable IDE0051

using Neo.Cryptography.ECC;
using Neo.Ledger;
using Neo.Persistence;
using Neo.VM;
using Neo.VM.Types;
using System;
using System.Numerics;
using Array = Neo.VM.Types.Array;

namespace Neo.SmartContract.Native.Tokens
{
public partial class NeoToken
{
private const byte Prefix_GasPerBlock = 17;
private const byte Prefix_RewardRatio = 73;
private const byte Prefix_VoterRewardPerCommittee = 23;
private const byte Prefix_HolderRewardPerBlock = 57;

[ContractMethod(0_05000000, CallFlags.AllowModifyStates)]
private bool SetGasPerBlock(ApplicationEngine engine, BigInteger gasPerBlock)
{
if (gasPerBlock < 0 || gasPerBlock > 8 * GAS.Factor) return false;
if (!CheckCommittees(engine)) return false;
StorageItem item = engine.Snapshot.Storages.GetAndChange(CreateStorageKey(Prefix_GasPerBlock));
item.Value = gasPerBlock.ToByteArray();
return true;
}

[ContractMethod(0_05000000, CallFlags.AllowModifyStates)]
private bool SetRewardRatio(ApplicationEngine engine, byte neoHoldersRewardRatio, byte committeesRewardRatio, byte votersRewardRatio)
{
if (checked(neoHoldersRewardRatio + committeesRewardRatio + votersRewardRatio) != 100) return false;
if (!CheckCommittees(engine)) return false;
RewardRatio rewardRatio = engine.Snapshot.Storages.GetAndChange(CreateStorageKey(Prefix_RewardRatio), () => new StorageItem(new RewardRatio())).GetInteroperable<RewardRatio>();
rewardRatio.NeoHolder = neoHoldersRewardRatio;
rewardRatio.Committee = committeesRewardRatio;
rewardRatio.Voter = votersRewardRatio;
return true;
}

[ContractMethod(1_00000000, CallFlags.AllowStates)]
public BigInteger GetGasPerBlock(StoreView snapshot)
{
return new BigInteger(snapshot.Storages.TryGet(CreateStorageKey(Prefix_GasPerBlock)).Value);
}

[ContractMethod(1_00000000, CallFlags.AllowStates)]
internal RewardRatio GetRewardRatio(StoreView snapshot)
{
return snapshot.Storages.TryGet(CreateStorageKey(Prefix_RewardRatio)).GetInteroperable<RewardRatio>();
}

private void DistributeGas(ApplicationEngine engine, UInt160 account, NeoAccountState state)
{
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, 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;

var voteScriptHash = Contract.CreateSignatureContract(vote).ScriptHash;
var endKey = CreateStorageKey(Prefix_VoterRewardPerCommittee).Add(voteScriptHash).Add(uint.MaxValue - start - 1);
var startKey = CreateStorageKey(Prefix_VoterRewardPerCommittee).Add(voteScriptHash).Add(uint.MaxValue - end - 1);
var enumerator = snapshot.Storages.FindRange(startKey, endKey).GetEnumerator();
if (!enumerator.MoveNext()) return neoHolderReward;

var endRewardPerNeo = new BigInteger(enumerator.Current.Value.Value);
var startRewardPerNeo = BigInteger.Zero;
var borderKey = CreateStorageKey(Prefix_VoterRewardPerCommittee).Add(voteScriptHash).Add(uint.MaxValue);
enumerator = snapshot.Storages.FindRange(endKey, borderKey).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)
{
var endRewardItem = snapshot.Storages.TryGet(CreateStorageKey(Prefix_HolderRewardPerBlock).Add(uint.MaxValue - end - 1));
var startRewardItem = snapshot.Storages.TryGet(CreateStorageKey(Prefix_HolderRewardPerBlock).Add(uint.MaxValue - start - 1));
BigInteger startReward = startRewardItem is null ? 0 : new BigInteger(startRewardItem.Value);
return value * (new BigInteger(endRewardItem.Value) - startReward) / TotalAmount;
}

[ContractMethod(0_03000000, CallFlags.AllowStates)]
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.VoteTo, state.Balance, state.BalanceHeight, end);
}

private void DistributeGasForCommittee(ApplicationEngine engine)
{
var gasPerBlock = GetGasPerBlock(engine.Snapshot);
(ECPoint, BigInteger)[] committeeVotes = GetCommitteeVotes(engine.Snapshot);
int validatorNumber = GetValidators(engine.Snapshot).Length;
RewardRatio rewardRatio = GetRewardRatio(engine.Snapshot);
BigInteger holderRewardPerBlock = gasPerBlock * rewardRatio.NeoHolder / 100; // The final calculation should be divided by the total number of NEO
BigInteger committeeRewardPerBlock = gasPerBlock * rewardRatio.Committee / 100 / committeeVotes.Length;
BigInteger voterRewardPerBlock = gasPerBlock * rewardRatio.Voter / 100 / (committeeVotes.Length + validatorNumber);

// Keep track of incremental gains of neo holders

var index = engine.Snapshot.PersistingBlock.Index;
var holderRewards = holderRewardPerBlock;
var holderRewardKey = CreateStorageKey(Prefix_HolderRewardPerBlock).Add(uint.MaxValue - index - 1);
var holderBorderKey = CreateStorageKey(Prefix_HolderRewardPerBlock).Add(uint.MaxValue);
var enumerator = engine.Snapshot.Storages.FindRange(holderRewardKey, holderBorderKey).GetEnumerator();
if (enumerator.MoveNext())
holderRewards += new BigInteger(enumerator.Current.Value.Value);
engine.Snapshot.Storages.Add(holderRewardKey, new StorageItem() { Value = holderRewards.ToByteArray() });

for (var i = 0; i < committeeVotes.Length; i++)
{
// Keep track of incremental gains for each committee's voters

UInt160 committeeAddr = Contract.CreateSignatureContract(committeeVotes[i].Item1).ScriptHash;
if (committeeVotes[i].Item2 > 0)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

StandbyCommittee will initially have no votes. I'm not sure if it is ok. (After confirmed, I will fix UT)

{
BigInteger voterRewardPerCommittee = (i < validatorNumber ? 2 : 1) * voterRewardPerBlock * 10000L / (committeeVotes[i].Item2); // Zoom in 10000 times, and the final calculation should be divided 10000L
enumerator = engine.Snapshot.Storages.Find(CreateStorageKey(Prefix_VoterRewardPerCommittee).Add(committeeAddr).ToArray()).GetEnumerator();
if (enumerator.MoveNext())
voterRewardPerCommittee += new BigInteger(enumerator.Current.Value.Value);
var storageKey = CreateStorageKey(Prefix_VoterRewardPerCommittee).Add(committeeAddr).Add(uint.MaxValue - index - 1);
engine.Snapshot.Storages.Add(storageKey, new StorageItem() { Value = voterRewardPerCommittee.ToByteArray() });
}

// Mint the reward for committee by each block

GAS.Mint(engine, committeeAddr, committeeRewardPerBlock);
}
}
}

internal class RewardRatio : IInteroperable
{
public int NeoHolder;
public int Committee;
public int Voter;

public void FromStackItem(StackItem stackItem)
{
Array array = (Array)stackItem;
NeoHolder = (int)array[0].GetInteger();
Committee = (int)array[1].GetInteger();
Voter = (int)array[2].GetInteger();
}

public StackItem ToStackItem(ReferenceCounter referenceCounter)
{
return new Array() { new Integer(NeoHolder), new Integer(Committee), new Integer(Voter) };
}
}
}
205 changes: 205 additions & 0 deletions src/neo/SmartContract/Native/Tokens/NeoToken.Governance.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
#pragma warning disable IDE0051

using Neo.Cryptography.ECC;
using Neo.IO;
using Neo.Ledger;
using Neo.Persistence;
using Neo.VM;
using Neo.VM.Types;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;

namespace Neo.SmartContract.Native.Tokens
{
public partial class NeoToken
{
private const byte Prefix_VotersCount = 1;
private const byte Prefix_Candidate = 13;
private const byte Prefix_NextValidators = 77;

public const decimal EffectiveVoterTurnout = 0.2M;

[ContractMethod(0_05000000, CallFlags.AllowModifyStates)]
private bool RegisterCandidate(ApplicationEngine engine, ECPoint pubkey)
{
if (!engine.CheckWitnessInternal(Contract.CreateSignatureRedeemScript(pubkey).ToScriptHash()))
return false;
RegisterCandidateInternal(engine.Snapshot, pubkey);
return true;
}

private void RegisterCandidateInternal(StoreView snapshot, ECPoint pubkey)
{
StorageKey key = CreateStorageKey(Prefix_Candidate).Add(pubkey);
StorageItem item = snapshot.Storages.GetAndChange(key, () => new StorageItem(new CandidateState()));
CandidateState state = item.GetInteroperable<CandidateState>();
state.Registered = true;
}

[ContractMethod(0_05000000, CallFlags.AllowModifyStates)]
private bool UnregisterCandidate(ApplicationEngine engine, ECPoint pubkey)
{
if (!engine.CheckWitnessInternal(Contract.CreateSignatureRedeemScript(pubkey).ToScriptHash()))
return false;
StorageKey key = CreateStorageKey(Prefix_Candidate).Add(pubkey);
if (engine.Snapshot.Storages.TryGet(key) is null) return true;
StorageItem item = engine.Snapshot.Storages.GetAndChange(key);
CandidateState state = item.GetInteroperable<CandidateState>();
if (state.Votes.IsZero)
engine.Snapshot.Storages.Delete(key);
else
state.Registered = false;
return true;
}

[ContractMethod(5_00000000, CallFlags.AllowModifyStates)]
private bool Vote(ApplicationEngine engine, UInt160 account, ECPoint voteTo)
{
if (!engine.CheckWitnessInternal(account)) return false;
NeoAccountState state_account = engine.Snapshot.Storages.GetAndChange(CreateStorageKey(Prefix_Account).Add(account))?.GetInteroperable<NeoAccountState>();
if (state_account is null) return false;
CandidateState validator_new = null;
if (voteTo != null)
{
validator_new = engine.Snapshot.Storages.GetAndChange(CreateStorageKey(Prefix_Candidate).Add(voteTo))?.GetInteroperable<CandidateState>();
if (validator_new is null) return false;
if (!validator_new.Registered) return false;
}
if (state_account.VoteTo is null ^ voteTo is null)
{
StorageItem item = engine.Snapshot.Storages.GetAndChange(CreateStorageKey(Prefix_VotersCount));
if (state_account.VoteTo is null)
item.Add(state_account.Balance);
else
item.Add(-state_account.Balance);
}
if (state_account.VoteTo != null)
{
StorageKey key = CreateStorageKey(Prefix_Candidate).Add(state_account.VoteTo);
StorageItem storage_validator = engine.Snapshot.Storages.GetAndChange(key);
CandidateState state_validator = storage_validator.GetInteroperable<CandidateState>();
state_validator.Votes -= state_account.Balance;
if (!state_validator.Registered && state_validator.Votes.IsZero)
engine.Snapshot.Storages.Delete(key);
}
state_account.VoteTo = voteTo;
if (validator_new != null)
{
validator_new.Votes += state_account.Balance;
}
return true;
}

[ContractMethod(1_00000000, CallFlags.AllowStates)]
public (ECPoint PublicKey, BigInteger Votes)[] GetCandidates(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)]
public ECPoint[] GetValidators(StoreView snapshot)
{
return GetCommitteeMembers(snapshot).Take(ProtocolSettings.Default.ValidatorsCount).OrderBy(p => p).ToArray();
}

[ContractMethod(1_00000000, CallFlags.AllowStates)]
public ECPoint[] GetCommittee(StoreView snapshot)
{
return GetCommitteeMembers(snapshot).OrderBy(p => p).ToArray();
}

public UInt160 GetCommitteeAddress(StoreView snapshot)
{
ECPoint[] committees = GetCommittee(snapshot);
return Contract.CreateMultiSigRedeemScript(committees.Length - (committees.Length - 1) / 2, committees).ToScriptHash();
}

private IEnumerable<ECPoint> GetCommitteeMembers(StoreView snapshot)
{
decimal votersCount = (decimal)new BigInteger(snapshot.Storages[CreateStorageKey(Prefix_VotersCount)].Value);
decimal VoterTurnout = votersCount / (decimal)TotalAmount;
if (VoterTurnout < EffectiveVoterTurnout)
return Blockchain.StandbyCommittee;
var candidates = GetCandidates(snapshot);
if (candidates.Length < ProtocolSettings.Default.CommitteeMembersCount)
return Blockchain.StandbyCommittee;
return candidates.OrderByDescending(p => p.Votes).ThenBy(p => p.PublicKey).Select(p => p.PublicKey).Take(ProtocolSettings.Default.CommitteeMembersCount);
}

private (ECPoint PublicKey, BigInteger Votes)[] GetCommitteeVotes(StoreView snapshot)
{
(ECPoint PublicKey, BigInteger Votes)[] committeeVotes = new (ECPoint PublicKey, BigInteger Votes)[ProtocolSettings.Default.CommitteeMembersCount];
var i = 0;
foreach (var commiteePubKey in GetCommitteeMembers(snapshot))
{
var item = snapshot.Storages.TryGet(CreateStorageKey(Prefix_Candidate).Add(commiteePubKey));
if (item is null)
committeeVotes[i] = (commiteePubKey, BigInteger.Zero);
else
committeeVotes[i] = (commiteePubKey, item.GetInteroperable<CandidateState>().Votes);
i++;
}
return committeeVotes;
}

[ContractMethod(1_00000000, CallFlags.AllowStates)]
public ECPoint[] GetNextBlockValidators(StoreView snapshot)
{
StorageItem storage = snapshot.Storages.TryGet(CreateStorageKey(Prefix_NextValidators));
if (storage is null) return Blockchain.StandbyValidators;
return storage.Value.AsSerializableArray<ECPoint>();
}

public bool CheckCommittees(ApplicationEngine engine)
{
UInt160 committeeMultiSigAddr = NEO.GetCommitteeAddress(engine.Snapshot);
return engine.CheckWitnessInternal(committeeMultiSigAddr);
}

public class NeoAccountState : AccountState
{
public uint BalanceHeight;
public ECPoint VoteTo;

public override void FromStackItem(StackItem stackItem)
{
base.FromStackItem(stackItem);
Struct @struct = (Struct)stackItem;
BalanceHeight = (uint)@struct[1].GetInteger();
VoteTo = @struct[2].IsNull ? null : @struct[2].GetSpan().AsSerializable<ECPoint>();
}

public override StackItem ToStackItem(ReferenceCounter referenceCounter)
{
Struct @struct = (Struct)base.ToStackItem(referenceCounter);
@struct.Add(BalanceHeight);
@struct.Add(VoteTo?.ToArray() ?? StackItem.Null);
return @struct;
}
}

internal class CandidateState : IInteroperable
{
public bool Registered = true;
public BigInteger Votes;

public void FromStackItem(StackItem stackItem)
{
Struct @struct = (Struct)stackItem;
Registered = @struct[0].GetBoolean();
Votes = @struct[1].GetInteger();
}

public StackItem ToStackItem(ReferenceCounter referenceCounter)
{
return new Struct(referenceCounter) { Registered, Votes };
}
}
}
}
Loading