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 51 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
202 changes: 169 additions & 33 deletions src/neo/SmartContract/Native/Tokens/NeoToken.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@ public sealed class NeoToken : Nep5Token<NeoToken.NeoAccountState>
private const byte Prefix_Candidate = 33;
private const byte Prefix_NextValidators = 14;

private const byte Prefix_GasPerBlock = 17;
private const byte Prefix_RewardRatio = 73;
private const byte Prefix_VoterRewardPerCommittee = 23;
private const byte Prefix_HolderRewardPerBlock = 57;

internal NeoToken()
{
this.TotalAmount = 100000000 * Factor;
Expand All @@ -48,47 +53,144 @@ protected override void OnBalanceChanging(ApplicationEngine engine, UInt160 acco
}
}

[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(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(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 amount = BigInteger.Zero;
uint ustart = start / Blockchain.DecrementInterval;
if (ustart < Blockchain.GenerationAmount.Length)

BigInteger neoHolderReward = CalculateNeoHolderReward(snapshot, value, start, end);
if (vote is null) return neoHolderReward;

var endKey = CreateStorageKey(Prefix_VoterRewardPerCommittee).Add(vote).Add(uint.MaxValue - start - 1);
var startKey = CreateStorageKey(Prefix_VoterRewardPerCommittee).Add(vote).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(vote).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);
erikzhang marked this conversation as resolved.
Show resolved Hide resolved
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++)
{
uint istart = start % Blockchain.DecrementInterval;
uint uend = end / Blockchain.DecrementInterval;
uint iend = end % Blockchain.DecrementInterval;
if (uend >= Blockchain.GenerationAmount.Length)
{
uend = (uint)Blockchain.GenerationAmount.Length;
iend = 0;
}
if (iend == 0)
{
uend--;
iend = Blockchain.DecrementInterval;
}
while (ustart < uend)
// Keep track of incremental gains for each committee's voters

if (committeeVotes[i].Item2 > 0)
{
amount += (Blockchain.DecrementInterval - istart) * Blockchain.GenerationAmount[ustart];
ustart++;
istart = 0;
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(committeeVotes[i].Item1).ToArray()).GetEnumerator();
if (enumerator.MoveNext())
voterRewardPerCommittee += new BigInteger(enumerator.Current.Value.Value);
var storageKey = CreateStorageKey(Prefix_VoterRewardPerCommittee).Add(committeeVotes[i].Item1).Add(uint.MaxValue - index - 1);
engine.Snapshot.Storages.Add(storageKey, new StorageItem() { Value = voterRewardPerCommittee.ToByteArray() });
}
amount += (iend - istart) * Blockchain.GenerationAmount[ustart];

// Mint the reward for committee by each block

UInt160 committeeAddr = Contract.CreateSignatureContract(committeeVotes[i].Item1).ScriptHash;
GAS.Mint(engine, committeeAddr, committeeRewardPerBlock);
}
return value * amount * GAS.Factor / TotalAmount;
}

internal override void Initialize(ApplicationEngine engine)
{
// Initialize economic parameters

engine.Snapshot.Storages.Add(CreateStorageKey(Prefix_GasPerBlock), new StorageItem
{
Value = (5 * GAS.Factor).ToByteArray()
});
engine.Snapshot.Storages.Add(CreateStorageKey(Prefix_RewardRatio), new StorageItem(new RewardRatio
{
NeoHolder = 10,
Committee = 5,
Voter = 85
}));

engine.Snapshot.Storages.Add(CreateStorageKey(Prefix_VotersCount), new StorageItem(new byte[0]));
Mint(engine, Blockchain.GetConsensusAddress(Blockchain.StandbyValidators), TotalAmount);
}
Expand All @@ -98,15 +200,7 @@ protected override void OnPersist(ApplicationEngine engine)
base.OnPersist(engine);
StorageItem storage = engine.Snapshot.Storages.GetAndChange(CreateStorageKey(Prefix_NextValidators), () => new StorageItem());
storage.Value = GetValidators(engine.Snapshot).ToByteArray();
}

[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(state.Balance, state.BalanceHeight, end);
DistributeGasForCommittee(engine);
}

[ContractMethod(0_05000000, CallFlags.AllowModifyStates)]
Expand Down Expand Up @@ -204,6 +298,28 @@ public UInt160 GetCommitteeAddress(StoreView snapshot)
return Contract.CreateMultiSigRedeemScript(committees.Length - (committees.Length - 1) / 2, committees).ToScriptHash();
}

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

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;
}

private IEnumerable<ECPoint> GetCommitteeMembers(StoreView snapshot)
{
decimal votersCount = (decimal)(BigInteger)snapshot.Storages[CreateStorageKey(Prefix_VotersCount)];
Expand Down Expand Up @@ -263,5 +379,25 @@ public StackItem ToStackItem(ReferenceCounter referenceCounter)
return new Struct(referenceCounter) { Registered, Votes };
}
}

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

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

public StackItem ToStackItem(ReferenceCounter referenceCounter)
{
return new VM.Types.Array() { new Integer(NeoHolder), new Integer(Committee), new Integer(Voter) };
}
}
}
}
47 changes: 32 additions & 15 deletions tests/neo.UnitTests/SmartContract/Native/Tokens/UT_GasToken.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ public void TestSetup()
public void Check_BalanceOfTransferAndBurn()
{
var snapshot = Blockchain.Singleton.GetSnapshot();
snapshot.PersistingBlock = new Block() { Index = 1000 };
snapshot.PersistingBlock = new Block() { Index = 100 };

byte[] from = Blockchain.GetConsensusAddress(Blockchain.StandbyValidators).ToArray();

Expand All @@ -48,17 +48,18 @@ public void Check_BalanceOfTransferAndBurn()

// Check unclaim

snapshot.Storages.Add(CreateStorageKey(57, uint.MaxValue - 100 - 1), new StorageItem() { Value = new BigInteger(100 * 100000000L).ToByteArray() });
snapshot.Storages.Add(CreateStorageKey(57, uint.MaxValue - 0 - 1), new StorageItem() { Value = new BigInteger(0).ToByteArray() });
var unclaim = UT_NeoToken.Check_UnclaimedGas(snapshot, from);
unclaim.Value.Should().Be(new BigInteger(600000000000));
unclaim.Value.Should().Be(new BigInteger(10000000000));
unclaim.State.Should().BeTrue();

// Transfer

NativeContract.NEO.Transfer(snapshot, from, to, BigInteger.Zero, true).Should().BeTrue();
NativeContract.NEO.BalanceOf(snapshot, from).Should().Be(100000000);
NativeContract.NEO.BalanceOf(snapshot, to).Should().Be(0);

NativeContract.GAS.BalanceOf(snapshot, from).Should().Be(30006000_00000000);
NativeContract.GAS.BalanceOf(snapshot, from).Should().Be(3000010000000000);
NativeContract.GAS.BalanceOf(snapshot, to).Should().Be(0);

// Check unclaim
Expand All @@ -68,21 +69,22 @@ public void Check_BalanceOfTransferAndBurn()
unclaim.State.Should().BeTrue();

supply = NativeContract.GAS.TotalSupply(snapshot);
supply.Should().Be(30006000_00000000);
supply.Should().Be(3000010000000000);


snapshot.Storages.GetChangeSet().Count().Should().Be(keyCount + 3); // Gas
snapshot.Storages.GetChangeSet().Count().Should().Be(keyCount + 5); // Gas

// Transfer

keyCount = snapshot.Storages.GetChangeSet().Count();

NativeContract.GAS.Transfer(snapshot, from, to, 30006000_00000000, false).Should().BeFalse(); // Not signed
NativeContract.GAS.Transfer(snapshot, from, to, 30006000_00000001, true).Should().BeFalse(); // More than balance
NativeContract.GAS.Transfer(snapshot, from, to, 30006000_00000000, true).Should().BeTrue(); // All balance
NativeContract.GAS.Transfer(snapshot, from, to, 3000005000000800, false).Should().BeFalse(); // Not signed
NativeContract.GAS.Transfer(snapshot, from, to, 3000010000000001, true).Should().BeFalse(); // More than balance
NativeContract.GAS.Transfer(snapshot, from, to, 3000010000000000, true).Should().BeTrue(); // All balance

// Balance of

NativeContract.GAS.BalanceOf(snapshot, to).Should().Be(30006000_00000000);
NativeContract.GAS.BalanceOf(snapshot, to).Should().Be(3000010000000000);
NativeContract.GAS.BalanceOf(snapshot, from).Should().Be(0);

snapshot.Storages.GetChangeSet().Count().Should().Be(keyCount + 1); // All
Expand All @@ -97,20 +99,18 @@ public void Check_BalanceOfTransferAndBurn()

// Burn more than expected

Assert.ThrowsException<InvalidOperationException>(() =>
NativeContract.GAS.Burn(engine, new UInt160(to), new BigInteger(30006000_00000001)));
Assert.ThrowsException<InvalidOperationException>(() => NativeContract.GAS.Burn(engine, new UInt160(to), new BigInteger(3000010000000001)));

// Real burn

NativeContract.GAS.Burn(engine, new UInt160(to), new BigInteger(1));

NativeContract.GAS.BalanceOf(snapshot, to).Should().Be(30005999_99999999);
NativeContract.GAS.BalanceOf(snapshot, to).Should().Be(3000009999999999);

keyCount.Should().Be(snapshot.Storages.GetChangeSet().Count());

// Burn all

NativeContract.GAS.Burn(engine, new UInt160(to), new BigInteger(30005999_99999999));
NativeContract.GAS.Burn(engine, new UInt160(to), new BigInteger(3000009999999999));

(keyCount - 1).Should().Be(snapshot.Storages.GetChangeSet().Count());

Expand All @@ -132,5 +132,22 @@ public void Check_BadScript()

Assert.ThrowsException<InvalidOperationException>(() => NativeContract.GAS.Invoke(engine));
}

internal static StorageKey CreateStorageKey(byte prefix, byte[] key = null)
{
StorageKey storageKey = new StorageKey
{
Id = NativeContract.NEO.Id,
Key = new byte[sizeof(byte) + (key?.Length ?? 0)]
};
storageKey.Key[0] = prefix;
key?.CopyTo(storageKey.Key.AsSpan(1));
return storageKey;
}

internal static StorageKey CreateStorageKey(byte prefix, uint key)
{
return CreateStorageKey(prefix, BitConverter.GetBytes(key));
}
}
}
Loading