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

[Neo Core Fix] Check params at Calling Contract #3438

Open
wants to merge 34 commits into
base: HF_Echidna
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
f26303a
check param when calling contracts
Jim8y Jul 23, 2024
ad33527
check tests
Jim8y Jul 23, 2024
c01ac8f
try fix
Jim8y Jul 24, 2024
9f394f3
revert change to UT
Jim8y Jul 25, 2024
01ed4dc
Merge branch 'master' into check-params
Jim8y Jul 25, 2024
bb6692b
revert debug
Jim8y Jul 25, 2024
5aa0d30
revert change
Jim8y Jul 25, 2024
df39d45
Update src/Neo/SmartContract/ApplicationEngine.cs
Jim8y Jul 25, 2024
9ced170
Merge branch 'master' into check-params
shargon Jul 27, 2024
e0328e3
`[Move]` Part-10 Classes into Different Library - `Neo.Extensions` (#…
cschuchardt88 Dec 12, 2024
c531917
[Add] Get Accounts and Balances for GasToken (#3614)
cschuchardt88 Dec 13, 2024
e0edd98
[Fixed] Dotnet version for DevContainer (#3620)
cschuchardt88 Dec 13, 2024
3669912
fix: move unit-tests of DataCache to corresponding dir (#3625)
nan01ab Dec 16, 2024
692cfbb
fix: remove the only compiler warning (#3623)
nan01ab Dec 16, 2024
71aac33
[`Fix`]: `UInt160.TryParse` when source has whitespaces (#3626)
nan01ab Dec 16, 2024
d749eaf
[`Add`]: add missing unit-tests for `LogStorageStore` (#3624)
nan01ab Dec 17, 2024
4c052ff
Merge branch 'master' into check-params
Jim8y Dec 19, 2024
707dbd4
Merge branch 'HF_Echidna' into check-params
Jim8y Dec 19, 2024
c086661
Merge branch 'master' into HF_Echidna
Jim8y Dec 19, 2024
61de3a4
[Plugin RpcServer] fix issue 3629 (#3630)
Jim8y Dec 19, 2024
0f1507f
Merge branch 'master' into HF_Echidna
Jim8y Dec 19, 2024
9e183fc
check param when calling contracts
Jim8y Jul 23, 2024
4d1c6bd
check tests
Jim8y Jul 23, 2024
8da7163
try fix
Jim8y Jul 24, 2024
3bb2012
revert debug
Jim8y Jul 25, 2024
8a9bbc5
revert change
Jim8y Jul 25, 2024
6b25105
Update src/Neo/SmartContract/ApplicationEngine.cs
Jim8y Jul 25, 2024
c13c9d6
Merge branch 'check-params' of github.com:Jim8y/neo into check-params
Jim8y Dec 19, 2024
120f7d3
try fix
Jim8y Jul 24, 2024
5e53ee4
revert debug
Jim8y Jul 25, 2024
9c0770e
revert change
Jim8y Jul 25, 2024
32507a9
Merge branch 'check-params' of github.com:Jim8y/neo into check-params
Jim8y Dec 19, 2024
6ff0356
reset
Jim8y Dec 19, 2024
f83e4f3
Merge branch 'check-params' of github.com:Jim8y/neo into check-params
Jim8y Dec 19, 2024
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
5 changes: 4 additions & 1 deletion .devcontainer/devcontainer.dockerfile
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
FROM mcr.microsoft.com/devcontainers/dotnet:9.0-noble
# https://github.com/dotnet/dotnet-docker/blob/main/README.sdk.md
# https://mcr.microsoft.com/en-us/artifact/mar/dotnet/sdk/tags <-- this shows all images
FROM mcr.microsoft.com/dotnet/sdk:9.0.101-noble

# Install the libleveldb-dev package
RUN apt-get update && apt-get install -y libleveldb-dev
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="MSTest.TestFramework" Version="3.6.3" />
<ProjectReference Include="..\..\src\Neo.Extensions\Neo.Extensions.csproj" />
<PackageReference Include="BenchmarkDotNet" Version="0.14.0" />
</ItemGroup>
Expand Down
2 changes: 0 additions & 2 deletions benchmarks/Neo.VM.Benchmarks/Neo.VM.Benchmarks.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,10 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="MSTest.TestFramework" Version="3.6.3" />
<ProjectReference Include="..\..\src\Neo.Extensions\Neo.Extensions.csproj" />
<ProjectReference Include="..\..\src\Neo.Json\Neo.Json.csproj" />
<ProjectReference Include="..\..\src\Neo.VM\Neo.VM.csproj" />
<PackageReference Include="BenchmarkDotNet" Version="0.14.0" />
<ProjectReference Include="..\..\tests\Neo.VM.Tests\Neo.VM.Tests.csproj" />
</ItemGroup>

</Project>
24 changes: 22 additions & 2 deletions benchmarks/Neo.VM.Benchmarks/OpCode/BenchmarkEngine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
// Redistribution and use in source and binary forms with or without
// modifications are permitted.

using Neo.Test.Types;
using Neo.VM.Types;
using System.Diagnostics;
using System.Runtime.CompilerServices;

Expand All @@ -18,12 +18,14 @@ namespace Neo.VM.Benchmark.OpCode
/// <summary>
/// A simple benchmark engine for <see cref="ExecutionEngine"/>.
/// </summary>
public class BenchmarkEngine : TestEngine
public class BenchmarkEngine : ExecutionEngine
{
private readonly Dictionary<VM.OpCode, (int Count, TimeSpan TotalTime)> _opcodeStats = new();
private readonly Dictionary<Script, HashSet<uint>> _breakPoints = new();
private long _gasConsumed = 0;

public BenchmarkEngine() : base(ComposeJumpTable()) { }

/// <summary>
/// Add a breakpoint at the specified position of the specified script. The VM will break the execution when it reaches the breakpoint.
/// </summary>
Expand Down Expand Up @@ -187,5 +189,23 @@ private void PrintOpcodeStats()
$"Avg Time: {kvp.Value.TotalTime.TotalMilliseconds * 1000 / kvp.Value.Count,10:F2} μs");
}
}

private static JumpTable ComposeJumpTable()
{
JumpTable jumpTable = new JumpTable();
jumpTable[VM.OpCode.SYSCALL] = OnSysCall;
return jumpTable;
}

private static void OnSysCall(ExecutionEngine engine, VM.Instruction instruction)
{
uint method = instruction.TokenU32;
if (method == 0x77777777)
engine.CurrentContext!.EvaluationStack.Push(StackItem.FromInterface(new object()));
else if (method == 0xaddeadde)
engine.JumpTable.ExecuteThrow(engine, "error");
else
throw new Exception();
}
}
}
1 change: 0 additions & 1 deletion benchmarks/Neo.VM.Benchmarks/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,6 @@
}

var methods = benchmarkType.GetMethods(BindingFlags.Public | BindingFlags.Instance);

foreach (var method in methods)
{
if (method.DeclaringType == benchmarkType && !method.GetCustomAttributes<GlobalSetupAttribute>().Any())
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Copyright (C) 2015-2024 The Neo Project.
//
// Helper.cs file belongs to the neo project and is free
// ContractParameterExtensions.cs file belongs to the neo project and is free
// software distributed under the MIT software license, see the
// accompanying file LICENSE in the main directory of the
// repository or http://www.opensource.org/licenses/mit-license.php
Expand All @@ -10,8 +10,6 @@
// modifications are permitted.

using Neo.Cryptography.ECC;
using Neo.Extensions;
using Neo.Json;
using Neo.SmartContract;
using Neo.VM.Types;
using System;
Expand All @@ -20,12 +18,9 @@
using System.Numerics;
using Array = Neo.VM.Types.Array;

namespace Neo.VM
namespace Neo.Extensions
{
/// <summary>
/// A helper class related to NeoVM.
/// </summary>
public static class Helper
public static class ContractParameterExtensions
{
/// <summary>
/// Converts the <see cref="ContractParameter"/> to a <see cref="StackItem"/>.
Expand All @@ -46,7 +41,7 @@ private static StackItem ToStackItem(ContractParameter parameter, List<(StackIte
{
case ContractParameterType.Array:
if (context is null)
context = new List<(StackItem, ContractParameter)>();
context = [];
else
(stackItem, _) = context.FirstOrDefault(p => ReferenceEquals(p.Item2, parameter));
if (stackItem is null)
Expand All @@ -57,7 +52,7 @@ private static StackItem ToStackItem(ContractParameter parameter, List<(StackIte
break;
case ContractParameterType.Map:
if (context is null)
context = new List<(StackItem, ContractParameter)>();
context = [];
else
(stackItem, _) = context.FirstOrDefault(p => ReferenceEquals(p.Item2, parameter));
if (stackItem is null)
Expand Down
42 changes: 42 additions & 0 deletions src/Neo/Extensions/SmartContract/GasTokenExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// Copyright (C) 2015-2024 The Neo Project.
//
// GasTokenExtensions.cs file belongs to the neo project and is free
// software distributed under the MIT software license, see the
// accompanying file LICENSE in the main directory of the
// repository or http://www.opensource.org/licenses/mit-license.php
// for more details.
//
// Redistribution and use in source and binary forms with or without
// modifications are permitted.

using Neo.Persistence;
using Neo.SmartContract;
using Neo.SmartContract.Native;
using System;
using System.Collections.Generic;
using System.Numerics;

namespace Neo.Extensions
{
public static class GasTokenExtensions
{
public static IEnumerable<(UInt160 Address, BigInteger Balance)> GetAccounts(this GasToken gasToken, DataCache snapshot)
{
if (gasToken is null)
throw new ArgumentNullException(nameof(gasToken));

if (snapshot is null)
throw new ArgumentNullException(nameof(snapshot));

var kb = new KeyBuilder(gasToken.Id, GasToken.Prefix_Account);
var prefixKey = kb.ToArray();

foreach (var (key, value) in snapshot.Find(prefixKey, SeekDirection.Forward))
{
var keyBytes = key.ToArray();
var addressHash = new UInt160(keyBytes.AsSpan(prefixKey.Length));
yield return new(addressHash, value.GetInteroperable<AccountState>().Balance);
}
}
}
}
13 changes: 5 additions & 8 deletions src/Neo/SmartContract/ApplicationEngine.Runtime.cs
Original file line number Diff line number Diff line change
Expand Up @@ -449,6 +449,7 @@ protected internal Signer[] GetCurrentSigners()
private static bool CheckItemType(StackItem item, ContractParameterType type)
{
StackItemType aType = item.Type;
if (aType == StackItemType.Any) return true;
Copy link
Member

Choose a reason for hiding this comment

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

I don't think it's correct for VM-compatible SC types, because for example, if you require stackitem to be Boolean, then Any just doesn't match this requirement.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I see what you mean, will update.

if (aType == StackItemType.Pointer) return false;
switch (type)
{
Expand All @@ -459,7 +460,7 @@ private static bool CheckItemType(StackItem item, ContractParameterType type)
case ContractParameterType.Integer:
return aType == StackItemType.Integer;
case ContractParameterType.ByteArray:
return aType is StackItemType.Any or StackItemType.ByteString or StackItemType.Buffer;
return aType is StackItemType.ByteString or StackItemType.Buffer;
case ContractParameterType.String:
{
if (aType is StackItemType.ByteString or StackItemType.Buffer)
Expand All @@ -474,27 +475,23 @@ private static bool CheckItemType(StackItem item, ContractParameterType type)
return false;
}
case ContractParameterType.Hash160:
if (aType == StackItemType.Any) return true;
if (aType != StackItemType.ByteString && aType != StackItemType.Buffer) return false;
return item.GetSpan().Length == UInt160.Length;
case ContractParameterType.Hash256:
if (aType == StackItemType.Any) return true;
if (aType != StackItemType.ByteString && aType != StackItemType.Buffer) return false;
return item.GetSpan().Length == UInt256.Length;
case ContractParameterType.PublicKey:
if (aType == StackItemType.Any) return true;
if (aType != StackItemType.ByteString && aType != StackItemType.Buffer) return false;
return item.GetSpan().Length == 33;
case ContractParameterType.Signature:
if (aType == StackItemType.Any) return true;
if (aType != StackItemType.ByteString && aType != StackItemType.Buffer) return false;
return item.GetSpan().Length == 64;
case ContractParameterType.Array:
return aType is StackItemType.Any or StackItemType.Array or StackItemType.Struct;
return aType is StackItemType.Array or StackItemType.Struct;
case ContractParameterType.Map:
return aType is StackItemType.Any or StackItemType.Map;
return aType is StackItemType.Map;
case ContractParameterType.InteropInterface:
return aType is StackItemType.Any or StackItemType.InteropInterface;
return aType is StackItemType.InteropInterface;
default:
return false;
}
Expand Down
4 changes: 4 additions & 0 deletions src/Neo/SmartContract/ApplicationEngine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -325,7 +325,11 @@ private ExecutionContext CallContractInternal(ContractState contract, ContractMe
state.CallingContext = currentContext;

for (int i = args.Count - 1; i >= 0; i--)
{
if (!CheckItemType(args[i], method.Parameters[i].Type))
throw new InvalidOperationException($"The type of the argument `{args[i]}` does not match the formal parameter.");
Copy link
Member

@AnnaShaleva AnnaShaleva Aug 7, 2024

Choose a reason for hiding this comment

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

It may be too harsh for mainnet contracts (even if enabled with hardfrok), this change may break some of them and even prevent them from update. What do you think about using the same approach as it was done for SC Events parameters check (#2810 (comment)): in next release we firstly enable a warning log in case of parameters mismatch, and then in the subsequent release we convert the log to an exception.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It may be too harsh for mainnet contracts (even if enabled with hardfrok), this change may break some of them and even prevent them from update. What do you think about using the same approach as it was done for SC Events parameters check (#2810 (comment)): in next release we firstly enable a warning log in case of parameters mismatch, and then in the subsequent release we convert the log to an exception.

We have discussed this in the last meeting, check will only happen for newly deployed or updated contracts, this pr will be updated.

context_new.EvaluationStack.Push(args[i]);
}

return context_new;
}
Expand Down
2 changes: 1 addition & 1 deletion src/Neo/SmartContract/Native/FungibleToken.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ public abstract class FungibleToken<TState> : NativeContract
/// <summary>
/// The prefix for storing account states.
/// </summary>
protected const byte Prefix_Account = 20;
protected internal const byte Prefix_Account = 20;

/// <summary>
/// Initializes a new instance of the <see cref="FungibleToken{TState}"/> class.
Expand Down
2 changes: 1 addition & 1 deletion src/Neo/UInt160.cs
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ public static bool TryParse(string str, out UInt160 result)
var data = new byte[Length];
for (var i = 0; i < Length; i++)
{
if (!byte.TryParse(str.AsSpan(i * 2 + startIndex, 2), NumberStyles.HexNumber, null, out data[Length - i - 1]))
if (!byte.TryParse(str.AsSpan(i * 2 + startIndex, 2), NumberStyles.AllowHexSpecifier, null, out data[Length - i - 1]))
return false;
}
result = new(data);
Expand Down
8 changes: 8 additions & 0 deletions src/Plugins/RpcServer/RpcException.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,17 @@ namespace Neo.Plugins.RpcServer
{
public class RpcException : Exception
{
private readonly RpcError _rpcError;

public RpcException(RpcError error) : base(error.ErrorMessage)
{
HResult = error.Code;
_rpcError = error;
}

public RpcError GetError()
{
return _rpcError;
}
}
}
25 changes: 20 additions & 5 deletions src/Plugins/RpcServer/RpcServer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -274,7 +274,7 @@ public async Task ProcessAsync(HttpContext context)
await context.Response.WriteAsync(response.ToString(), Encoding.UTF8);
}

private async Task<JObject> ProcessRequestAsync(HttpContext context, JObject request)
internal async Task<JObject> ProcessRequestAsync(HttpContext context, JObject request)
{
if (!request.ContainsProperty("id")) return null;
var @params = request["params"] ?? new JArray();
Expand Down Expand Up @@ -315,11 +315,13 @@ private async Task<JObject> ProcessRequestAsync(HttpContext context, JObject req
{
if (param.ParameterType == typeof(UInt160))
{
args[i] = ParameterConverter.ConvertUInt160(jsonParameters[i], system.Settings.AddressVersion);
args[i] = ParameterConverter.ConvertUInt160(jsonParameters[i],
system.Settings.AddressVersion);
}
else
{
args[i] = ParameterConverter.ConvertParameter(jsonParameters[i], param.ParameterType);
args[i] = ParameterConverter.ConvertParameter(jsonParameters[i],
param.ParameterType);
}
}
catch (Exception e) when (e is not RpcException)
Expand All @@ -333,7 +335,8 @@ private async Task<JObject> ProcessRequestAsync(HttpContext context, JObject req
{
args[i] = param.DefaultValue;
}
else if (param.ParameterType.IsValueType && Nullable.GetUnderlyingType(param.ParameterType) == null)
else if (param.ParameterType.IsValueType &&
Nullable.GetUnderlyingType(param.ParameterType) == null)
{
throw new ArgumentException($"Required parameter '{param.Name}' is missing");
}
Expand Down Expand Up @@ -366,11 +369,23 @@ private async Task<JObject> ProcessRequestAsync(HttpContext context, JObject req
catch (Exception ex) when (ex is not RpcException)
{
#if DEBUG
return CreateErrorResponse(request["id"], RpcErrorFactory.NewCustomError(ex.HResult, ex.Message, ex.StackTrace));
return CreateErrorResponse(request["id"],
RpcErrorFactory.NewCustomError(ex.HResult, ex.Message, ex.StackTrace));
#else
return CreateErrorResponse(request["id"], RpcErrorFactory.NewCustomError(ex.HResult, ex.Message));
#endif
}
catch (RpcException ex)
{

#if DEBUG
return CreateErrorResponse(request["id"],
RpcErrorFactory.NewCustomError(ex.HResult, ex.Message, ex.StackTrace));
#else
return CreateErrorResponse(request["id"], ex.GetError());
#endif

}
}

public void RegisterMethods(object handler)
Expand Down
Loading
Loading