Skip to content
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
978 changes: 0 additions & 978 deletions benchmarks/Neo.Json.Benchmarks/Data/RpcTestCases.json

Large diffs are not rendered by default.

55 changes: 52 additions & 3 deletions src/Neo/SmartContract/Native/Governance.cs
Original file line number Diff line number Diff line change
Expand Up @@ -460,9 +460,58 @@ void _OnBalanceChanged(ApplicationEngine engine, UInt160 assetId, UInt160 accoun
async ContractTask _OnTransfer(ApplicationEngine engine, UInt160 assetId, UInt160 from, UInt160 to, BigInteger amount, StackItem data)
{
if (assetId != NeoTokenId) return;
var list = engine.CurrentContext!.GetState<ExecutionContextState>().CallingContext!.GetState<List<GasDistribution>>();
foreach (var distribution in list)
await TokenManagement.MintInternal(engine, GasTokenId, distribution.Account, distribution.Amount, assertOwner: false, callOnBalanceChanged: false, callOnPayment: true, callOnTransfer: false);
if (amount.IsZero || from == to)
{
// Handle unclaimed gas distribution when transferring zero amount
// This allows claiming unclaimed gas by transferring 0 NEO
StorageKey key = CreateStorageKey(Prefix_NeoAccount, from);
var accountState = engine.SnapshotCache.GetAndChange(key)?.GetInteroperable<NeoAccountState>();
if (accountState is null) return;
BigInteger balance = NativeContract.TokenManagement.BalanceOf(engine.SnapshotCache, NeoTokenId, from);
GasDistribution? distribution = DistributeGas(engine, from, accountState, balance);
if (distribution is not null)
await TokenManagement.MintInternal(engine, GasTokenId, distribution.Account, distribution.Amount, assertOwner: false, callOnBalanceChanged: false, callOnPayment: true, callOnTransfer: false);
}
else
{
var list = engine.CurrentContext!.GetState<ExecutionContextState>().CallingContext!.GetState<List<GasDistribution>>();
foreach (var distribution in list)
await TokenManagement.MintInternal(engine, GasTokenId, distribution.Account, distribution.Amount, assertOwner: false, callOnBalanceChanged: false, callOnPayment: true, callOnTransfer: false);
}
}

/// <summary>
/// Handles payment callback for validator registration.
/// </summary>
/// <param name="engine">The engine used to process the payment.</param>
/// <param name="assetId">The asset identifier.</param>
/// <param name="from">The sender account.</param>
/// <param name="amount">The amount of tokens sent.</param>
/// <param name="data">Optional data containing the public key for registration.</param>
[ContractMethod(CpuFee = 1 << 15, RequiredCallFlags = CallFlags.States | CallFlags.AllowNotify)]
private async ContractTask _OnPayment(ApplicationEngine engine, UInt160 assetId, UInt160 from, BigInteger amount, StackItem data)
Copy link
Owner

Choose a reason for hiding this comment

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

Do we have to keep this? RegisterCandidate doesn't work?

Copy link
Author

Choose a reason for hiding this comment

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

Both can work. Just keep it compatible. Not bad but more convenient.

{
// Only accept GAS for validator registration, not NEO
if (assetId != GasTokenId)
throw new InvalidOperationException($"Only GAS can be accepted for validator registration, got {assetId}");

// Check if the amount matches the registration price
long registerPrice = GetRegisterPrice(engine.SnapshotCache);
if ((long)amount != registerPrice)
throw new ArgumentOutOfRangeException(nameof(amount), $"Amount must equal the registration price {registerPrice}, got {amount}");

// Extract public key from data
if (data is not ByteString dataBytes || dataBytes.GetSpan().Length == 0)
throw new FormatException("Data parameter must contain the public key for registration");

ECPoint pubkey = ECPoint.DecodePoint(dataBytes.GetSpan(), ECCurve.Secp256r1);

// Register the candidate
if (!RegisterInternal(engine, pubkey))
throw new InvalidOperationException("Failed to register candidate. The witness does not match the public key.");
Copy link

Choose a reason for hiding this comment

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

how about if it is already registered?
should we abort?

Copy link

Choose a reason for hiding this comment

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

Maybe it is good to return false as well


// Burn the registration fee (the GAS sent to this contract)
await TokenManagement.BurnInternal(engine, GasTokenId, Hash, amount, assertOwner: false, callOnBalanceChanged: false, callOnTransfer: false);
}

GasDistribution? DistributeGas(ApplicationEngine engine, UInt160 account, NeoAccountState state, BigInteger balance)
Expand Down
41 changes: 7 additions & 34 deletions src/Neo/Wallets/AssetDescriptor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,8 @@
// Redistribution and use in source and binary forms with or without
// modifications are permitted.

using Neo.Extensions.VM;
using Neo.Persistence;
using Neo.SmartContract;
using Neo.SmartContract.Native;
using Neo.VM;

namespace Neo.Wallets;

Expand Down Expand Up @@ -46,39 +43,15 @@ public class AssetDescriptor
/// Initializes a new instance of the <see cref="AssetDescriptor"/> class.
/// </summary>
/// <param name="snapshot">The snapshot used to read data.</param>
/// <param name="settings">The <see cref="ProtocolSettings"/> used by the <see cref="ApplicationEngine"/>.</param>
/// <param name="assetId">The id of the asset.</param>
public AssetDescriptor(DataCache snapshot, ProtocolSettings settings, UInt160 assetId)
public AssetDescriptor(DataCache snapshot, UInt160 assetId)
{
// GasToken is managed by TokenManagement, not a contract itself
if (assetId.Equals(NativeContract.Governance.GasTokenId))
{
TokenState token = NativeContract.TokenManagement.GetTokenInfo(snapshot, assetId)!;
AssetId = assetId;
AssetName = Governance.GasTokenName;
Symbol = token.Symbol;
Decimals = token.Decimals;
}
else
{
var contract = NativeContract.ContractManagement.GetContract(snapshot, assetId)
?? throw new ArgumentException($"No asset contract found for assetId {assetId}. Please ensure the assetId is correct and the asset is deployed on the blockchain.", nameof(assetId));

byte[] script;
using (ScriptBuilder sb = new())
{
sb.EmitDynamicCall(assetId, "decimals", CallFlags.ReadOnly);
sb.EmitDynamicCall(assetId, "symbol", CallFlags.ReadOnly);
script = sb.ToArray();
}

using var engine = ApplicationEngine.Run(script, snapshot, settings: settings, gas: 0_30000000L);
if (engine.State != VMState.HALT) throw new ArgumentException($"Failed to execute 'decimals' or 'symbol' method for asset {assetId}. The contract execution did not complete successfully (VM state: {engine.State}).", nameof(assetId));
AssetId = assetId;
AssetName = contract.Manifest.Name;
Symbol = engine.ResultStack.Pop().GetString()!;
Decimals = (byte)engine.ResultStack.Pop().GetInteger();
}
TokenState token = NativeContract.TokenManagement.GetTokenInfo(snapshot, assetId)
?? throw new ArgumentException($"No token found for assetId {assetId}. Please ensure the assetId is correct and the asset is deployed on the blockchain.", nameof(assetId));
AssetId = assetId;
AssetName = token.Name;
Symbol = token.Symbol;
Decimals = token.Decimals;
}

public override string ToString()
Expand Down
24 changes: 2 additions & 22 deletions src/Neo/Wallets/Wallet.cs
Original file line number Diff line number Diff line change
Expand Up @@ -564,22 +564,7 @@ public Transaction MakeTransaction(DataCache snapshot, TransferOutput[] outputs,
var balances = new List<(UInt160 Account, BigInteger Value)>();
foreach (UInt160 account in accounts)
{
BigInteger value;
// GAS token uses TokenManagement.BalanceOf which requires assetId as first parameter
// So we can't use EmitDynamicCall with GasTokenId as contract address
if (assetId.Equals(NativeContract.Governance.GasTokenId))
{
value = NativeContract.TokenManagement.BalanceOf(snapshot, assetId, account);
}
else
{
using ScriptBuilder sb2 = new();
sb2.EmitDynamicCall(assetId, "balanceOf", CallFlags.ReadOnly, account);
using ApplicationEngine engine = ApplicationEngine.Run(sb2.ToArray(), snapshot, settings: ProtocolSettings, persistingBlock: persistingBlock);
if (engine.State != VMState.HALT)
throw new InvalidOperationException($"Failed to execute balanceOf method for asset {assetId} on account {account}. The smart contract execution faulted with state: {engine.State}.");
value = engine.ResultStack.Pop().GetInteger();
}
BigInteger value = NativeContract.TokenManagement.BalanceOf(snapshot, assetId, account);
if (value.Sign > 0) balances.Add((account, value));
}
BigInteger sum_balance = balances.Select(p => p.Value).Sum();
Expand All @@ -604,12 +589,7 @@ public Transaction MakeTransaction(DataCache snapshot, TransferOutput[] outputs,
Scopes = WitnessScope.CalledByEntry
});
}
// GAS token uses TokenManagement.Transfer which requires assetId as first parameter
// So we need to call TokenManagement contract's transfer method, not GasTokenId's transfer
if (assetId.Equals(NativeContract.Governance.GasTokenId))
sb.EmitDynamicCall(NativeContract.TokenManagement.Hash, "transfer", assetId, account, output.ScriptHash, value, output.Data);
else
sb.EmitDynamicCall(output.AssetId, "transfer", account, output.ScriptHash, value, output.Data);
sb.EmitDynamicCall(NativeContract.TokenManagement.Hash, "transfer", assetId, account, output.ScriptHash, value, output.Data);
sb.Emit(OpCode.ASSERT);
}
}
Expand Down
40 changes: 31 additions & 9 deletions tests/Neo.UnitTests/Extensions/UT_ContractStateExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@

using Neo.Extensions.IO;
using Neo.Extensions.SmartContract;
using Neo.SmartContract;
using Neo.SmartContract.Native;
using System.Numerics;
using System.Reflection;

namespace Neo.UnitTests.Extensions;

Expand All @@ -29,29 +32,48 @@ public void Initialize()
[TestMethod]
public void TestGetStorage()
{
var contractStorage = NativeContract.ContractManagement.FindContractStorage(_system.StoreView, NativeContract.NEO.Id);
var contractStorage = NativeContract.ContractManagement.FindContractStorage(_system.StoreView, NativeContract.Governance.Id);
Assert.IsNotNull(contractStorage);

var neoContract = NativeContract.ContractManagement.GetContractById(_system.StoreView, NativeContract.NEO.Id);
Assert.IsNotNull(neoContract);
var governanceContract = NativeContract.ContractManagement.GetContractById(_system.StoreView, NativeContract.Governance.Id);
Assert.IsNotNull(governanceContract);

contractStorage = neoContract.FindStorage(_system.StoreView);
contractStorage = governanceContract.FindStorage(_system.StoreView);

Assert.IsNotNull(contractStorage);

contractStorage = neoContract.FindStorage(_system.StoreView, [20]);
contractStorage = governanceContract.FindStorage(_system.StoreView, [10]);

Assert.IsNotNull(contractStorage);

UInt160 address = "0x9f8f056a53e39585c7bb52886418c7bed83d126b";
var item = neoContract.GetStorage(_system.StoreView, [20, .. address.ToArray()]);
var item = governanceContract.GetStorage(_system.StoreView, [10, .. address.ToArray()]);

Assert.IsNotNull(item);
Assert.AreEqual(100_000_000, item.GetInteroperable<AccountState>().Balance);
var neoAccountStateType = typeof(Governance).GetNestedType("NeoAccountState", BindingFlags.NonPublic | BindingFlags.Instance)
?? throw new InvalidOperationException("NeoAccountState type not found");
var neoAccountState = GetInteroperable(item, neoAccountStateType);
// NeoAccountState has BalanceHeight, VoteTo, and LastGasPerVote fields (not Balance)
// NEO token balance is now stored in TokenManagement, not in NeoAccountState
var balanceHeightField = neoAccountStateType.GetField("BalanceHeight", BindingFlags.Public | BindingFlags.Instance);
var balanceHeight = (uint)(balanceHeightField?.GetValue(neoAccountState) ?? throw new InvalidOperationException("BalanceHeight field not found"));
// The test address should have a BalanceHeight value from the genesis block
Assert.IsTrue(balanceHeight >= 0, "BalanceHeight should be a valid block height");

// Ensure GetInteroperableClone don't change nothing

item.GetInteroperableClone<AccountState>().Balance = 123;
Assert.AreEqual(100_000_000, item.GetInteroperable<AccountState>().Balance);
var cloneMethod = typeof(StorageItem).GetMethod("GetInteroperableClone", BindingFlags.Public | BindingFlags.Instance, null, Type.EmptyTypes, null);
var genericMethod = cloneMethod?.MakeGenericMethod(neoAccountStateType);
var clonedState = genericMethod?.Invoke(item, null);
balanceHeightField?.SetValue(clonedState, (uint)123);
var balanceHeightAfterClone = (uint)(balanceHeightField?.GetValue(neoAccountState) ?? throw new InvalidOperationException("BalanceHeight field not found"));
Assert.AreEqual(balanceHeight, balanceHeightAfterClone, "Original state should not be affected by clone modification");
}

private static object GetInteroperable(StorageItem item, Type type)
{
var method = typeof(StorageItem).GetMethod("GetInteroperable", BindingFlags.Public | BindingFlags.Instance, null, Type.EmptyTypes, null);
var genericMethod = method?.MakeGenericMethod(type);
return genericMethod?.Invoke(item, null) ?? throw new InvalidOperationException("GetInteroperable method not found");
}
}
39 changes: 0 additions & 39 deletions tests/Neo.UnitTests/Extensions/UT_NeoTokenExtensions.cs

This file was deleted.

2 changes: 1 addition & 1 deletion tests/Neo.UnitTests/GasTests/GasFixturesTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ public static void AssertFixture(GasTestFixture fixture, DataCache snapshot)
{
if (fixture.Signature.SignedByCommittee)
{
signatures.Add(NativeContract.NEO.GetCommitteeAddress(snapshot));
signatures.Add(NativeContract.Governance.GetCommitteeAddress(snapshot));
}
}

Expand Down
2 changes: 1 addition & 1 deletion tests/Neo.UnitTests/Ledger/UT_Blockchain.cs
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ internal static StorageKey CreateStorageKey(byte prefix, byte[]? key = null)
key?.CopyTo(buffer.AsSpan(1));
return new()
{
Id = NativeContract.NEO.Id,
Id = NativeContract.Governance.Id,
Key = buffer
};
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,6 @@ public void Verify()

Assert.IsFalse(test.Verify(snapshotCache, new Transaction() { Signers = Array.Empty<Signer>(), Attributes = [test], Witnesses = null! }));
Assert.IsFalse(test.Verify(snapshotCache, new Transaction() { Signers = new Signer[] { new() { Account = UInt160.Parse("0xa400ff00ff00ff00ff00ff00ff00ff00ff00ff01") } }, Attributes = [test], Witnesses = null! }));
Assert.IsTrue(test.Verify(snapshotCache, new Transaction() { Signers = new Signer[] { new() { Account = NativeContract.NEO.GetCommitteeAddress(snapshotCache) } }, Attributes = [test], Witnesses = null! }));
Assert.IsTrue(test.Verify(snapshotCache, new Transaction() { Signers = new Signer[] { new() { Account = NativeContract.Governance.GetCommitteeAddress(snapshotCache) } }, Attributes = [test], Witnesses = null! }));
}
}
6 changes: 3 additions & 3 deletions tests/Neo.UnitTests/Network/P2P/Payloads/UT_Transaction.cs
Original file line number Diff line number Diff line change
Expand Up @@ -677,7 +677,7 @@ public void FeeIsSignatureContract_TestScope_CurrentHash_NEO_FAULT()
{
Account = acc.ScriptHash,
Scopes = WitnessScope.CustomContracts,
AllowedContracts = [NativeContract.NEO.Hash]
AllowedContracts = [NativeContract.Governance.NeoTokenId]
} };

// using this...
Expand Down Expand Up @@ -738,7 +738,7 @@ public void FeeIsSignatureContract_TestScope_CurrentHash_NEO_GAS()
{
Account = acc.ScriptHash,
Scopes = WitnessScope.CustomContracts,
AllowedContracts = [NativeContract.NEO.Hash, NativeContract.TokenManagement.Hash]
AllowedContracts = [NativeContract.Governance.NeoTokenId, NativeContract.TokenManagement.Hash]
} };

// using this...
Expand Down Expand Up @@ -838,7 +838,7 @@ public void FeeIsSignatureContract_TestScope_NoScopeFAULT()
{
Account = acc.ScriptHash,
Scopes = WitnessScope.CustomContracts,
AllowedContracts = [NativeContract.NEO.Hash, NativeContract.Governance.GasTokenId]
AllowedContracts = [NativeContract.Governance.NeoTokenId, NativeContract.Governance.GasTokenId]
} };

// using this...
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,11 +64,11 @@ public void TestContractManifestFromJson()
public void TestEquals()
{
var descriptor1 = ContractPermissionDescriptor.CreateWildcard();
var descriptor2 = ContractPermissionDescriptor.Create(NativeContract.NEO.Hash);
var descriptor2 = ContractPermissionDescriptor.Create(NativeContract.Governance.NeoTokenId);

Assert.AreNotEqual(descriptor1, descriptor2);

var descriptor3 = ContractPermissionDescriptor.Create(NativeContract.NEO.Hash);
var descriptor3 = ContractPermissionDescriptor.Create(NativeContract.Governance.NeoTokenId);

Assert.AreEqual(descriptor2, descriptor3);
}
Expand Down
10 changes: 5 additions & 5 deletions tests/Neo.UnitTests/SmartContract/Native/UT_GasToken.cs
Original file line number Diff line number Diff line change
Expand Up @@ -74,11 +74,11 @@ public async Task Check_BalanceOfTransferAndBurn()

// Transfer

Assert.IsTrue(NativeContract.NEO.Transfer(snapshot, from, to, BigInteger.Zero, true, persistingBlock));
Assert.ThrowsExactly<InvalidOperationException>(() => _ = NativeContract.NEO.Transfer(snapshot, from, null, BigInteger.Zero, true, persistingBlock));
Assert.ThrowsExactly<InvalidOperationException>(() => _ = NativeContract.NEO.Transfer(snapshot, null, to, BigInteger.Zero, false, persistingBlock));
Assert.AreEqual(100000000, NativeContract.NEO.BalanceOf(snapshot, from));
Assert.AreEqual(0, NativeContract.NEO.BalanceOf(snapshot, to));
Assert.IsTrue(UT_NeoToken.Transfer(snapshot, from, to, BigInteger.Zero, true, persistingBlock));
Assert.ThrowsExactly<InvalidOperationException>(() => _ = UT_NeoToken.Transfer(snapshot, from, null, BigInteger.Zero, true, persistingBlock));
Assert.ThrowsExactly<InvalidOperationException>(() => _ = UT_NeoToken.Transfer(snapshot, null, to, BigInteger.Zero, false, persistingBlock));
Assert.AreEqual(100000000, UT_NeoToken.BalanceOf(snapshot, from));
Assert.AreEqual(0, UT_NeoToken.BalanceOf(snapshot, to));

Assert.AreEqual(52000500_00000000, NativeContract.TokenManagement.BalanceOf(snapshot, NativeContract.Governance.GasTokenId, new UInt160(from)));
Assert.AreEqual(0, NativeContract.TokenManagement.BalanceOf(snapshot, NativeContract.Governance.GasTokenId, new UInt160(to)));
Expand Down
Loading