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
101 changes: 101 additions & 0 deletions src/Plugins/RpcServer/ParameterConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,15 @@
// Redistribution and use in source and binary forms with or without
// modifications are permitted.

using Neo.Cryptography.ECC;
using Neo.Extensions;
using Neo.Json;
using Neo.Network.P2P.Payloads;
using Neo.Plugins.RpcServer.Model;
using Neo.Wallets;
using System;
using System.Collections.Generic;
using System.Linq;
using JToken = Neo.Json.JToken;

namespace Neo.Plugins.RpcServer
Expand Down Expand Up @@ -138,6 +142,103 @@ private static RpcError CreateInvalidParamError<T>(JToken token)
{
return RpcError.InvalidParams.WithData($"Invalid {typeof(T)} value: {token}");
}

/// <summary>
/// Create a SignersAndWitnesses from a JSON array.
/// Each item in the JSON array should be a JSON object with the following properties:
/// - "signer": A JSON object with the following properties:
/// - "account": A hex-encoded UInt160 or a Base58Check address, required.
/// - "scopes": A enum string representing the scopes(WitnessScope) of the signer, required.
/// - "allowedcontracts": An array of hex-encoded UInt160, optional.
/// - "allowedgroups": An array of hex-encoded ECPoint, optional.
/// - "rules": An array of strings representing the rules(WitnessRule) of the signer, optional.
/// - "witness": A JSON object with the following properties:
/// - "invocation": A base64-encoded string representing the invocation script, optional.
/// - "verification": A base64-encoded string representing the verification script, optional.
/// </summary>
/// <param name="json">The JSON array to create a SignersAndWitnesses from.</param>
/// <param name="addressVersion">The address version to use for the signers.</param>
/// <returns>A SignersAndWitnesses object.</returns>
/// <exception cref="RpcException">Thrown when the JSON array is invalid.</exception>
internal static (Signer[] Signers, Witness[] Witnesses) ToSignersAndWitnesses(this JArray json, byte addressVersion)
{
var signers = json.ToSigners(addressVersion);
var witnesses = json.ToWitnesses();
return new(signers, witnesses);
}

/// <summary>
/// Create a Signer array from a JSON array.
/// Each item in the JSON array should be a JSON object with the following properties:
/// - "account": A hex-encoded UInt160 or a Base58Check address, required.
/// - "scopes": A enum string representing the scopes(WitnessScope) of the signer, required.
/// - "allowedcontracts": An array of hex-encoded UInt160, optional.
/// - "allowedgroups": An array of hex-encoded ECPoint, optional.
/// - "rules": An array of strings representing the rules(WitnessRule) of the signer, optional.
/// </summary>
/// <param name="json">The JSON array to create a Signer array from.</param>
/// <param name="addressVersion">The address version to use for the signers.</param>
/// <returns>A Signer array.</returns>
/// <exception cref="RpcException">Thrown when the JSON array is invalid or max allowed witness exceeded.</exception>
internal static Signer[] ToSigners(this JArray json, byte addressVersion)
{
if (json.Count > Transaction.MaxTransactionAttributes)
throw new RpcException(RpcError.InvalidParams.WithData("Max allowed witness exceeded."));

var ret = json.Select(u => new Signer
{
Account = u["account"].AsString().AddressToScriptHash(addressVersion),
Scopes = (WitnessScope)Enum.Parse(typeof(WitnessScope), u["scopes"]?.AsString()),
AllowedContracts = ((JArray)u["allowedcontracts"])?.Select(p => UInt160.Parse(p.AsString())).ToArray() ?? [],
AllowedGroups = ((JArray)u["allowedgroups"])?.Select(p => ECPoint.Parse(p.AsString(), ECCurve.Secp256r1)).ToArray() ?? [],
Rules = ((JArray)u["rules"])?.Select(r => WitnessRule.FromJson((JObject)r)).ToArray() ?? [],
}).ToArray();

// Validate format
_ = ret.ToByteArray().AsSerializableArray<Signer>();
return ret;
}

/// <summary>
/// Create a Witness array from a JSON array.
/// Each item in the JSON array should be a JSON object with the following properties:
/// - "invocation": A base64-encoded string representing the invocation script, optional.
/// - "verification": A base64-encoded string representing the verification script, optional.
/// </summary>
/// <param name="json">The JSON array to create a Witness array from.</param>
/// <returns>A Witness array.</returns>
/// <exception cref="RpcException">Thrown when the JSON array is invalid or max allowed witness exceeded.</exception>
internal static Witness[] ToWitnesses(this JArray json)
{
if (json.Count > Transaction.MaxTransactionAttributes)
throw new RpcException(RpcError.InvalidParams.WithData("Max allowed witness exceeded."));

return json.Select(u => new
{
Invocation = u["invocation"]?.AsString(),
Verification = u["verification"]?.AsString()
})
.Where(x => x.Invocation != null || x.Verification != null)
.Select(x => new Witness()
{
InvocationScript = Convert.FromBase64String(x.Invocation ?? string.Empty),
VerificationScript = Convert.FromBase64String(x.Verification ?? string.Empty)
})
.ToArray();
}

/// <summary>
/// Converts an hex-encoded UInt160 or a Base58Check address to a script hash.
/// </summary>
/// <param name="address">The address to convert.</param>
/// <param name="version">The address version to use for the conversion.</param>
/// <returns>The script hash corresponding to the address.</returns>
internal static UInt160 AddressToScriptHash(this string address, byte version)
{
if (UInt160.TryParse(address, out var scriptHash))
return scriptHash;
return address.ToScriptHash(version);
}
}

public static class TypeExtensions
Expand Down
112 changes: 42 additions & 70 deletions src/Plugins/RpcServer/RpcServer.SmartContract.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
// Redistribution and use in source and binary forms with or without
// modifications are permitted.

using Neo.Cryptography.ECC;
using Neo.Extensions;
using Neo.Json;
using Neo.Network.P2P.Payloads;
Expand All @@ -24,7 +23,6 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using Array = System.Array;

namespace Neo.Plugins.RpcServer
{
Expand Down Expand Up @@ -171,47 +169,6 @@ private static JObject ToJson(StackItem item, Session session)
return json;
}

private static Signer[] SignersFromJson(JArray _params, ProtocolSettings settings)
{
if (_params.Count > Transaction.MaxTransactionAttributes)
{
throw new RpcException(RpcError.InvalidParams.WithData("Max allowed witness exceeded."));
}

var ret = _params.Select(u => new Signer
{
Account = AddressToScriptHash(u["account"].AsString(), settings.AddressVersion),
Scopes = (WitnessScope)Enum.Parse(typeof(WitnessScope), u["scopes"]?.AsString()),
AllowedContracts = ((JArray)u["allowedcontracts"])?.Select(p => UInt160.Parse(p.AsString())).ToArray() ?? Array.Empty<UInt160>(),
AllowedGroups = ((JArray)u["allowedgroups"])?.Select(p => ECPoint.Parse(p.AsString(), ECCurve.Secp256r1)).ToArray() ?? Array.Empty<ECPoint>(),
Rules = ((JArray)u["rules"])?.Select(r => WitnessRule.FromJson((JObject)r)).ToArray() ?? Array.Empty<WitnessRule>(),
}).ToArray();

// Validate format

_ = ret.ToByteArray().AsSerializableArray<Signer>();

return ret;
}

private static Witness[] WitnessesFromJson(JArray _params)
{
if (_params.Count > Transaction.MaxTransactionAttributes)
{
throw new RpcException(RpcError.InvalidParams.WithData("Max allowed witness exceeded."));
}

return _params.Select(u => new
{
Invocation = u["invocation"]?.AsString(),
Verification = u["verification"]?.AsString()
}).Where(x => x.Invocation != null || x.Verification != null).Select(x => new Witness()
{
InvocationScript = Convert.FromBase64String(x.Invocation ?? string.Empty),
VerificationScript = Convert.FromBase64String(x.Verification ?? string.Empty)
}).ToArray();
}

/// <summary>
/// Invokes a function on a contract.
/// <para>Request format:</para>
Expand All @@ -224,13 +181,16 @@ private static Witness[] WitnessesFromJson(JArray _params)
/// "operation", // the operation to invoke
/// [{"type": "ContractParameterType", "value": "The parameter value"}], // ContractParameter, the arguments
/// [{
/// "account": "An UInt160 or Base58Check address",
/// "scopes": "WitnessScope", // WitnessScope
/// // The part of the Signer
/// "account": "An UInt160 or Base58Check address", // The account of the signer, required
/// "scopes": "WitnessScope", // WitnessScope, required
/// "allowedcontracts": ["The contract hash(UInt160)"], // optional
/// "allowedgroups": ["PublicKey"], // ECPoint, i.e. ECC PublicKey, optional
/// "rules": [{"action": "WitnessRuleAction", "condition": {/*A json of WitnessCondition*/}}] // WitnessRule
/// }], // A Signer array, optional
/// [{"invocation": "A Base64-encoded string","verification": "A Base64-encoded string"}] // A Witness array, optional
/// // The part of the Witness, optional
/// "invocation": "A Base64-encoded string",
/// "verification": "A Base64-encoded string"
/// }], // A JSON array of signers and witnesses, optional
/// false // useDiagnostic, a bool value indicating whether to use diagnostic information, optional
/// ]
/// }</code>
Expand Down Expand Up @@ -266,9 +226,8 @@ private static Witness[] WitnessesFromJson(JArray _params)
/// [0]: The script hash of the contract to invoke as a string.
/// [1]: The operation to invoke as a string.
/// [2]: The arguments to pass to the function as an array of ContractParameter. Optional.
/// [3]: The signers as an array of Signer. Optional.
/// [4]: The witnesses as an array of Witness. Optional.
/// [5]: A boolean value indicating whether to use diagnostic information. Optional.
/// [3]: The JSON array of signers and witnesses<see cref="ParameterConverter.ToSignersAndWitnesses"/>. Optional.
/// [4]: A boolean value indicating whether to use diagnostic information. Optional.
/// </param>
/// <returns>The result of the function invocation.</returns>
/// <exception cref="RpcException">
Expand All @@ -277,17 +236,24 @@ private static Witness[] WitnessesFromJson(JArray _params)
[RpcMethod]
protected internal virtual JToken InvokeFunction(JArray _params)
{
UInt160 script_hash = Result.Ok_Or(() => UInt160.Parse(_params[0].AsString()), RpcError.InvalidParams.WithData($"Invalid script hash {nameof(script_hash)}"));
string operation = Result.Ok_Or(() => _params[1].AsString(), RpcError.InvalidParams);
ContractParameter[] args = _params.Count >= 3 ? ((JArray)_params[2]).Select(p => ContractParameter.FromJson((JObject)p)).ToArray() : [];
Signer[] signers = _params.Count >= 4 ? SignersFromJson((JArray)_params[3], system.Settings) : null;
Witness[] witnesses = _params.Count >= 4 ? WitnessesFromJson((JArray)_params[3]) : null;
bool useDiagnostic = _params.Count >= 5 && _params[4].GetBoolean();
var scriptHash = Result.Ok_Or(() => UInt160.Parse(_params[0].AsString()),
RpcError.InvalidParams.WithData($"Invalid script hash `{_params[0]}`"));

var operation = Result.Ok_Or(() => _params[1].AsString(), RpcError.InvalidParams);
var args = _params.Count >= 3
? ((JArray)_params[2]).Select(p => ContractParameter.FromJson((JObject)p)).ToArray()
: [];

var (signers, witnesses) = _params.Count >= 4
? ((JArray)_params[3]).ToSignersAndWitnesses(system.Settings.AddressVersion)
: (null, null);

var useDiagnostic = _params.Count >= 5 && _params[4].GetBoolean();

byte[] script;
using (ScriptBuilder sb = new())
{
script = sb.EmitDynamicCall(script_hash, operation, args).ToArray();
script = sb.EmitDynamicCall(scriptHash, operation, args).ToArray();
}
return GetInvokeResult(script, signers, witnesses, useDiagnostic);
}
Expand All @@ -302,13 +268,16 @@ protected internal virtual JToken InvokeFunction(JArray _params)
/// "params": [
/// "A Base64-encoded script", // the script to invoke
/// [{
/// "account": "An UInt160 or Base58Check address",
/// "scopes": "WitnessScope", // WitnessScope
/// // The part of the Signer
/// "account": "An UInt160 or Base58Check address", // The account of the signer, required
/// "scopes": "WitnessScope", // WitnessScope, required
/// "allowedcontracts": ["The contract hash(UInt160)"], // optional
/// "allowedgroups": ["PublicKey"], // ECPoint, i.e. ECC PublicKey, optional
/// "rules": [{"action": "WitnessRuleAction", "condition": {A json of WitnessCondition}}] // WitnessRule
/// }], // A Signer array, optional
/// [{"invocation": "A Base64-encoded string","verification": "A Base64-encoded string"}] // A Witness array, optional
/// "rules": [{"action": "WitnessRuleAction", "condition": {/* A json of WitnessCondition */ }}], // WitnessRule
/// // The part of the Witness, optional
/// "invocation": "A Base64-encoded string",
/// "verification": "A Base64-encoded string"
/// }], // A JSON array of signers and witnesses, optional
/// false // useDiagnostic, a bool value indicating whether to use diagnostic information, optional
/// ]
/// }</code>
Expand Down Expand Up @@ -348,8 +317,7 @@ protected internal virtual JToken InvokeFunction(JArray _params)
/// </summary>
/// <param name="_params">An array containing the following elements:
/// [0]: The script as a Base64-encoded string.
/// [1]: The signers as an array of Signer. Optional.
/// [2]: The witnesses as an array of Witness. Optional.
/// [1]: The JSON array of signers and witnesses<see cref="ParameterConverter.ToSignersAndWitnesses"/>. Optional.
/// [3]: A boolean value indicating whether to use diagnostic information. Optional.
/// </param>
/// <returns>The result of the script invocation.</returns>
Expand All @@ -359,10 +327,11 @@ protected internal virtual JToken InvokeFunction(JArray _params)
[RpcMethod]
protected internal virtual JToken InvokeScript(JArray _params)
{
byte[] script = Result.Ok_Or(() => Convert.FromBase64String(_params[0].AsString()), RpcError.InvalidParams);
Signer[] signers = _params.Count >= 2 ? SignersFromJson((JArray)_params[1], system.Settings) : null;
Witness[] witnesses = _params.Count >= 2 ? WitnessesFromJson((JArray)_params[1]) : null;
bool useDiagnostic = _params.Count >= 3 && _params[2].GetBoolean();
var script = Result.Ok_Or(() => Convert.FromBase64String(_params[0].AsString()), RpcError.InvalidParams);
var (signers, witnesses) = _params.Count >= 2
? ((JArray)_params[1]).ToSignersAndWitnesses(system.Settings.AddressVersion)
: (null, null);
var useDiagnostic = _params.Count >= 3 && _params[2].GetBoolean();
return GetInvokeResult(script, signers, witnesses, useDiagnostic);
}

Expand Down Expand Up @@ -469,9 +438,12 @@ protected internal virtual JToken TerminateSession(JArray _params)
[RpcMethod]
protected internal virtual JToken GetUnclaimedGas(JArray _params)
{
string address = Result.Ok_Or(() => _params[0].AsString(), RpcError.InvalidParams.WithData($"Invalid address {nameof(address)}"));
var address = Result.Ok_Or(() => _params[0].AsString(),
RpcError.InvalidParams.WithData($"Invalid address `{_params[0]}`"));
var json = new JObject();
UInt160 scriptHash = Result.Ok_Or(() => AddressToScriptHash(address, system.Settings.AddressVersion), RpcError.InvalidParams);
var scriptHash = Result.Ok_Or(
() => address.AddressToScriptHash(system.Settings.AddressVersion),
RpcError.InvalidParams.WithData($"Invalid address `{address}`"));

var snapshot = system.StoreView;
json["unclaimed"] = NativeContract.NEO.UnclaimedGas(snapshot, scriptHash, NativeContract.Ledger.CurrentIndex(snapshot) + 1).ToString();
Expand Down
Loading
Loading