Skip to content

Commit

Permalink
Optimize dynamic call (#2211)
Browse files Browse the repository at this point in the history
  • Loading branch information
erikzhang authored Jan 10, 2021
1 parent b87df5d commit d0a4976
Show file tree
Hide file tree
Showing 15 changed files with 93 additions and 90 deletions.
2 changes: 1 addition & 1 deletion src/neo/Network/P2P/Payloads/OracleResponse.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ public class OracleResponse : TransactionAttribute
static OracleResponse()
{
using ScriptBuilder sb = new ScriptBuilder();
sb.EmitDynamicCall(NativeContract.Oracle.Hash, "finish", false);
sb.EmitDynamicCall(NativeContract.Oracle.Hash, "finish");
FixedScript = sb.ToArray();
}

Expand Down
19 changes: 12 additions & 7 deletions src/neo/SmartContract/ApplicationEngine.Contract.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
using Neo.Cryptography.ECC;
using Neo.Network.P2P.Payloads;
using Neo.SmartContract.Manifest;
using Neo.SmartContract.Native;
using Neo.VM.Types;
using System;
using Array = Neo.VM.Types.Array;

namespace Neo.SmartContract
{
Expand All @@ -20,17 +22,20 @@ partial class ApplicationEngine
public static readonly InteropDescriptor System_Contract_NativeOnPersist = Register("System.Contract.NativeOnPersist", nameof(NativeOnPersist), 0, CallFlags.WriteStates);
public static readonly InteropDescriptor System_Contract_NativePostPersist = Register("System.Contract.NativePostPersist", nameof(NativePostPersist), 0, CallFlags.WriteStates);

protected internal void CallContract(UInt160 contractHash, string method, CallFlags callFlags, bool hasReturnValue, ushort pcount)
protected internal void CallContract(UInt160 contractHash, string method, CallFlags callFlags, Array args)
{
if (method.StartsWith('_')) throw new ArgumentException($"Invalid Method Name: {method}");
if ((callFlags & ~CallFlags.All) != 0)
throw new ArgumentOutOfRangeException(nameof(callFlags));
if (pcount > CurrentContext.EvaluationStack.Count)
throw new InvalidOperationException();
StackItem[] args = new StackItem[pcount];
for (int i = 0; i < pcount; i++)
args[i] = Pop();
CallContractInternal(contractHash, method, callFlags, hasReturnValue, args);

ContractState contract = NativeContract.ContractManagement.GetContract(Snapshot, contractHash);
if (contract is null) throw new InvalidOperationException($"Called Contract Does Not Exist: {contractHash}");
ContractMethodDescriptor md = contract.Manifest.Abi.GetMethod(method);
if (md is null) throw new InvalidOperationException($"Method {method} Does Not Exist In Contract {contractHash}");
bool hasReturnValue = md.ReturnType != ContractParameterType.Void;

if (!hasReturnValue) CurrentContext.EvaluationStack.Push(StackItem.Null);
CallContractInternal(contract, md, callFlags, hasReturnValue, args);
}

protected internal void CallNativeContract(string name)
Expand Down
20 changes: 12 additions & 8 deletions src/neo/SmartContract/ApplicationEngine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -82,16 +82,20 @@ private ExecutionContext CallContractInternal(UInt160 contractHash, string metho
if (contract is null) throw new InvalidOperationException($"Called Contract Does Not Exist: {contractHash}");
ContractMethodDescriptor md = contract.Manifest.Abi.GetMethod(method);
if (md is null) throw new InvalidOperationException($"Method {method} Does Not Exist In Contract {contractHash}");
return CallContractInternal(contract, md, flags, hasReturnValue, args);
}

if (md.Safe)
private ExecutionContext CallContractInternal(ContractState contract, ContractMethodDescriptor method, CallFlags flags, bool hasReturnValue, IReadOnlyList<StackItem> args)
{
if (method.Safe)
{
flags &= ~CallFlags.WriteStates;
}
else
{
ContractState currentContract = NativeContract.ContractManagement.GetContract(Snapshot, CurrentScriptHash);
if (currentContract?.CanCall(contract, method) == false)
throw new InvalidOperationException($"Cannot Call Method {method} Of Contract {contractHash} From Contract {CurrentScriptHash}");
if (currentContract?.CanCall(contract, method.Name) == false)
throw new InvalidOperationException($"Cannot Call Method {method} Of Contract {contract.Hash} From Contract {CurrentScriptHash}");
}

if (invocationCounter.TryGetValue(contract.Hash, out var counter))
Expand All @@ -107,16 +111,16 @@ private ExecutionContext CallContractInternal(UInt160 contractHash, string metho
UInt160 callingScriptHash = state.ScriptHash;
CallFlags callingFlags = state.CallFlags;

if (args.Length != md.Parameters.Length) throw new InvalidOperationException($"Method {method} Expects {md.Parameters.Length} Arguments But Receives {args.Length} Arguments");
if (hasReturnValue ^ (md.ReturnType != ContractParameterType.Void)) throw new InvalidOperationException("The return value type does not match.");
ExecutionContext context_new = LoadContract(contract, method, flags & callingFlags, hasReturnValue, (ushort)args.Length);
if (args.Count != method.Parameters.Length) throw new InvalidOperationException($"Method {method} Expects {method.Parameters.Length} Arguments But Receives {args.Count} Arguments");
if (hasReturnValue ^ (method.ReturnType != ContractParameterType.Void)) throw new InvalidOperationException("The return value type does not match.");
ExecutionContext context_new = LoadContract(contract, method.Name, flags & callingFlags, hasReturnValue, (ushort)args.Count);
state = context_new.GetState<ExecutionContextState>();
state.CallingScriptHash = callingScriptHash;

for (int i = args.Length - 1; i >= 0; i--)
for (int i = args.Count - 1; i >= 0; i--)
context_new.EvaluationStack.Push(args[i]);
if (NativeContract.IsNative(contract.Hash))
context_new.EvaluationStack.Push(method);
context_new.EvaluationStack.Push(method.Name);

return context_new;
}
Expand Down
4 changes: 2 additions & 2 deletions src/neo/SmartContract/Native/NativeContract.cs
Original file line number Diff line number Diff line change
Expand Up @@ -150,11 +150,11 @@ internal virtual void PostPersist(ApplicationEngine engine)
{
}

public ApplicationEngine TestCall(string operation, bool hasReturnValue, params object[] args)
public ApplicationEngine TestCall(string operation, params object[] args)
{
using (ScriptBuilder sb = new ScriptBuilder())
{
sb.EmitDynamicCall(Hash, operation, hasReturnValue, args);
sb.EmitDynamicCall(Hash, operation, args);
return ApplicationEngine.Run(sb.ToArray());
}
}
Expand Down
17 changes: 8 additions & 9 deletions src/neo/VM/Helper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,36 +46,35 @@ public static ScriptBuilder Emit(this ScriptBuilder sb, params OpCode[] ops)
return sb;
}

public static ScriptBuilder EmitDynamicCall(this ScriptBuilder sb, UInt160 scriptHash, string operation, bool hasReturnValue)
public static ScriptBuilder EmitDynamicCall(this ScriptBuilder sb, UInt160 scriptHash, string operation)
{
sb.EmitPush(0);
sb.EmitPush(hasReturnValue ? 1 : 0);
sb.Emit(OpCode.NEWARRAY0);
sb.EmitPush(CallFlags.All);
sb.EmitPush(operation);
sb.EmitPush(scriptHash);
sb.EmitSysCall(ApplicationEngine.System_Contract_Call);
return sb;
}

public static ScriptBuilder EmitDynamicCall(this ScriptBuilder sb, UInt160 scriptHash, string operation, bool hasReturnValue, params ContractParameter[] args)
public static ScriptBuilder EmitDynamicCall(this ScriptBuilder sb, UInt160 scriptHash, string operation, params ContractParameter[] args)
{
for (int i = args.Length - 1; i >= 0; i--)
sb.EmitPush(args[i]);
sb.EmitPush(args.Length);
sb.EmitPush(hasReturnValue ? 1 : 0);
sb.Emit(OpCode.PACK);
sb.EmitPush(CallFlags.All);
sb.EmitPush(operation);
sb.EmitPush(scriptHash);
sb.EmitSysCall(ApplicationEngine.System_Contract_Call);
return sb;
}

public static ScriptBuilder EmitDynamicCall(this ScriptBuilder sb, UInt160 scriptHash, string operation, bool hasReturnValue, params object[] args)
public static ScriptBuilder EmitDynamicCall(this ScriptBuilder sb, UInt160 scriptHash, string operation, params object[] args)
{
for (int i = args.Length - 1; i >= 0; i--)
sb.EmitPush(args[i]);
sb.EmitPush(args.Length);
sb.EmitPush(hasReturnValue ? 1 : 0);
sb.Emit(OpCode.PACK);
sb.EmitPush(CallFlags.All);
sb.EmitPush(operation);
sb.EmitPush(scriptHash);
Expand Down Expand Up @@ -213,10 +212,10 @@ public static ScriptBuilder EmitSysCall(this ScriptBuilder sb, uint method, para
/// <param name="operation">contract operation</param>
/// <param name="args">operation arguments</param>
/// <returns></returns>
public static byte[] MakeScript(this UInt160 scriptHash, string operation, bool hasReturnValue, params object[] args)
public static byte[] MakeScript(this UInt160 scriptHash, string operation, params object[] args)
{
using ScriptBuilder sb = new ScriptBuilder();
sb.EmitDynamicCall(scriptHash, operation, hasReturnValue, args);
sb.EmitDynamicCall(scriptHash, operation, args);
return sb.ToArray();
}

Expand Down
4 changes: 2 additions & 2 deletions src/neo/Wallets/AssetDescriptor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ public AssetDescriptor(UInt160 asset_id)
byte[] script;
using (ScriptBuilder sb = new ScriptBuilder())
{
sb.EmitDynamicCall(asset_id, "decimals", true);
sb.EmitDynamicCall(asset_id, "symbol", true);
sb.EmitDynamicCall(asset_id, "decimals");
sb.EmitDynamicCall(asset_id, "symbol");
script = sb.ToArray();
}
using ApplicationEngine engine = ApplicationEngine.Run(script, snapshot, gas: 0_10000000);
Expand Down
8 changes: 4 additions & 4 deletions src/neo/Wallets/Wallet.cs
Original file line number Diff line number Diff line change
Expand Up @@ -138,10 +138,10 @@ public BigDecimal GetBalance(UInt160 asset_id, params UInt160[] accounts)
sb.EmitPush(0);
foreach (UInt160 account in accounts)
{
sb.EmitDynamicCall(asset_id, "balanceOf", true, account);
sb.EmitDynamicCall(asset_id, "balanceOf", account);
sb.Emit(OpCode.ADD);
}
sb.EmitDynamicCall(asset_id, "decimals", true);
sb.EmitDynamicCall(asset_id, "decimals");
script = sb.ToArray();
}
using ApplicationEngine engine = ApplicationEngine.Run(script, gas: 20000000L * accounts.Length);
Expand Down Expand Up @@ -265,7 +265,7 @@ public Transaction MakeTransaction(TransferOutput[] outputs, UInt160 from = null
foreach (UInt160 account in accounts)
using (ScriptBuilder sb2 = new ScriptBuilder())
{
sb2.EmitDynamicCall(assetId, "balanceOf", true, account);
sb2.EmitDynamicCall(assetId, "balanceOf", account);
using (ApplicationEngine engine = ApplicationEngine.Run(sb2.ToArray(), snapshot))
{
if (engine.State.HasFlag(VMState.FAULT))
Expand Down Expand Up @@ -296,7 +296,7 @@ public Transaction MakeTransaction(TransferOutput[] outputs, UInt160 from = null
Scopes = WitnessScope.CalledByEntry
});
}
sb.EmitDynamicCall(output.AssetId, "transfer", true, account, output.ScriptHash, value, output.Data);
sb.EmitDynamicCall(output.AssetId, "transfer", account, output.ScriptHash, value, output.Data);
sb.Emit(OpCode.ASSERT);
}
}
Expand Down
6 changes: 3 additions & 3 deletions tests/neo.UnitTests/Extensions/NativeContractExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ public static class NativeContractExtensions
public static ContractState DeployContract(this StoreView snapshot, UInt160 sender, byte[] nefFile, byte[] manifest, long gas = 200_00000000)
{
var script = new ScriptBuilder();
script.EmitDynamicCall(NativeContract.ContractManagement.Hash, "deploy", true, nefFile, manifest);
script.EmitDynamicCall(NativeContract.ContractManagement.Hash, "deploy", nefFile, manifest);

var engine = ApplicationEngine.Create(TriggerType.Application,
sender != null ? new Transaction() { Signers = new Signer[] { new Signer() { Account = sender } } } : null, snapshot, null, gas);
Expand All @@ -34,7 +34,7 @@ public static ContractState DeployContract(this StoreView snapshot, UInt160 send
public static void UpdateContract(this StoreView snapshot, UInt160 callingScriptHash, byte[] nefFile, byte[] manifest)
{
var script = new ScriptBuilder();
script.EmitDynamicCall(NativeContract.ContractManagement.Hash, "update", false, nefFile, manifest);
script.EmitDynamicCall(NativeContract.ContractManagement.Hash, "update", nefFile, manifest);

var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshot);
engine.LoadScript(script.ToArray());
Expand All @@ -57,7 +57,7 @@ public static void UpdateContract(this StoreView snapshot, UInt160 callingScript
public static void DestroyContract(this StoreView snapshot, UInt160 callingScriptHash)
{
var script = new ScriptBuilder();
script.EmitDynamicCall(NativeContract.ContractManagement.Hash, "destroy", false);
script.EmitDynamicCall(NativeContract.ContractManagement.Hash, "destroy");

var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshot);
engine.LoadScript(script.ToArray());
Expand Down
14 changes: 7 additions & 7 deletions tests/neo.UnitTests/Network/P2P/Payloads/UT_Transaction.cs
Original file line number Diff line number Diff line change
Expand Up @@ -319,7 +319,7 @@ public void FeeIsSignatureContract_TestScope_Global()
{
// self-transfer of 1e-8 GAS
BigInteger value = (new BigDecimal(1, 8)).Value;
sb.EmitDynamicCall(NativeContract.GAS.Hash, "transfer", true, acc.ScriptHash, acc.ScriptHash, value, null);
sb.EmitDynamicCall(NativeContract.GAS.Hash, "transfer", acc.ScriptHash, acc.ScriptHash, value, null);
sb.Emit(OpCode.ASSERT);
script = sb.ToArray();
}
Expand Down Expand Up @@ -405,7 +405,7 @@ public void FeeIsSignatureContract_TestScope_CurrentHash_GAS()
{
// self-transfer of 1e-8 GAS
BigInteger value = (new BigDecimal(1, 8)).Value;
sb.EmitDynamicCall(NativeContract.GAS.Hash, "transfer", true, acc.ScriptHash, acc.ScriptHash, value, null);
sb.EmitDynamicCall(NativeContract.GAS.Hash, "transfer", acc.ScriptHash, acc.ScriptHash, value, null);
sb.Emit(OpCode.ASSERT);
script = sb.ToArray();
}
Expand Down Expand Up @@ -492,7 +492,7 @@ public void FeeIsSignatureContract_TestScope_CalledByEntry_Plus_GAS()
{
// self-transfer of 1e-8 GAS
System.Numerics.BigInteger value = (new BigDecimal(1, 8)).Value;
sb.EmitDynamicCall(NativeContract.GAS.Hash, "transfer", true, acc.ScriptHash, acc.ScriptHash, value, null);
sb.EmitDynamicCall(NativeContract.GAS.Hash, "transfer", acc.ScriptHash, acc.ScriptHash, value, null);
sb.Emit(OpCode.ASSERT);
script = sb.ToArray();
}
Expand Down Expand Up @@ -580,7 +580,7 @@ public void FeeIsSignatureContract_TestScope_CurrentHash_NEO_FAULT()
{
// self-transfer of 1e-8 GAS
System.Numerics.BigInteger value = (new BigDecimal(1, 8)).Value;
sb.EmitDynamicCall(NativeContract.GAS.Hash, "transfer", true, acc.ScriptHash, acc.ScriptHash, value);
sb.EmitDynamicCall(NativeContract.GAS.Hash, "transfer", acc.ScriptHash, acc.ScriptHash, value);
sb.Emit(OpCode.ASSERT);
script = sb.ToArray();
}
Expand Down Expand Up @@ -632,7 +632,7 @@ public void FeeIsSignatureContract_TestScope_CurrentHash_NEO_GAS()
{
// self-transfer of 1e-8 GAS
BigInteger value = (new BigDecimal(1, 8)).Value;
sb.EmitDynamicCall(NativeContract.GAS.Hash, "transfer", true, acc.ScriptHash, acc.ScriptHash, value, null);
sb.EmitDynamicCall(NativeContract.GAS.Hash, "transfer", acc.ScriptHash, acc.ScriptHash, value, null);
sb.Emit(OpCode.ASSERT);
script = sb.ToArray();
}
Expand Down Expand Up @@ -722,7 +722,7 @@ public void FeeIsSignatureContract_TestScope_NoScopeFAULT()
{
// self-transfer of 1e-8 GAS
BigInteger value = (new BigDecimal(1, 8)).Value;
sb.EmitDynamicCall(NativeContract.GAS.Hash, "transfer", true, acc.ScriptHash, acc.ScriptHash, value);
sb.EmitDynamicCall(NativeContract.GAS.Hash, "transfer", acc.ScriptHash, acc.ScriptHash, value);
sb.Emit(OpCode.ASSERT);
script = sb.ToArray();
}
Expand Down Expand Up @@ -985,7 +985,7 @@ public void FeeIsSignatureContract_TestScope_FeeOnly_Default()
{
// self-transfer of 1e-8 GAS
BigInteger value = (new BigDecimal(1, 8)).Value;
sb.EmitDynamicCall(NativeContract.GAS.Hash, "transfer", true, acc.ScriptHash, acc.ScriptHash, value, null);
sb.EmitDynamicCall(NativeContract.GAS.Hash, "transfer", acc.ScriptHash, acc.ScriptHash, value, null);
sb.Emit(OpCode.ASSERT);
script = sb.ToArray();
}
Expand Down
Loading

0 comments on commit d0a4976

Please sign in to comment.