Skip to content

Commit

Permalink
Modify manifest according to the last amendment of NEP-3 (#1481)
Browse files Browse the repository at this point in the history
  • Loading branch information
erikzhang committed Apr 15, 2020
1 parent 966f4cf commit 77cee24
Show file tree
Hide file tree
Showing 17 changed files with 213 additions and 179 deletions.
8 changes: 4 additions & 4 deletions src/neo/SmartContract/ApplicationEngine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -114,20 +114,20 @@ private static Block CreateDummyBlock(StoreView snapshot)
}

public static ApplicationEngine Run(byte[] script, StoreView snapshot,
IVerifiable container = null, Block persistingBlock = null, bool testMode = false, long extraGAS = default)
IVerifiable container = null, Block persistingBlock = null, int offset = 0, bool testMode = false, long extraGAS = default)
{
snapshot.PersistingBlock = persistingBlock ?? snapshot.PersistingBlock ?? CreateDummyBlock(snapshot);
ApplicationEngine engine = new ApplicationEngine(TriggerType.Application, container, snapshot, extraGAS, testMode);
engine.LoadScript(script);
engine.LoadScript(script).InstructionPointer = offset;
engine.Execute();
return engine;
}

public static ApplicationEngine Run(byte[] script, IVerifiable container = null, Block persistingBlock = null, bool testMode = false, long extraGAS = default)
public static ApplicationEngine Run(byte[] script, IVerifiable container = null, Block persistingBlock = null, int offset = 0, bool testMode = false, long extraGAS = default)
{
using (SnapshotView snapshot = Blockchain.Singleton.GetSnapshot())
{
return Run(script, snapshot, container, persistingBlock, testMode, extraGAS);
return Run(script, snapshot, container, persistingBlock, offset, testMode, extraGAS);
}
}

Expand Down
14 changes: 11 additions & 3 deletions src/neo/SmartContract/Helper.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
using Neo.Cryptography;
using Neo.Cryptography.ECC;
using Neo.Ledger;
using Neo.Network.P2P.Payloads;
using Neo.Persistence;
using Neo.SmartContract.Manifest;
using Neo.VM;
using Neo.VM.Types;
using System;
Expand Down Expand Up @@ -143,19 +145,25 @@ internal static bool VerifyWitnesses(this IVerifiable verifiable, StoreView snap
if (hashes.Length != verifiable.Witnesses.Length) return false;
for (int i = 0; i < hashes.Length; i++)
{
int offset;
byte[] verification = verifiable.Witnesses[i].VerificationScript;
if (verification.Length == 0)
{
verification = snapshot.Contracts.TryGet(hashes[i])?.Script;
if (verification is null) return false;
ContractState cs = snapshot.Contracts.TryGet(hashes[i]);
if (cs is null) return false;
ContractMethodDescriptor md = cs.Manifest.Abi.GetMethod("verify");
if (md is null) return false;
verification = cs.Script;
offset = md.Offset;
}
else
{
if (hashes[i] != verifiable.Witnesses[i].ScriptHash) return false;
offset = 0;
}
using (ApplicationEngine engine = new ApplicationEngine(TriggerType.Verification, verifiable, snapshot, gas))
{
engine.LoadScript(verification, CallFlags.ReadOnly);
engine.LoadScript(verification, CallFlags.ReadOnly).InstructionPointer = offset;
engine.LoadScript(verifiable.Witnesses[i].InvocationScript, CallFlags.None);
if (engine.Execute() == VMState.FAULT) return false;
if (!engine.ResultStack.TryPop(out StackItem result) || !result.ToBoolean()) return false;
Expand Down
49 changes: 29 additions & 20 deletions src/neo/SmartContract/InteropService.Contract.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@
using Neo.Ledger;
using Neo.Persistence;
using Neo.SmartContract.Manifest;
using Neo.SmartContract.Native;
using Neo.VM;
using Neo.VM.Types;
using System;
using System.Linq;
using Array = Neo.VM.Types.Array;

namespace Neo.SmartContract
{
Expand Down Expand Up @@ -111,38 +113,33 @@ private static bool Contract_Destroy(ApplicationEngine engine)

private static bool Contract_Call(ApplicationEngine engine)
{
StackItem contractHash = engine.CurrentContext.EvaluationStack.Pop();
StackItem method = engine.CurrentContext.EvaluationStack.Pop();
StackItem args = engine.CurrentContext.EvaluationStack.Pop();
if (!engine.TryPop(out ReadOnlySpan<byte> contractHash)) return false;
if (!engine.TryPop(out string method)) return false;
if (!engine.TryPop(out Array args)) return false;

return Contract_CallEx(engine, new UInt160(contractHash.GetSpan()), method, args, CallFlags.All);
return Contract_CallEx(engine, new UInt160(contractHash), method, args, CallFlags.All);
}

private static bool Contract_CallEx(ApplicationEngine engine)
{
StackItem contractHash = engine.CurrentContext.EvaluationStack.Pop();
StackItem method = engine.CurrentContext.EvaluationStack.Pop();
StackItem args = engine.CurrentContext.EvaluationStack.Pop();
if (!engine.TryPop(out ReadOnlySpan<byte> contractHash)) return false;
if (!engine.TryPop(out string method)) return false;
if (!engine.TryPop(out Array args)) return false;
if (!engine.TryPop(out int flags)) return false;

if (!engine.CurrentContext.EvaluationStack.TryPop<PrimitiveType>(out var flagItem))
{
return false;
}
if (!Enum.IsDefined(typeof(CallFlags), (CallFlags)flags)) return false;

CallFlags flags = (CallFlags)(int)flagItem.ToBigInteger();
if (!Enum.IsDefined(typeof(CallFlags), flags)) return false;

return Contract_CallEx(engine, new UInt160(contractHash.GetSpan()), method, args, flags);
return Contract_CallEx(engine, new UInt160(contractHash), method, args, (CallFlags)flags);
}

private static bool Contract_CallEx(ApplicationEngine engine, UInt160 contractHash, StackItem method, StackItem args, CallFlags flags)
private static bool Contract_CallEx(ApplicationEngine engine, UInt160 contractHash, string method, Array args, CallFlags flags)
{
ContractState contract = engine.Snapshot.Contracts.TryGet(contractHash);
if (contract is null) return false;

ContractManifest currentManifest = engine.Snapshot.Contracts.TryGet(engine.CurrentScriptHash)?.Manifest;

if (currentManifest != null && !currentManifest.CanCall(contract.Manifest, method.GetString()))
if (currentManifest != null && !currentManifest.CanCall(contract.Manifest, method))
return false;

if (engine.InvocationCounter.TryGetValue(contract.ScriptHash, out var counter))
Expand All @@ -158,13 +155,25 @@ private static bool Contract_CallEx(ApplicationEngine engine, UInt160 contractHa
UInt160 callingScriptHash = state.ScriptHash;
CallFlags callingFlags = state.CallFlags;

ExecutionContext context_new = engine.LoadScript(contract.Script, 1);
ContractMethodDescriptor md = contract.Manifest.Abi.GetMethod(method);
if (md is null) return false;
int rvcount = md.ReturnType == ContractParameterType.Void ? 0 : 1;
ExecutionContext context_new = engine.LoadScript(contract.Script, rvcount);
state = context_new.GetState<ExecutionContextState>();
state.CallingScriptHash = callingScriptHash;
state.CallFlags = flags & callingFlags;

context_new.EvaluationStack.Push(args);
context_new.EvaluationStack.Push(method);
if (NativeContract.IsNative(contractHash))
{
context_new.EvaluationStack.Push(args);
context_new.EvaluationStack.Push(method);
}
else
{
for (int i = args.Count - 1; i >= 0; i--)
context_new.EvaluationStack.Push(args[i]);
context_new.InstructionPointer = md.Offset;
}
return true;
}

Expand Down
18 changes: 10 additions & 8 deletions src/neo/SmartContract/Manifest/ContractAbi.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Neo.IO.Json;
using System.Collections.Generic;
using System.Linq;

namespace Neo.SmartContract.Manifest
Expand All @@ -8,16 +9,13 @@ namespace Neo.SmartContract.Manifest
/// </summary>
public class ContractAbi
{
private IReadOnlyDictionary<string, ContractMethodDescriptor> methodDictionary;

/// <summary>
/// Hash is the script hash of the contract. It is encoded as a hexadecimal string in big-endian.
/// </summary>
public UInt160 Hash { get; set; }

/// <summary>
/// Entrypoint is a Method object which describe the details of the entrypoint of the contract.
/// </summary>
public ContractMethodDescriptor EntryPoint { get; set; }

/// <summary>
/// Methods is an array of Method objects which describe the details of each method in the contract.
/// </summary>
Expand All @@ -33,7 +31,6 @@ public ContractAbi Clone()
return new ContractAbi
{
Hash = Hash,
EntryPoint = EntryPoint.Clone(),
Methods = Methods.Select(p => p.Clone()).ToArray(),
Events = Events.Select(p => p.Clone()).ToArray()
};
Expand All @@ -49,17 +46,22 @@ public static ContractAbi FromJson(JObject json)
return new ContractAbi
{
Hash = UInt160.Parse(json["hash"].AsString()),
EntryPoint = ContractMethodDescriptor.FromJson(json["entryPoint"]),
Methods = ((JArray)json["methods"]).Select(u => ContractMethodDescriptor.FromJson(u)).ToArray(),
Events = ((JArray)json["events"]).Select(u => ContractEventDescriptor.FromJson(u)).ToArray()
};
}

public ContractMethodDescriptor GetMethod(string name)
{
methodDictionary ??= Methods.ToDictionary(p => p.Name);
methodDictionary.TryGetValue(name, out var method);
return method;
}

public JObject ToJson()
{
var json = new JObject();
json["hash"] = Hash.ToString();
json["entryPoint"] = EntryPoint.ToJson();
json["methods"] = new JArray(Methods.Select(u => u.ToJson()).ToArray());
json["events"] = new JArray(Events.Select(u => u.ToJson()).ToArray());
return json;
Expand Down
27 changes: 1 addition & 26 deletions src/neo/SmartContract/Manifest/ContractManifest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public class ContractManifest : ISerializable
/// <summary>
/// Max length for a valid Contract Manifest
/// </summary>
public const int MaxLength = 2048;
public const int MaxLength = 4096;

/// <summary>
/// Serialized size
Expand Down Expand Up @@ -64,31 +64,6 @@ public class ContractManifest : ISerializable
/// </summary>
public JObject Extra { get; set; }

/// <summary>
/// Create Default Contract manifest
/// </summary>
/// <param name="hash">Hash</param>
/// <returns>Return default manifest for this contract</returns>
public static ContractManifest CreateDefault(UInt160 hash)
{
return new ContractManifest()
{
Permissions = new[] { ContractPermission.DefaultPermission },
Abi = new ContractAbi()
{
Hash = hash,
EntryPoint = ContractMethodDescriptor.DefaultEntryPoint,
Events = new ContractEventDescriptor[0],
Methods = new ContractMethodDescriptor[0]
},
Features = ContractFeatures.NoProperty,
Groups = new ContractGroup[0],
SafeMethods = WildcardContainer<string>.Create(),
Trusts = WildcardContainer<UInt160>.Create(),
Extra = null,
};
}

/// <summary>
/// Return true if is allowed
/// </summary>
Expand Down
29 changes: 9 additions & 20 deletions src/neo/SmartContract/Manifest/ContractMethodDescriptor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,27 +6,13 @@ namespace Neo.SmartContract.Manifest
{
public class ContractMethodDescriptor : ContractEventDescriptor
{
/// <summary>
/// Default entry point
/// </summary>
public static readonly ContractMethodDescriptor DefaultEntryPoint = new ContractMethodDescriptor()
private int _offset;

public int Offset
{
Name = "Main",
Parameters = new ContractParameterDefinition[]
{
new ContractParameterDefinition()
{
Name = "operation",
Type = ContractParameterType.String
},
new ContractParameterDefinition()
{
Name = "args",
Type = ContractParameterType.Array
}
},
ReturnType = ContractParameterType.Any
};
get => _offset;
set => _offset = value >= 0 ? value : throw new FormatException();
}

/// <summary>
/// Returntype indicates the return type of the method. It can be one of the following values:
Expand All @@ -40,6 +26,7 @@ public new ContractMethodDescriptor Clone()
{
Name = Name,
Parameters = Parameters.Select(p => p.Clone()).ToArray(),
Offset = Offset,
ReturnType = ReturnType
};
}
Expand All @@ -55,13 +42,15 @@ public new static ContractMethodDescriptor FromJson(JObject json)
{
Name = json["name"].AsString(),
Parameters = ((JArray)json["parameters"]).Select(u => ContractParameterDefinition.FromJson(u)).ToArray(),
Offset = (int)json["offset"].AsNumber(),
ReturnType = (ContractParameterType)Enum.Parse(typeof(ContractParameterType), json["returnType"].AsString()),
};
}

public override JObject ToJson()
{
var json = base.ToJson();
json["offset"] = Offset;
json["returnType"] = ReturnType.ToString();
return json;
}
Expand Down
31 changes: 25 additions & 6 deletions src/neo/SmartContract/Native/NativeContract.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,11 @@ namespace Neo.SmartContract.Native
{
public abstract class NativeContract
{
private static readonly List<NativeContract> contracts = new List<NativeContract>();
private static readonly List<NativeContract> contractsList = new List<NativeContract>();
private static readonly Dictionary<UInt160, NativeContract> contractsDictionary = new Dictionary<UInt160, NativeContract>();
private readonly Dictionary<string, ContractMethodMetadata> methods = new Dictionary<string, ContractMethodMetadata>();

public static IReadOnlyCollection<NativeContract> Contracts { get; } = contracts;
public static IReadOnlyCollection<NativeContract> Contracts { get; } = contractsList;
public static NeoToken NEO { get; } = new NeoToken();
public static GasToken GAS { get; } = new GasToken();
public static PolicyContract Policy { get; } = new PolicyContract();
Expand All @@ -42,7 +43,6 @@ protected NativeContract()
this.Script = sb.ToArray();
}
this.Hash = Script.ToScriptHash();
this.Manifest = ContractManifest.CreateDefault(this.Hash);
List<ContractMethodDescriptor> descriptors = new List<ContractMethodDescriptor>();
List<string> safeMethods = new List<string>();
foreach (MethodInfo method in GetType().GetMethods(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public))
Expand All @@ -64,9 +64,23 @@ protected NativeContract()
RequiredCallFlags = attribute.SafeMethod ? CallFlags.None : CallFlags.AllowModifyStates
});
}
this.Manifest.Abi.Methods = descriptors.ToArray();
this.Manifest.SafeMethods = WildcardContainer<string>.Create(safeMethods.ToArray());
contracts.Add(this);
this.Manifest = new ContractManifest
{
Permissions = new[] { ContractPermission.DefaultPermission },
Abi = new ContractAbi()
{
Hash = Hash,
Events = new ContractEventDescriptor[0],
Methods = descriptors.ToArray()
},
Features = ContractFeatures.NoProperty,
Groups = new ContractGroup[0],
SafeMethods = WildcardContainer<string>.Create(safeMethods.ToArray()),
Trusts = WildcardContainer<UInt160>.Create(),
Extra = null,
};
contractsList.Add(this);
contractsDictionary.Add(Hash, this);
}

protected StorageKey CreateStorageKey(byte prefix, byte[] key = null)
Expand Down Expand Up @@ -102,6 +116,11 @@ internal bool Invoke(ApplicationEngine engine)
return true;
}

public static bool IsNative(UInt160 hash)
{
return contractsDictionary.ContainsKey(hash);
}

internal long GetPrice(EvaluationStack stack, StoreView snapshot)
{
return methods.TryGetValue(stack.Peek().GetString(), out ContractMethodMetadata method) ? method.Price : 0;
Expand Down
4 changes: 2 additions & 2 deletions tests/neo.UnitTests/Ledger/UT_ContractState.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public class UT_ContractState
[TestInitialize]
public void TestSetup()
{
manifest = ContractManifest.CreateDefault(UInt160.Parse("0xa400ff00ff00ff00ff00ff00ff00ff00ff00ff01"));
manifest = TestUtils.CreateDefaultManifest(UInt160.Parse("0xa400ff00ff00ff00ff00ff00ff00ff00ff00ff01"));
contract = new ContractState
{
Script = script,
Expand Down Expand Up @@ -84,7 +84,7 @@ public void TestDeserialize()
public void TestGetSize()
{
ISerializable newContract = contract;
newContract.Size.Should().Be(372);
newContract.Size.Should().Be(239);
}

[TestMethod]
Expand Down
Loading

0 comments on commit 77cee24

Please sign in to comment.