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 neo holders #1845

Merged
merged 28 commits into from
Aug 21, 2020
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
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
147 changes: 118 additions & 29 deletions src/neo/SmartContract/Native/Tokens/NeoToken.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ 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_RewardRatio = 15;
private const byte Prefix_HolderRewardPerBlock = 27;

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

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.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, 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)
{
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)
{
amount += (Blockchain.DecrementInterval - istart) * Blockchain.GenerationAmount[ustart];
ustart++;
istart = 0;
}
amount += (iend - istart) * Blockchain.GenerationAmount[ustart];
}
return value * amount * GAS.Factor / TotalAmount;
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);
BigInteger endReward = endRewardItem is null ? 0 : new BigInteger(endRewardItem.Value);
return value * (endReward - startReward) / TotalAmount;
}

private void RecordCumulativeGasByBlock(ApplicationEngine engine)
{
var gasPerBlock = GetGasPerBlock(engine.Snapshot);
RewardRatio rewardRatio = GetRewardRatio(engine.Snapshot);
var holderRewards = gasPerBlock * rewardRatio.NeoHolder / 100; // The final calculation should be divided by the total number of NEO

// Keep track of incremental gains of neo holders

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

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,6 +110,41 @@ 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();
RecordCumulativeGasByBlock(engine);
}

[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>();
}

[ContractMethod(0_03000000, CallFlags.AllowStates)]
Expand All @@ -106,7 +153,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(state.Balance, state.BalanceHeight, end);
return CalculateBonus(snapshot, state.Balance, state.BalanceHeight, end);
}

[ContractMethod(0_05000000, CallFlags.AllowModifyStates)]
Expand Down Expand Up @@ -204,6 +251,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 +332,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) };
}
}
}
}
40 changes: 30 additions & 10 deletions tests/neo.UnitTests/SmartContract/Native/Tokens/UT_GasToken.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,11 @@ public void Check_BalanceOfTransferAndBurn()

// Check unclaim

snapshot.Storages.Add(CreateStorageKey(27, uint.MaxValue - 1000 - 1), new StorageItem() { Value = new BigInteger(1000 * 100000000L).ToByteArray() });
snapshot.Storages.Add(CreateStorageKey(27, 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(1000 * 100000000L));
unclaim.State.Should().BeTrue();

// Transfer
Expand All @@ -58,7 +61,7 @@ public void Check_BalanceOfTransferAndBurn()
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(30001000_00000000);
NativeContract.GAS.BalanceOf(snapshot, to).Should().Be(0);

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

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

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, 30001000_00000000, false).Should().BeFalse(); // Not signed
NativeContract.GAS.Transfer(snapshot, from, to, 30001000_00000001, true).Should().BeFalse(); // More than balance
NativeContract.GAS.Transfer(snapshot, from, to, 30001000_00000000, true).Should().BeTrue(); // All balance

// Balance of

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

snapshot.Storages.GetChangeSet().Count().Should().Be(keyCount + 1); // All
Expand All @@ -104,13 +107,13 @@ public void Check_BalanceOfTransferAndBurn()

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(3000099999999999);

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(3000099999999999));

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

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

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

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

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