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

Modify manifest according to the last amendment of NEP-3 #1481

Merged
merged 20 commits into from
Apr 15, 2020
Merged
Show file tree
Hide file tree
Changes from 19 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
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");
shargon marked this conversation as resolved.
Show resolved Hide resolved
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);
erikzhang marked this conversation as resolved.
Show resolved Hide resolved
if (md is null) return false;
int rvcount = md.ReturnType == ContractParameterType.Void ? 0 : 1;
erikzhang marked this conversation as resolved.
Show resolved Hide resolved
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))
Copy link
Member

Choose a reason for hiding this comment

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

Native is native?
Is initialized?

Copy link
Member Author

Choose a reason for hiding this comment

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

Native contracts have no code, so you cannot find methods by offset. Therefore, Contract.Call needs to check whether the called contract is native.

{
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)
shargon marked this conversation as resolved.
Show resolved Hide resolved
{
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
25 changes: 0 additions & 25 deletions src/neo/SmartContract/Manifest/ContractManifest.cs
Original file line number Diff line number Diff line change
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 class ContractMethodDescriptor : ContractEventDescriptor
{
Name = Name,
Parameters = Parameters.Select(p => p.Clone()).ToArray(),
Offset = Offset,
ReturnType = ReturnType
};
}
Expand All @@ -55,13 +42,15 @@ public class ContractMethodDescriptor : ContractEventDescriptor
{
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