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

Allow contracts to load script at runtime dynamically #2756

Merged
merged 12 commits into from
Sep 16, 2022
2 changes: 1 addition & 1 deletion src/Neo/SmartContract/ApplicationEngine.Contract.cs
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ protected internal void CallContract(UInt160 contractHash, string method, CallFl
bool hasReturnValue = md.ReturnType != ContractParameterType.Void;

ExecutionContext context = CallContractInternal(contract, md, callFlags, hasReturnValue, args);
if (!hasReturnValue) context.GetState<ExecutionContextState>().PushNullWhenReturn = true;
context.GetState<ExecutionContextState>().IsDynamicCall = true;
}

/// <summary>
Expand Down
30 changes: 30 additions & 0 deletions src/Neo/SmartContract/ApplicationEngine.Runtime.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
using Neo.IO;
using Neo.Network.P2P.Payloads;
using Neo.SmartContract.Native;
using Neo.VM;
using Neo.VM.Types;
using System;
using System.Collections.Generic;
Expand Down Expand Up @@ -90,6 +91,12 @@ partial class ApplicationEngine
/// </summary>
public static readonly InteropDescriptor System_Runtime_GetEntryScriptHash = Register("System.Runtime.GetEntryScriptHash", nameof(EntryScriptHash), 1 << 4, CallFlags.None);

/// <summary>
/// The <see cref="InteropDescriptor"/> of System.Runtime.LoadScript.
/// Loads a script at rumtime.
/// </summary>
public static readonly InteropDescriptor System_Runtime_LoadScript = Register("System.Runtime.LoadScript", nameof(RuntimeLoadScript), 1 << 15, CallFlags.AllowCall);

/// <summary>
/// The <see cref="InteropDescriptor"/> of System.Runtime.CheckWitness.
/// Determines whether the specified account has witnessed the current transaction.
Expand Down Expand Up @@ -189,6 +196,27 @@ protected internal StackItem GetScriptContainer()
return interop.ToStackItem(ReferenceCounter);
}

/// <summary>
/// The implementation of System.Runtime.LoadScript.
/// Loads a script at rumtime.
/// </summary>
protected internal void RuntimeLoadScript(byte[] script, CallFlags callFlags, Array args)
shargon marked this conversation as resolved.
Show resolved Hide resolved
{
if ((callFlags & ~CallFlags.All) != 0)
throw new ArgumentOutOfRangeException(nameof(callFlags));

ExecutionContextState state = CurrentContext.GetState<ExecutionContextState>();
ExecutionContext context = LoadScript(script, configureState: p =>
{
p.CallingScriptHash = state.ScriptHash;
p.CallFlags = callFlags & state.CallFlags;
p.IsDynamicCall = true;
});

for (int i = args.Count - 1; i >= 0; i--)
context.EvaluationStack.Push(args[i]);
}

/// <summary>
/// The implementation of System.Runtime.CheckWitness.
/// Determines whether the specified account has witnessed the current transaction.
Expand Down Expand Up @@ -306,6 +334,8 @@ protected internal void RuntimeLog(byte[] state)
protected internal void RuntimeNotify(byte[] eventName, Array state)
{
if (eventName.Length > MaxEventName) throw new ArgumentException(null, nameof(eventName));
if (CurrentContext.GetState<ExecutionContextState>().Contract is null)
shargon marked this conversation as resolved.
Show resolved Hide resolved
throw new InvalidOperationException("Notifications are not allowed in dynamic scripts.");
using MemoryStream ms = new(MaxNotificationSize);
using BinaryWriter writer = new(ms, Utility.StrictUTF8, true);
BinarySerializer.Serialize(writer, state, MaxNotificationSize);
Expand Down
8 changes: 7 additions & 1 deletion src/Neo/SmartContract/ApplicationEngine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -273,7 +273,13 @@ protected override void ContextUnloaded(ExecutionContext context)
{
ExecutionContextState contextState = CurrentContext.GetState<ExecutionContextState>();
contextState.NotificationCount += state.NotificationCount;
if (state.PushNullWhenReturn) Push(StackItem.Null);
if (state.IsDynamicCall)
{
if (context.EvaluationStack.Count == 0)
Push(StackItem.Null);
else if (context.EvaluationStack.Count > 1)
throw new NotSupportedException("Multiple return values are not allowed in cross-contract calls.");
}
}
}
else
Expand Down
2 changes: 1 addition & 1 deletion src/Neo/SmartContract/ExecutionContextState.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,6 @@ public class ExecutionContextState

public int NotificationCount { get; set; }

public bool PushNullWhenReturn { get; set; }
public bool IsDynamicCall { get; set; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ public void TestNotSupportedNotification()
{
using var engine = ApplicationEngine.Create(TriggerType.Application, null, null, TestBlockchain.TheNeoSystem.GenesisBlock, settings: TestBlockchain.TheNeoSystem.Settings, gas: 1100_00000000);
engine.LoadScript(Array.Empty<byte>());
engine.CurrentContext.GetState<ExecutionContextState>().Contract = new();

// circular

Expand Down
2 changes: 2 additions & 0 deletions tests/Neo.UnitTests/SmartContract/UT_InteropService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ public void Runtime_GetNotifications_Test()
// Execute

engine.LoadScript(script.ToArray());
engine.CurrentContext.GetState<ExecutionContextState>().Contract = new();
var currentScriptHash = engine.EntryScriptHash;

Assert.AreEqual(VMState.HALT, engine.Execute());
Expand Down Expand Up @@ -145,6 +146,7 @@ public void Runtime_GetNotifications_Test()
// Execute

engine.LoadScript(script.ToArray());
engine.CurrentContext.GetState<ExecutionContextState>().Contract = new();
var currentScriptHash = engine.EntryScriptHash;

Assert.AreEqual(VMState.HALT, engine.Execute());
Expand Down