From 35eea42a99eafc26dfec2ab2a682cbb15e976352 Mon Sep 17 00:00:00 2001 From: Luchuan Date: Fri, 21 Aug 2020 14:30:32 +0800 Subject: [PATCH] Add incentive for neo holders (#1845) --- src/neo/Ledger/Blockchain.cs | 2 - src/neo/SmartContract/Native/KeyBuilder.cs | 8 ++ .../SmartContract/Native/NativeContract.cs | 6 ++ .../SmartContract/Native/PolicyContract.cs | 18 ++-- .../SmartContract/Native/Tokens/NeoToken.cs | 97 ++++++++++++++----- .../Native/Tokens/UT_GasToken.cs | 37 +++++-- .../Native/Tokens/UT_NeoToken.cs | 89 ++++++++++++++++- .../SmartContract/Native/UT_KeyBuilder.cs | 4 + 8 files changed, 211 insertions(+), 50 deletions(-) diff --git a/src/neo/Ledger/Blockchain.cs b/src/neo/Ledger/Blockchain.cs index c7ce9a412a..0ec0c6ade4 100644 --- a/src/neo/Ledger/Blockchain.cs +++ b/src/neo/Ledger/Blockchain.cs @@ -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]; diff --git a/src/neo/SmartContract/Native/KeyBuilder.cs b/src/neo/SmartContract/Native/KeyBuilder.cs index 79ae513df8..0d6c80970e 100644 --- a/src/neo/SmartContract/Native/KeyBuilder.cs +++ b/src/neo/SmartContract/Native/KeyBuilder.cs @@ -37,6 +37,14 @@ unsafe public KeyBuilder Add(T key) where T : unmanaged return Add(new ReadOnlySpan(&key, sizeof(T))); } + unsafe public KeyBuilder AddBigEndian(T key) where T : unmanaged + { + ReadOnlySpan buffer = new ReadOnlySpan(&key, sizeof(T)); + for (int i = buffer.Length - 1; i >= 0; i--) + stream.WriteByte(buffer[i]); + return this; + } + public byte[] ToArray() { using (stream) diff --git a/src/neo/SmartContract/Native/NativeContract.cs b/src/neo/SmartContract/Native/NativeContract.cs index adf83a3b1c..54ef2c5d6d 100644 --- a/src/neo/SmartContract/Native/NativeContract.cs +++ b/src/neo/SmartContract/Native/NativeContract.cs @@ -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); diff --git a/src/neo/SmartContract/Native/PolicyContract.cs b/src/neo/SmartContract/Native/PolicyContract.cs index 244b64deb2..32f90ba89d 100644 --- a/src/neo/SmartContract/Native/PolicyContract.cs +++ b/src/neo/SmartContract/Native/PolicyContract.cs @@ -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) { @@ -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); @@ -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; @@ -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); @@ -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; @@ -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 accounts = storage.GetSerializableList(); @@ -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; diff --git a/src/neo/SmartContract/Native/Tokens/NeoToken.cs b/src/neo/SmartContract/Native/Tokens/NeoToken.cs index aa8ea6da75..01c3311f8d 100644 --- a/src/neo/SmartContract/Native/Tokens/NeoToken.cs +++ b/src/neo/SmartContract/Native/Tokens/NeoToken.cs @@ -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 { @@ -26,6 +27,11 @@ public sealed class NeoToken : Nep5Token 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() { @@ -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(); + 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); } @@ -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(); + 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(); + 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(); - return CalculateBonus(state.Balance, state.BalanceHeight, end); + return CalculateBonus(snapshot, state.Balance, state.BalanceHeight, end); } [ContractMethod(0_05000000, CallFlags.AllowModifyStates)] @@ -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 }))); + } + } } } diff --git a/tests/neo.UnitTests/SmartContract/Native/Tokens/UT_GasToken.cs b/tests/neo.UnitTests/SmartContract/Native/Tokens/UT_GasToken.cs index 380bdb65a6..0a25d9973a 100644 --- a/tests/neo.UnitTests/SmartContract/Native/Tokens/UT_GasToken.cs +++ b/tests/neo.UnitTests/SmartContract/Native/Tokens/UT_GasToken.cs @@ -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 @@ -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 @@ -68,7 +68,7 @@ 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 @@ -76,13 +76,13 @@ public void Check_BalanceOfTransferAndBurn() 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 @@ -98,19 +98,19 @@ public void Check_BalanceOfTransferAndBurn() // Burn more than expected Assert.ThrowsException(() => - 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()); @@ -132,5 +132,22 @@ public void Check_BadScript() Assert.ThrowsException(() => 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; + } } } diff --git a/tests/neo.UnitTests/SmartContract/Native/Tokens/UT_NeoToken.cs b/tests/neo.UnitTests/SmartContract/Native/Tokens/UT_NeoToken.cs index 75857ea9d3..4fae26a467 100644 --- a/tests/neo.UnitTests/SmartContract/Native/Tokens/UT_NeoToken.cs +++ b/tests/neo.UnitTests/SmartContract/Native/Tokens/UT_NeoToken.cs @@ -187,7 +187,7 @@ public void Check_UnclaimedGas() byte[] from = Blockchain.GetConsensusAddress(Blockchain.StandbyValidators).ToArray(); var unclaim = 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(); unclaim = Check_UnclaimedGas(snapshot, new byte[19]); @@ -339,7 +339,7 @@ public void Check_Transfer() // Check unclaim var unclaim = 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 @@ -415,7 +415,12 @@ public void Check_BadScript() public void TestCalculateBonus() { var snapshot = Blockchain.Singleton.GetSnapshot(); + snapshot.PersistingBlock = new Block { Index = 0 }; + StorageKey key = CreateStorageKey(20, UInt160.Zero.ToArray()); + + // Fault: balance < 0 + snapshot.Storages.Add(key, new StorageItem(new NeoAccountState { Balance = -100 @@ -423,11 +428,25 @@ public void TestCalculateBonus() Action action = () => NativeContract.NEO.UnclaimedGas(snapshot, UInt160.Zero, 10).Should().Be(new BigInteger(0)); action.Should().Throw(); 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 { Balance = 100 })); - NativeContract.NEO.UnclaimedGas(snapshot, UInt160.Zero, 30 * Blockchain.DecrementInterval).Should().Be(new BigInteger(7000000000)); + NativeContract.NEO.UnclaimedGas(snapshot, UInt160.Zero, 100).Should().Be(new BigInteger(0.5 * 100 * 100)); + snapshot.Storages.Delete(key); } [TestMethod] @@ -546,6 +565,22 @@ public void TestTotalSupply() NativeContract.NEO.TotalSupply(snapshot).Should().Be(new BigInteger(100000000)); } + [TestMethod] + public void TestEconomicParameter() + { + var snapshot = Blockchain.Singleton.GetSnapshot(); + snapshot.PersistingBlock = new Block { Index = 0 }; + + (BigInteger, bool) result = Check_GetGasPerBlock(snapshot); + result.Item2.Should().BeTrue(); + result.Item1.Should().Be(5 * NativeContract.GAS.Factor); + + snapshot.PersistingBlock = new Block { Index = 10 }; + (VM.Types.Boolean, bool) result1 = Check_SetGasPerBlock(snapshot, 10 * NativeContract.GAS.Factor); + result1.Item2.Should().BeTrue(); + result1.Item1.GetBoolean().Should().BeTrue(); + } + [TestMethod] public void TestUnclaimedGas() { @@ -619,6 +654,54 @@ public void TestVote() return (true, result.GetBoolean()); } + internal static (BigInteger Value, bool State) Check_GetGasPerBlock(StoreView snapshot) + { + var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshot); + + engine.LoadScript(NativeContract.NEO.Script); + + var script = new ScriptBuilder(); + script.EmitPush(0); + script.Emit(OpCode.PACK); + script.EmitPush("getGasPerBlock"); + engine.LoadScript(script.ToArray()); + + if (engine.Execute() == VMState.FAULT) + { + return (BigInteger.Zero, false); + } + + var result = engine.ResultStack.Pop(); + result.Should().BeOfType(typeof(VM.Types.Integer)); + + return (((VM.Types.Integer)result).GetInteger(), true); + } + + internal static (VM.Types.Boolean Value, bool State) Check_SetGasPerBlock(StoreView snapshot, BigInteger gasPerBlock) + { + UInt160 committeeMultiSigAddr = NativeContract.NEO.GetCommitteeAddress(snapshot); + var engine = ApplicationEngine.Create(TriggerType.Application, new Nep5NativeContractExtensions.ManualWitness(committeeMultiSigAddr), snapshot); + + engine.LoadScript(NativeContract.NEO.Script); + + var script = new ScriptBuilder(); + script.EmitPush(gasPerBlock); + script.EmitPush(1); + script.Emit(OpCode.PACK); + script.EmitPush("setGasPerBlock"); + engine.LoadScript(script.ToArray()); + + if (engine.Execute() == VMState.FAULT) + { + return (false, false); + } + + var result = engine.ResultStack.Pop(); + result.Should().BeOfType(typeof(VM.Types.Boolean)); + + return (((VM.Types.Boolean)result).GetBoolean(), true); + } + internal static (bool State, bool Result) Check_Vote(StoreView snapshot, byte[] account, byte[] pubkey, bool signAccount) { var engine = ApplicationEngine.Create(TriggerType.Application, diff --git a/tests/neo.UnitTests/SmartContract/Native/UT_KeyBuilder.cs b/tests/neo.UnitTests/SmartContract/Native/UT_KeyBuilder.cs index 96552dc12e..9db398f0cb 100644 --- a/tests/neo.UnitTests/SmartContract/Native/UT_KeyBuilder.cs +++ b/tests/neo.UnitTests/SmartContract/Native/UT_KeyBuilder.cs @@ -30,6 +30,10 @@ public void Test() key = new KeyBuilder(1, 2); key = key.Add(new a() { x = 123 }); Assert.AreEqual("01000000027b000000", key.ToArray().ToHexString()); + + key = new KeyBuilder(1, 0); + key = key.AddBigEndian(new a() { x = 1 }); + Assert.AreEqual("010000000000000001", key.ToArray().ToHexString()); } } }