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 all 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
2 changes: 0 additions & 2 deletions src/neo/Ledger/Blockchain.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,6 @@ internal class PreverifyCompleted { public Transaction Transaction; public Verif
public class RelayResult { public IInventory Inventory; public VerifyResult Result; }

public static readonly uint MillisecondsPerBlock = ProtocolSettings.Default.MillisecondsPerBlock;
public const uint DecrementInterval = 2000000;
public static readonly uint[] GenerationAmount = { 6, 5, 4, 3, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 };
public static readonly TimeSpan TimePerBlock = TimeSpan.FromMilliseconds(MillisecondsPerBlock);
public static readonly ECPoint[] StandbyCommittee = ProtocolSettings.Default.StandbyCommittee.Select(p => ECPoint.DecodePoint(p.HexToBytes(), ECCurve.Secp256r1)).ToArray();
public static readonly ECPoint[] StandbyValidators = StandbyCommittee[0..ProtocolSettings.Default.ValidatorsCount];
Expand Down
8 changes: 8 additions & 0 deletions src/neo/SmartContract/Native/KeyBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,14 @@ unsafe public KeyBuilder Add<T>(T key) where T : unmanaged
return Add(new ReadOnlySpan<byte>(&key, sizeof(T)));
}

unsafe public KeyBuilder AddBigEndian<T>(T key) where T : unmanaged
{
ReadOnlySpan<byte> buffer = new ReadOnlySpan<byte>(&key, sizeof(T));
for (int i = buffer.Length - 1; i >= 0; i--)
stream.WriteByte(buffer[i]);
return this;
}

public byte[] ToArray()
{
using (stream)
Expand Down
6 changes: 6 additions & 0 deletions src/neo/SmartContract/Native/NativeContract.cs
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,12 @@ protected NativeContract()
contractsHashDictionary.Add(Hash, this);
}

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

private protected KeyBuilder CreateStorageKey(byte prefix)
{
return new KeyBuilder(Id, prefix);
Expand Down
18 changes: 6 additions & 12 deletions src/neo/SmartContract/Native/PolicyContract.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,6 @@ public PolicyContract()
Manifest.Features = ContractFeatures.HasStorage;
}

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

[ContractMethod(0_01000000, CallFlags.AllowStates)]
public uint GetMaxTransactionsPerBlock(StoreView snapshot)
{
Expand Down Expand Up @@ -93,7 +87,7 @@ public bool IsAnyAccountBlocked(StoreView snapshot, params UInt160[] hashes)
[ContractMethod(0_03000000, CallFlags.AllowModifyStates)]
private bool SetMaxBlockSize(ApplicationEngine engine, uint value)
{
if (!CheckCommittees(engine)) return false;
if (!CheckCommittee(engine)) return false;
if (Network.P2P.Message.PayloadMaxSize <= value) return false;
StorageItem storage = engine.Snapshot.Storages.GetAndChange(CreateStorageKey(Prefix_MaxBlockSize), () => new StorageItem());
storage.Set(value);
Expand All @@ -103,7 +97,7 @@ private bool SetMaxBlockSize(ApplicationEngine engine, uint value)
[ContractMethod(0_03000000, CallFlags.AllowModifyStates)]
private bool SetMaxTransactionsPerBlock(ApplicationEngine engine, uint value)
{
if (!CheckCommittees(engine)) return false;
if (!CheckCommittee(engine)) return false;
StorageItem storage = engine.Snapshot.Storages.GetAndChange(CreateStorageKey(Prefix_MaxTransactionsPerBlock), () => new StorageItem());
storage.Set(value);
return true;
Expand All @@ -112,7 +106,7 @@ private bool SetMaxTransactionsPerBlock(ApplicationEngine engine, uint value)
[ContractMethod(0_03000000, CallFlags.AllowModifyStates)]
private bool SetMaxBlockSystemFee(ApplicationEngine engine, long value)
{
if (!CheckCommittees(engine)) return false;
if (!CheckCommittee(engine)) return false;
if (value <= 4007600) return false;
StorageItem storage = engine.Snapshot.Storages.GetAndChange(CreateStorageKey(Prefix_MaxBlockSystemFee), () => new StorageItem());
storage.Set(value);
Expand All @@ -122,7 +116,7 @@ private bool SetMaxBlockSystemFee(ApplicationEngine engine, long value)
[ContractMethod(0_03000000, CallFlags.AllowModifyStates)]
private bool SetFeePerByte(ApplicationEngine engine, long value)
{
if (!CheckCommittees(engine)) return false;
if (!CheckCommittee(engine)) return false;
StorageItem storage = engine.Snapshot.Storages.GetAndChange(CreateStorageKey(Prefix_FeePerByte), () => new StorageItem());
storage.Set(value);
return true;
Expand All @@ -131,7 +125,7 @@ private bool SetFeePerByte(ApplicationEngine engine, long value)
[ContractMethod(0_03000000, CallFlags.AllowModifyStates)]
private bool BlockAccount(ApplicationEngine engine, UInt160 account)
{
if (!CheckCommittees(engine)) return false;
if (!CheckCommittee(engine)) return false;
StorageKey key = CreateStorageKey(Prefix_BlockedAccounts);
StorageItem storage = engine.Snapshot.Storages.GetOrAdd(key, () => new StorageItem(new byte[1]));
List<UInt160> accounts = storage.GetSerializableList<UInt160>();
Expand All @@ -145,7 +139,7 @@ private bool BlockAccount(ApplicationEngine engine, UInt160 account)
[ContractMethod(0_03000000, CallFlags.AllowModifyStates)]
private bool UnblockAccount(ApplicationEngine engine, UInt160 account)
{
if (!CheckCommittees(engine)) return false;
if (!CheckCommittee(engine)) return false;
StorageKey key = CreateStorageKey(Prefix_BlockedAccounts);
StorageItem storage = engine.Snapshot.Storages.TryGet(key);
if (storage is null) return false;
Expand Down
97 changes: 74 additions & 23 deletions src/neo/SmartContract/Native/Tokens/NeoToken.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using Array = Neo.VM.Types.Array;

namespace Neo.SmartContract.Native.Tokens
{
Expand All @@ -26,6 +27,11 @@ public sealed class NeoToken : Nep5Token<NeoToken.NeoAccountState>
private const byte Prefix_VotersCount = 1;
private const byte Prefix_Candidate = 33;
private const byte Prefix_NextValidators = 14;
private const byte Prefix_GasPerBlock = 29;

private const byte NeoHolderRewardRatio = 10;
private const byte CommitteeRewardRatio = 5;
private const byte VoterRewardRatio = 85;

internal NeoToken()
{
Expand All @@ -50,45 +56,45 @@ 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)

GasRecord gasRecord = snapshot.Storages[CreateStorageKey(Prefix_GasPerBlock)].GetInteroperable<GasRecord>();
BigInteger sum = 0;
for (var i = gasRecord.Count - 1; i >= 0; 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)
var currentIndex = gasRecord[i].Index;
if (currentIndex >= end) continue;
if (currentIndex > start)
{
uend--;
iend = Blockchain.DecrementInterval;
sum += gasRecord[i].GasPerBlock * (end - currentIndex);
end = currentIndex;
}
while (ustart < uend)
else
{
amount += (Blockchain.DecrementInterval - istart) * Blockchain.GenerationAmount[ustart];
ustart++;
istart = 0;
sum += gasRecord[i].GasPerBlock * (end - start);
break;
}
amount += (iend - istart) * Blockchain.GenerationAmount[ustart];
}
return value * amount * GAS.Factor / TotalAmount;
return value * sum * NeoHolderRewardRatio / 100 / TotalAmount;
}

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

engine.Snapshot.Storages.Add(CreateStorageKey(Prefix_GasPerBlock), new StorageItem(new GasRecord
{
(0, 5 * GAS.Factor)
}));

engine.Snapshot.Storages.Add(CreateStorageKey(Prefix_VotersCount), new StorageItem(new byte[0]));
Mint(engine, Blockchain.GetConsensusAddress(Blockchain.StandbyValidators), TotalAmount);
}
Expand All @@ -100,13 +106,41 @@ protected override void OnPersist(ApplicationEngine engine)
storage.Value = GetValidators(engine.Snapshot).ToByteArray();
}

[ContractMethod(0_05000000, CallFlags.AllowModifyStates)]
private bool SetGasPerBlock(ApplicationEngine engine, BigInteger gasPerBlock)
{
if (gasPerBlock < 0 || gasPerBlock > 10 * GAS.Factor)
throw new ArgumentOutOfRangeException(nameof(gasPerBlock));
if (!CheckCommittee(engine)) return false;
uint index = engine.Snapshot.PersistingBlock.Index + 1;
GasRecord gasRecord = engine.Snapshot.Storages.GetAndChange(CreateStorageKey(Prefix_GasPerBlock)).GetInteroperable<GasRecord>();
if (gasRecord[^1].Index == index)
gasRecord[^1] = (index, gasPerBlock);
else
gasRecord.Add((index, gasPerBlock));
return true;
}

[ContractMethod(0_01000000, CallFlags.AllowStates)]
public BigInteger GetGasPerBlock(StoreView snapshot)
{
var index = snapshot.PersistingBlock.Index;
GasRecord gasRecord = snapshot.Storages[CreateStorageKey(Prefix_GasPerBlock)].GetInteroperable<GasRecord>();
for (var i = gasRecord.Count - 1; i >= 0; i--)
{
if (gasRecord[i].Index <= index)
return gasRecord[i].GasPerBlock;
}
throw new InvalidOperationException();
}

[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);
return CalculateBonus(snapshot, state.Balance, state.BalanceHeight, end);
}

[ContractMethod(0_05000000, CallFlags.AllowModifyStates)]
Expand Down Expand Up @@ -263,5 +297,22 @@ public StackItem ToStackItem(ReferenceCounter referenceCounter)
return new Struct(referenceCounter) { Registered, Votes };
}
}

private sealed class GasRecord : List<(uint Index, BigInteger GasPerBlock)>, IInteroperable
{
public void FromStackItem(StackItem stackItem)
{
foreach (StackItem item in (Array)stackItem)
{
Struct @struct = (Struct)item;
Add(((uint)@struct[0].GetInteger(), @struct[1].GetInteger()));
}
}

public StackItem ToStackItem(ReferenceCounter referenceCounter)
{
return new Array(referenceCounter, this.Select(p => new Struct(referenceCounter, new StackItem[] { p.Index, p.GasPerBlock })));
}
}
}
}
37 changes: 27 additions & 10 deletions tests/neo.UnitTests/SmartContract/Native/Tokens/UT_GasToken.cs
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ public void Check_BalanceOfTransferAndBurn()
// Check unclaim

var unclaim = UT_NeoToken.Check_UnclaimedGas(snapshot, from);
unclaim.Value.Should().Be(new BigInteger(600000000000));
unclaim.Value.Should().Be(new BigInteger(0.5 * 1000 * 100000000L));
unclaim.State.Should().BeTrue();

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

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

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

snapshot.Storages.GetChangeSet().Count().Should().Be(keyCount + 3); // 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, 30000500_00000000, false).Should().BeFalse(); // Not signed
NativeContract.GAS.Transfer(snapshot, from, to, 30000500_00000001, true).Should().BeFalse(); // More than balance
NativeContract.GAS.Transfer(snapshot, from, to, 30000500_00000000, true).Should().BeTrue(); // All balance

// Balance of

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

snapshot.Storages.GetChangeSet().Count().Should().Be(keyCount + 1); // All
Expand All @@ -98,19 +98,19 @@ public void Check_BalanceOfTransferAndBurn()
// Burn more than expected

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

// 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(3000049999999999);

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

(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, 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