Skip to content

Commit

Permalink
Adapt to Script Hash Identification Change (#90)
Browse files Browse the repository at this point in the history
* WIP

* contract lookup by name

* print VM unhandled exception if present

* color output on log

* write recursive exceptions on invoke failure

* nep5 -> nep17

* adapt to trace debug model changes

* add json output options

* update dependencies

* contract list --json

Co-authored-by: Harry <harrypierson@ngd.neo.org>
  • Loading branch information
devhawk and Harry authored Dec 8, 2020
1 parent 37f8407 commit e70c441
Show file tree
Hide file tree
Showing 19 changed files with 233 additions and 108 deletions.
53 changes: 33 additions & 20 deletions src/neo3/BlockchainOperations.cs
Original file line number Diff line number Diff line change
Expand Up @@ -418,16 +418,7 @@ static async Task<UInt160> ParseAssetAsync(Node.IExpressNode expressNode, string
return uint160;
}

if (asset.EndsWith(".nef"))
{
var parser = new ContractParameterParser();
if (parser.TryLoadScriptHash(asset, System.Environment.CurrentDirectory, out var scriptHash))
{
return scriptHash;
}
}

var contracts = await expressNode.ListNep5ContractsAsync().ConfigureAwait(false);
var contracts = await expressNode.ListNep17ContractsAsync().ConfigureAwait(false);
for (int i = 0; i < contracts.Count; i++)
{
if (contracts[i].Symbol.Equals(asset, StringComparison.CurrentCultureIgnoreCase))
Expand All @@ -453,7 +444,7 @@ public async Task<UInt256> DeployContract(ExpressChain chain, string contract, E
var (nefFile, manifest) = await LoadContract(contract).ConfigureAwait(false);

using var sb = new ScriptBuilder();
sb.EmitSysCall(ApplicationEngine.System_Contract_Create, nefFile.Script, manifest.ToString());
sb.EmitSysCall(ApplicationEngine.System_Contract_Create, nefFile.ToArray(), manifest.ToString());
return await expressNode.ExecuteAsync(chain, account, sb.ToArray()).ConfigureAwait(false);

static async Task<(NefFile nefFile, ContractManifest manifest)> LoadContract(string contractPath)
Expand Down Expand Up @@ -481,7 +472,8 @@ public async Task<UInt256> InvokeContract(ExpressChain chain, string invocationF
}

using var expressNode = chain.GetExpressNode(trace);
var parser = GetContractParameterParser(chain);
var contracts = await expressNode.ListContractsAsync().ConfigureAwait(false);
var parser = GetContractParameterParser(chain, contracts);
var script = await parser.LoadInvocationScriptAsync(invocationFilePath).ConfigureAwait(false);
return await expressNode.ExecuteAsync(chain, account, script, additionalGas).ConfigureAwait(false);
}
Expand All @@ -494,14 +486,15 @@ public async Task<InvokeResult> TestInvokeContract(ExpressChain chain, string in
}

using var expressNode = chain.GetExpressNode();
var parser = GetContractParameterParser(chain);
var contracts = await expressNode.ListContractsAsync().ConfigureAwait(false);
var parser = GetContractParameterParser(chain, contracts);
var script = await parser.LoadInvocationScriptAsync(invocationFilePath).ConfigureAwait(false);
return await expressNode.InvokeAsync(script).ConfigureAwait(false);
}

ContractParameterParser GetContractParameterParser(ExpressChain chain)
ContractParameterParser GetContractParameterParser(ExpressChain chain, IReadOnlyList<(UInt160 hash, ContractManifest manifest)> contracts)
{
ContractParameterParser.TryGetAccount tryGetAccount = (string name, out UInt160 scriptHash) =>
ContractParameterParser.TryGetUInt160 tryGetAccount = (string name, out UInt160 scriptHash) =>
{
var account = GetAccount(chain, name);
if (account != null)
Expand All @@ -514,7 +507,27 @@ ContractParameterParser GetContractParameterParser(ExpressChain chain)
return false;
};

return new ContractParameterParser(new System.IO.Abstractions.FileSystem(), tryGetAccount);
var lookup = contracts.ToDictionary(c => c.manifest.Name, c => c.hash);
ContractParameterParser.TryGetUInt160 tryGetContract = (string name, out UInt160 scriptHash) =>
{
if (lookup.TryGetValue(name, out scriptHash))
{
return true;
}

foreach (var kvp in lookup)
{
if (string.Equals(name, kvp.Key, StringComparison.OrdinalIgnoreCase))
{
scriptHash = kvp.Value;
return true;
}
}

return false;
};

return new ContractParameterParser(tryGetAccount, tryGetContract);
}

public ExpressWalletAccount? GetAccount(ExpressChain chain, string name)
Expand Down Expand Up @@ -543,7 +556,7 @@ ContractParameterParser GetContractParameterParser(ExpressChain chain)
return null;
}

public async Task<(BigDecimal balance, Nep5Contract contract)> ShowBalance(ExpressChain chain, ExpressWalletAccount account, string asset)
public async Task<(BigDecimal balance, Nep17Contract contract)> ShowBalance(ExpressChain chain, ExpressWalletAccount account, string asset)
{
if (!NodeUtility.InitializeProtocolSettings(chain))
{
Expand All @@ -569,13 +582,13 @@ ContractParameterParser GetContractParameterParser(ExpressChain chain)
var symbol = Encoding.UTF8.GetString(stack[2].GetSpan());
var decimals = (byte)(stack[3].GetInteger());

return (new BigDecimal(balance, decimals), new Nep5Contract(name, symbol, decimals, assetHash));
return (new BigDecimal(balance, decimals), new Nep17Contract(name, symbol, decimals, assetHash));
}

throw new Exception("invalid script results");
}

public async Task<(RpcNep5Balance balance, Nep5Contract contract)[]> GetBalances(ExpressChain chain, ExpressWalletAccount account)
public async Task<(RpcNep17Balance balance, Nep17Contract contract)[]> GetBalances(ExpressChain chain, ExpressWalletAccount account)
{
if (!NodeUtility.InitializeProtocolSettings(chain))
{
Expand Down Expand Up @@ -650,7 +663,7 @@ public async Task<ContractManifest> GetContract(ExpressChain chain, string hashO
return await expressNode.GetContractAsync(scriptHash);
}

public async Task<IReadOnlyList<ContractManifest>> ListContracts(ExpressChain chain)
public async Task<IReadOnlyList<(UInt160 hash, ContractManifest manifest)>> ListContracts(ExpressChain chain)
{
if (!NodeUtility.InitializeProtocolSettings(chain))
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,24 +6,24 @@

namespace NeoExpress.Neo3.Models
{
public struct Nep5Contract
public struct Nep17Contract
{
public readonly string Name;
public readonly string Symbol;
public readonly byte Decimals;
public readonly UInt160 ScriptHash;

public Nep5Contract(string name, string symbol, byte decimals, UInt160 scriptHash)
public Nep17Contract(string name, string symbol, byte decimals, UInt160 scriptHash)
{
Name = name;
Symbol = symbol;
Decimals = decimals;
ScriptHash = scriptHash;
}

public static Nep5Contract Unknown(UInt160 scriptHash) => new Nep5Contract("unknown", "unknown", 0, scriptHash);
public static Nep17Contract Unknown(UInt160 scriptHash) => new Nep17Contract("unknown", "unknown", 0, scriptHash);

public static bool TryLoad(IReadOnlyStore store, UInt160 scriptHash, out Nep5Contract contract)
public static bool TryLoad(IReadOnlyStore store, UInt160 scriptHash, out Nep17Contract contract)
{
using var sb = new ScriptBuilder();
sb.EmitAppCall(scriptHash, "name");
Expand All @@ -36,7 +36,7 @@ public static bool TryLoad(IReadOnlyStore store, UInt160 scriptHash, out Nep5Con
var decimals = (byte)engine.ResultStack.Pop<Neo.VM.Types.Integer>().GetInteger();
var symbol = Encoding.UTF8.GetString(engine.ResultStack.Pop().GetSpan());
var name = Encoding.UTF8.GetString(engine.ResultStack.Pop().GetSpan());
contract = new Nep5Contract(name, symbol, decimals, scriptHash);
contract = new Nep17Contract(name, symbol, decimals, scriptHash);
return true;
}

Expand All @@ -54,13 +54,13 @@ public JObject ToJson()
return json;
}

public static Nep5Contract FromJson(JObject json)
public static Nep17Contract FromJson(JObject json)
{
var name = json["name"].AsString();
var symbol = json["symbol"].AsString();
var scriptHash = UInt160.Parse(json["scriptHash"].AsString());
var decimals = (byte)json["decimals"].AsNumber();
return new Nep5Contract(name, symbol, decimals, scriptHash);
return new Nep17Contract(name, symbol, decimals, scriptHash);
}
}
}
Expand Down
27 changes: 21 additions & 6 deletions src/neo3/Node/ExpressApplicationEngine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,16 @@ namespace NeoExpress.Neo3.Node
{
internal class ExpressApplicationEngine : ApplicationEngine
{
// In next preview, Execute method will be virtual so it will be easier to
// retrieve start/end state. In the meantime, preExecTrace and completeStateChange
// are used to control tracing of start/end states.

private readonly ITraceDebugSink traceDebugSink;
private readonly StoreView snapshot;
private readonly Dictionary<UInt160, string> contractNameMap = new Dictionary<UInt160, string>();

public ExpressApplicationEngine(ITraceDebugSink traceDebugSink, TriggerType trigger, IVerifiable container, StoreView snapshot, long gas)
: base(trigger, container, snapshot, gas)
{
this.traceDebugSink = traceDebugSink;
this.snapshot = snapshot;

Log += OnLog;
Notify += OnNotify;
}
Expand All @@ -35,19 +35,34 @@ public override void Dispose()
base.Dispose();
}

private string GetContractName(UInt160 scriptId)
{
if (contractNameMap.TryGetValue(scriptId, out var name))
{
return name;
}

var state = snapshot.Contracts.TryGet(scriptId);
name = state != null ? state.Manifest.Name : "";
contractNameMap[scriptId] = name;
return name;
}

private void OnNotify(object sender, NotifyEventArgs args)
{
if (ReferenceEquals(sender, this))
{
traceDebugSink.Notify(args);
var name = GetContractName(args.ScriptHash);
traceDebugSink.Notify(args, name);
}
}

private void OnLog(object sender, LogEventArgs args)
{
if (ReferenceEquals(sender, this))
{
traceDebugSink.Log(args);
var name = GetContractName(args.ScriptHash);
traceDebugSink.Log(args, name);
}
}

Expand Down
1 change: 1 addition & 0 deletions src/neo3/Node/ExpressApplicationEngineProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

namespace NeoExpress.Neo3.Node
{
// Note: namespace alias needed to avoid conflict with Plugin.System property
using SysIO = System.IO;

class ExpressApplicationEngineProvider : Plugin, IApplicationEngineProvider
Expand Down
2 changes: 1 addition & 1 deletion src/neo3/Node/ExpressOracle.cs
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ class ExpressOracle
// Calculate network fee

var engine = ApplicationEngine.Create(TriggerType.Verification, tx, snapshot.Clone());
engine.LoadScript(NativeContract.Oracle.Script, CallFlags.None, 0);
engine.LoadScript(NativeContract.Oracle.Script, CallFlags.None);
engine.LoadScript(new ScriptBuilder().Emit(OpCode.DEPTH, OpCode.PACK).EmitPush("verify").ToArray(), CallFlags.None);
if (engine.Execute() != VMState.HALT) return null;
tx.NetworkFee += engine.GasConsumed;
Expand Down
40 changes: 22 additions & 18 deletions src/neo3/Node/ExpressRpcServer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -80,29 +80,29 @@ public JObject GetApplicationLog(JArray _params)

// TODO: should the event name comparison be case insensitive?
// Native contracts use "Transfer" while neon preview 3 compiled contracts use "transfer"
static bool IsNep5Transfer(NotificationRecord notification)
static bool IsNep17Transfer(NotificationRecord notification)
=> notification.InventoryType == InventoryType.TX
&& notification.State.Count == 3
&& notification.State.Count == 4
&& (notification.EventName == "Transfer" || notification.EventName == "transfer");

static IEnumerable<(uint blockIndex, ushort txIndex, NotificationRecord notification)> GetNep5Transfers(IReadOnlyStore store)
static IEnumerable<(uint blockIndex, ushort txIndex, NotificationRecord notification)> GetNep17Transfers(IReadOnlyStore store)
{
return ExpressAppLogsPlugin
.GetNotifications(store)
.Where(t => IsNep5Transfer(t.notification));
.Where(t => IsNep17Transfer(t.notification));
}

public static IEnumerable<Nep5Contract> GetNep5Contracts(IReadOnlyStore store)
public static IEnumerable<Nep17Contract> GetNep17Contracts(IReadOnlyStore store)
{
var scriptHashes = new HashSet<UInt160>();
foreach (var (_, _, notification) in GetNep5Transfers(store))
foreach (var (_, _, notification) in GetNep17Transfers(store))
{
scriptHashes.Add(notification.ScriptHash);
}

foreach (var scriptHash in scriptHashes)
{
if (Nep5Contract.TryLoad(store, scriptHash, out var contract))
if (Nep17Contract.TryLoad(store, scriptHash, out var contract))
{
yield return contract;
}
Expand All @@ -126,11 +126,11 @@ static UInt160 ToUInt160(Neo.VM.Types.StackItem item)
: throw new ArgumentException("invalid UInt160", nameof(item));
}

public static IEnumerable<(Nep5Contract contract, BigInteger balance, uint lastUpdatedBlock)> GetNep5Balances(IReadOnlyStore store, UInt160 address)
public static IEnumerable<(Nep17Contract contract, BigInteger balance, uint lastUpdatedBlock)> GetNep17Balances(IReadOnlyStore store, UInt160 address)
{
var accounts = new Dictionary<UInt160, uint>();

foreach (var (blockIndex, _, notification) in GetNep5Transfers(store))
foreach (var (blockIndex, _, notification) in GetNep17Transfers(store))
{
var from = ToUInt160(notification.State[0]);
var to = ToUInt160(notification.State[1]);
Expand All @@ -144,8 +144,8 @@ static UInt160 ToUInt160(Neo.VM.Types.StackItem item)
{
if (TryGetBalance(kvp.Key, out var balance))
{
var contract = Nep5Contract.TryLoad(store, kvp.Key, out var _contract)
? _contract : Nep5Contract.Unknown(kvp.Key);
var contract = Nep17Contract.TryLoad(store, kvp.Key, out var _contract)
? _contract : Nep17Contract.Unknown(kvp.Key);
yield return (contract, balance, kvp.Value);
}
}
Expand All @@ -168,24 +168,25 @@ bool TryGetBalance(UInt160 asset, out BigInteger balance)
}

[RpcMethod]
public JObject ExpressGetNep5Contracts(JArray _)
public JObject ExpressGetNep17Contracts(JArray _)
{
using var snapshot = Blockchain.Singleton.Store.GetSnapshot();
var jsonContracts = new JArray();
foreach (var contract in GetNep5Contracts(snapshot))
foreach (var contract in GetNep17Contracts(snapshot))
{
var jsonContract = new JObject();
jsonContracts.Add(contract.ToJson());
}
return jsonContracts;
}

[RpcMethod]
public JObject GetNep5Balances(JArray @params)
public JObject GetNep17Balances(JArray @params)
{
var address = GetScriptHashFromParam(@params[0].AsString());
using var snapshot = Blockchain.Singleton.Store.GetSnapshot();
var balances = new JArray();
foreach (var (contract, balance, lastUpdatedBlock) in GetNep5Balances(snapshot, address))
foreach (var (contract, balance, lastUpdatedBlock) in GetNep17Balances(snapshot, address))
{
balances.Add(new JObject()
{
Expand All @@ -202,7 +203,7 @@ public JObject GetNep5Balances(JArray @params)
}

[RpcMethod]
public JObject GetNep5Transfers(JArray @params)
public JObject GetNep17Transfers(JArray @params)
{
var address = GetScriptHashFromParam(@params[0].AsString());

Expand All @@ -218,7 +219,7 @@ public JObject GetNep5Transfers(JArray @params)

{
using var snapshot = Blockchain.Singleton.Store.GetSnapshot();
foreach (var (blockIndex, txIndex, notification) in GetNep5Transfers(snapshot))
foreach (var (blockIndex, txIndex, notification) in GetNep17Transfers(snapshot))
{
var header = Blockchain.Singleton.GetHeader(blockIndex);
if (startTime <= header.Timestamp && header.Timestamp <= endTime)
Expand Down Expand Up @@ -289,7 +290,10 @@ static JObject MakeTransferJson(uint blockIndex, ushort txIndex, NotificationRec
var json = new JArray();
foreach (var (key, value) in contracts)
{
json.Add(value.Manifest.ToJson());
var jsonContract = new JObject();
jsonContract["hash"] = value.Hash.ToString();
jsonContract["manifest"] = value.Manifest.ToJson();
json.Add(jsonContract);
}
return json;
}
Expand Down
6 changes: 3 additions & 3 deletions src/neo3/Node/IExpressNode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,16 @@ internal interface IExpressNode : IDisposable
Task<UInt256> ExecuteAsync(ExpressChain chain, ExpressWalletAccount account, Script script, decimal additionalGas = 0);
Task<UInt256> SubmitTransactionAsync(Transaction tx);
Task<InvokeResult> InvokeAsync(Script script);
Task<(RpcNep5Balance balance, Nep5Contract contract)[]> GetBalancesAsync(UInt160 address);
Task<(RpcNep17Balance balance, Nep17Contract contract)[]> GetBalancesAsync(UInt160 address);
Task<(Transaction tx, RpcApplicationLog? appLog)> GetTransactionAsync(UInt256 txHash);
Task<Block> GetBlockAsync(UInt256 blockHash);
Task<Block> GetBlockAsync(uint blockIndex);
Task<Block> GetLatestBlockAsync();
Task<uint> GetTransactionHeight(UInt256 txHash);
Task<IReadOnlyList<ExpressStorage>> GetStoragesAsync(UInt160 scriptHash);
Task<ContractManifest> GetContractAsync(UInt160 scriptHash);
Task<IReadOnlyList<ContractManifest>> ListContractsAsync();
Task<IReadOnlyList<Nep5Contract>> ListNep5ContractsAsync();
Task<IReadOnlyList<(UInt160 hash, ContractManifest manifest)>> ListContractsAsync();
Task<IReadOnlyList<Nep17Contract>> ListNep17ContractsAsync();
Task<IReadOnlyList<(ulong requestId, OracleRequest request)>> ListOracleRequestsAsync();
Task<UInt256> SubmitOracleResponseAsync(ExpressChain chain, OracleResponse response, ECPoint[] oracleNodes);
}
Expand Down
Loading

0 comments on commit e70c441

Please sign in to comment.