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

Read only methods #1049

Closed
wants to merge 16 commits into from
16 changes: 8 additions & 8 deletions neo.UnitTests/SmartContract/Manifest/UT_ContractManifest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ public class UT_ContractManifest
[TestMethod]
public void ParseFromJson_Default()
{
var json = @"{""groups"":[],""features"":{""storage"":false,""payable"":false},""abi"":{""hash"":""0x0000000000000000000000000000000000000000"",""entryPoint"":{""name"":""Main"",""parameters"":[{""name"":""operation"",""type"":""String""},{""name"":""args"",""type"":""Array""}],""returnType"":""Any""},""methods"":[],""events"":[]},""permissions"":[{""contract"":""*"",""methods"":""*""}],""trusts"":[],""safeMethods"":[]}";
var json = @"{""groups"":[],""features"":{""storage"":false,""payable"":false},""abi"":{""hash"":""0x0000000000000000000000000000000000000000"",""entryPoint"":{""name"":""Main"",""parameters"":[{""name"":""operation"",""type"":""String""},{""name"":""args"",""type"":""Array""}],""returnType"":""Any""},""methods"":[],""events"":[]},""permissions"":[{""contract"":""*"",""methods"":""*""}],""trusts"":[],""readOnlyMethods"":[]}";
var manifest = ContractManifest.Parse(json);

Assert.AreEqual(manifest.ToString(), json);
Expand All @@ -21,7 +21,7 @@ public void ParseFromJson_Default()
[TestMethod]
public void ParseFromJson_Features()
{
var json = @"{""groups"":[],""features"":{""storage"":true,""payable"":true},""abi"":{""hash"":""0x0000000000000000000000000000000000000000"",""entryPoint"":{""name"":""Main"",""parameters"":[{""name"":""operation"",""type"":""String""},{""name"":""args"",""type"":""Array""}],""returnType"":""Any""},""methods"":[],""events"":[]},""permissions"":[{""contract"":""*"",""methods"":""*""}],""trusts"":[],""safeMethods"":[]}";
var json = @"{""groups"":[],""features"":{""storage"":true,""payable"":true},""abi"":{""hash"":""0x0000000000000000000000000000000000000000"",""entryPoint"":{""name"":""Main"",""parameters"":[{""name"":""operation"",""type"":""String""},{""name"":""args"",""type"":""Array""}],""returnType"":""Any""},""methods"":[],""events"":[]},""permissions"":[{""contract"":""*"",""methods"":""*""}],""trusts"":[],""readOnlyMethods"":[]}";
var manifest = ContractManifest.Parse(json);
Assert.AreEqual(manifest.ToJson().ToString(), json);

Expand All @@ -33,7 +33,7 @@ public void ParseFromJson_Features()
[TestMethod]
public void ParseFromJson_Permissions()
{
var json = @"{""groups"":[],""features"":{""storage"":false,""payable"":false},""abi"":{""hash"":""0x0000000000000000000000000000000000000000"",""entryPoint"":{""name"":""Main"",""parameters"":[{""name"":""operation"",""type"":""String""},{""name"":""args"",""type"":""Array""}],""returnType"":""Any""},""methods"":[],""events"":[]},""permissions"":[{""contract"":""0x0000000000000000000000000000000000000000"",""methods"":[""method1"",""method2""]}],""trusts"":[],""safeMethods"":[]}";
var json = @"{""groups"":[],""features"":{""storage"":false,""payable"":false},""abi"":{""hash"":""0x0000000000000000000000000000000000000000"",""entryPoint"":{""name"":""Main"",""parameters"":[{""name"":""operation"",""type"":""String""},{""name"":""args"",""type"":""Array""}],""returnType"":""Any""},""methods"":[],""events"":[]},""permissions"":[{""contract"":""0x0000000000000000000000000000000000000000"",""methods"":[""method1"",""method2""]}],""trusts"":[],""readOnlyMethods"":[]}";
var manifest = ContractManifest.Parse(json);
Assert.AreEqual(manifest.ToString(), json);

Expand All @@ -50,21 +50,21 @@ public void ParseFromJson_Permissions()
}

[TestMethod]
public void ParseFromJson_SafeMethods()
public void ParseFromJson_ReadOnlyMethods()
{
var json = @"{""groups"":[],""features"":{""storage"":false,""payable"":false},""abi"":{""hash"":""0x0000000000000000000000000000000000000000"",""entryPoint"":{""name"":""Main"",""parameters"":[{""name"":""operation"",""type"":""String""},{""name"":""args"",""type"":""Array""}],""returnType"":""Any""},""methods"":[],""events"":[]},""permissions"":[{""contract"":""*"",""methods"":""*""}],""trusts"":[],""safeMethods"":[""balanceOf""]}";
var json = @"{""groups"":[],""features"":{""storage"":false,""payable"":false},""abi"":{""hash"":""0x0000000000000000000000000000000000000000"",""entryPoint"":{""name"":""Main"",""parameters"":[{""name"":""operation"",""type"":""String""},{""name"":""args"",""type"":""Array""}],""returnType"":""Any""},""methods"":[],""events"":[]},""permissions"":[{""contract"":""*"",""methods"":""*""}],""trusts"":[],""readOnlyMethods"":[""balanceOf""]}";
var manifest = ContractManifest.Parse(json);
Assert.AreEqual(manifest.ToString(), json);

var check = ContractManifest.CreateDefault(UInt160.Zero);
check.SafeMethods = WildCardContainer<string>.Create("balanceOf");
check.ReadOnlyMethods = WildCardContainer<string>.Create("balanceOf");
Assert.AreEqual(manifest.ToString(), check.ToString());
}

[TestMethod]
public void ParseFromJson_Trust()
{
var json = @"{""groups"":[],""features"":{""storage"":false,""payable"":false},""abi"":{""hash"":""0x0000000000000000000000000000000000000000"",""entryPoint"":{""name"":""Main"",""parameters"":[{""name"":""operation"",""type"":""String""},{""name"":""args"",""type"":""Array""}],""returnType"":""Any""},""methods"":[],""events"":[]},""permissions"":[{""contract"":""*"",""methods"":""*""}],""trusts"":[""0x0000000000000000000000000000000000000001""],""safeMethods"":[]}";
var json = @"{""groups"":[],""features"":{""storage"":false,""payable"":false},""abi"":{""hash"":""0x0000000000000000000000000000000000000000"",""entryPoint"":{""name"":""Main"",""parameters"":[{""name"":""operation"",""type"":""String""},{""name"":""args"",""type"":""Array""}],""returnType"":""Any""},""methods"":[],""events"":[]},""permissions"":[{""contract"":""*"",""methods"":""*""}],""trusts"":[""0x0000000000000000000000000000000000000001""],""readOnlyMethods"":[]}";
var manifest = ContractManifest.Parse(json);
Assert.AreEqual(manifest.ToString(), json);

Expand All @@ -76,7 +76,7 @@ public void ParseFromJson_Trust()
[TestMethod]
public void ParseFromJson_Groups()
{
var json = @"{""groups"":[{""pubKey"":""03b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c"",""signature"":""41414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141""}],""features"":{""storage"":false,""payable"":false},""abi"":{""hash"":""0x0000000000000000000000000000000000000000"",""entryPoint"":{""name"":""Main"",""parameters"":[{""name"":""operation"",""type"":""String""},{""name"":""args"",""type"":""Array""}],""returnType"":""Any""},""methods"":[],""events"":[]},""permissions"":[{""contract"":""*"",""methods"":""*""}],""trusts"":[],""safeMethods"":[]}";
var json = @"{""groups"":[{""pubKey"":""03b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c"",""signature"":""41414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141""}],""features"":{""storage"":false,""payable"":false},""abi"":{""hash"":""0x0000000000000000000000000000000000000000"",""entryPoint"":{""name"":""Main"",""parameters"":[{""name"":""operation"",""type"":""String""},{""name"":""args"",""type"":""Array""}],""returnType"":""Any""},""methods"":[],""events"":[]},""permissions"":[{""contract"":""*"",""methods"":""*""}],""trusts"":[],""readOnlyMethods"":[]}";
var manifest = ContractManifest.Parse(json);
Assert.AreEqual(manifest.ToString(), json);

Expand Down
29 changes: 14 additions & 15 deletions neo/SmartContract/ApplicationEngine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,16 @@ public partial class ApplicationEngine : ExecutionEngine
public const long GasFree = 0;
private readonly long gas_amount;
private readonly bool testMode;
private readonly RandomAccessStack<UInt160> hashes = new RandomAccessStack<UInt160>();
private readonly List<NotifyEventArgs> notifications = new List<NotifyEventArgs>();
private readonly List<IDisposable> disposables = new List<IDisposable>();

public TriggerType Trigger { get; }
public IVerifiable ScriptContainer { get; }
public Snapshot Snapshot { get; }
public long GasConsumed { get; private set; } = 0;
public UInt160 CurrentScriptHash => hashes.Count > 0 ? hashes.Peek() : null;
public UInt160 CallingScriptHash => hashes.Count > 1 ? hashes.Peek(1) : null;
public UInt160 EntryScriptHash => hashes.Count > 0 ? hashes.Peek(hashes.Count - 1) : null;
public UInt160 CurrentScriptHash => CurrentContext?.GetState<ExecutionContextState>().ScriptHash;
public UInt160 CallingScriptHash => InvocationStack.Count > 1 ? InvocationStack.Peek(1).GetState<ExecutionContextState>().ScriptHash : null;
public UInt160 EntryScriptHash => EntryContext?.GetState<ExecutionContextState>().ScriptHash;
public IReadOnlyList<NotifyEventArgs> Notifications => notifications;
internal Dictionary<UInt160, int> InvocationCounter { get; } = new Dictionary<UInt160, int>();

Expand All @@ -36,8 +35,6 @@ public ApplicationEngine(TriggerType trigger, IVerifiable container, Snapshot sn
this.Trigger = trigger;
this.ScriptContainer = container;
this.Snapshot = snapshot;
ContextLoaded += ApplicationEngine_ContextLoaded;
ContextUnloaded += ApplicationEngine_ContextUnloaded;
}

internal T AddDisposable<T>(T disposable) where T : IDisposable
Expand All @@ -52,14 +49,16 @@ private bool AddGas(long gas)
return testMode || GasConsumed <= gas_amount;
}

private void ApplicationEngine_ContextLoaded(object sender, ExecutionContext e)
{
hashes.Push(((byte[])e.Script).ToScriptHash());
}

private void ApplicationEngine_ContextUnloaded(object sender, ExecutionContext e)
{
hashes.Pop();
protected override void LoadContext(ExecutionContext context)
{
// Set default execution context state

context.SetState(new ExecutionContextState()
{
ScriptHash = ((byte[])context.Script).ToScriptHash()
});

base.LoadContext(context);
}

public override void Dispose()
Expand Down Expand Up @@ -136,4 +135,4 @@ internal void SendNotification(UInt160 script_hash, StackItem state)
notifications.Add(notification);
}
}
}
}
15 changes: 15 additions & 0 deletions neo/SmartContract/ExecutionContextState.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
namespace Neo.SmartContract
{
public class ExecutionContextState
{
/// <summary>
/// Script hash
/// </summary>
public UInt160 ScriptHash { get; set; }

/// <summary>
/// Is read only
/// </summary>
public bool ReadOnly { get; set; } = false;
}
}
12 changes: 7 additions & 5 deletions neo/SmartContract/InteropDescriptor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,25 +11,27 @@ internal class InteropDescriptor
public long Price { get; }
public Func<RandomAccessStack<StackItem>, long> PriceCalculator { get; }
public TriggerType AllowedTriggers { get; }
public bool RequireWriteAccess { get; }

public InteropDescriptor(string method, Func<ApplicationEngine, bool> handler, long price, TriggerType allowedTriggers)
: this(method, handler, allowedTriggers)
public InteropDescriptor(string method, Func<ApplicationEngine, bool> handler, long price, TriggerType allowedTriggers, bool requireWriteAccess)
: this(method, handler, allowedTriggers, requireWriteAccess)
{
this.Price = price;
}

public InteropDescriptor(string method, Func<ApplicationEngine, bool> handler, Func<RandomAccessStack<StackItem>, long> priceCalculator, TriggerType allowedTriggers)
: this(method, handler, allowedTriggers)
public InteropDescriptor(string method, Func<ApplicationEngine, bool> handler, Func<RandomAccessStack<StackItem>, long> priceCalculator, TriggerType allowedTriggers, bool requireWriteAccess)
: this(method, handler, allowedTriggers, requireWriteAccess)
{
this.PriceCalculator = priceCalculator;
}

private InteropDescriptor(string method, Func<ApplicationEngine, bool> handler, TriggerType allowedTriggers)
private InteropDescriptor(string method, Func<ApplicationEngine, bool> handler, TriggerType allowedTriggers, bool requireWriteAccess)
{
this.Method = method;
this.Hash = method.ToInteropMethodHash();
this.Handler = handler;
this.AllowedTriggers = allowedTriggers;
this.RequireWriteAccess = requireWriteAccess;
}

public long GetPrice(RandomAccessStack<StackItem> stack)
Expand Down
6 changes: 3 additions & 3 deletions neo/SmartContract/InteropService.NEO.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ namespace Neo.SmartContract
{
static partial class InteropService
{
public static readonly uint Neo_Native_Deploy = Register("Neo.Native.Deploy", Native_Deploy, 0, TriggerType.Application);
public static readonly uint Neo_Native_Deploy = Register("Neo.Native.Deploy", Native_Deploy, 0, TriggerType.Application, true);
public static readonly uint Neo_Crypto_CheckSig = Register("Neo.Crypto.CheckSig", Crypto_CheckSig, 0_01000000, TriggerType.All);
public static readonly uint Neo_Crypto_CheckMultiSig = Register("Neo.Crypto.CheckMultiSig", Crypto_CheckMultiSig, GetCheckMultiSigPrice, TriggerType.All);
public static readonly uint Neo_Header_GetVersion = Register("Neo.Header.GetVersion", Header_GetVersion, 0_00000400, TriggerType.Application);
Expand All @@ -27,8 +27,8 @@ static partial class InteropService
public static readonly uint Neo_Transaction_GetWitnesses = Register("Neo.Transaction.GetWitnesses", Transaction_GetWitnesses, 0_00010000, TriggerType.All);
public static readonly uint Neo_Witness_GetVerificationScript = Register("Neo.Witness.GetVerificationScript", Witness_GetVerificationScript, 0_00000400, TriggerType.All);
public static readonly uint Neo_Account_IsStandard = Register("Neo.Account.IsStandard", Account_IsStandard, 0_00030000, TriggerType.All);
public static readonly uint Neo_Contract_Create = Register("Neo.Contract.Create", Contract_Create, GetDeploymentPrice, TriggerType.Application);
public static readonly uint Neo_Contract_Update = Register("Neo.Contract.Update", Contract_Update, GetDeploymentPrice, TriggerType.Application);
public static readonly uint Neo_Contract_Create = Register("Neo.Contract.Create", Contract_Create, GetDeploymentPrice, TriggerType.Application, true);
public static readonly uint Neo_Contract_Update = Register("Neo.Contract.Update", Contract_Update, GetDeploymentPrice, TriggerType.Application, true);
public static readonly uint Neo_Contract_GetScript = Register("Neo.Contract.GetScript", Contract_GetScript, 0_00000400, TriggerType.Application);
public static readonly uint Neo_Contract_IsPayable = Register("Neo.Contract.IsPayable", Contract_IsPayable, 0_00000400, TriggerType.Application);
public static readonly uint Neo_Storage_Find = Register("Neo.Storage.Find", Storage_Find, 0_01000000, TriggerType.Application);
Expand Down
20 changes: 13 additions & 7 deletions neo/SmartContract/InteropService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,8 @@ public static partial class InteropService
public static readonly uint System_Block_GetTransaction = Register("System.Block.GetTransaction", Block_GetTransaction, 0_00000400, TriggerType.Application);
public static readonly uint System_Transaction_GetHash = Register("System.Transaction.GetHash", Transaction_GetHash, 0_00000400, TriggerType.All);
public static readonly uint System_Contract_Call = Register("System.Contract.Call", Contract_Call, 0_01000000, TriggerType.System | TriggerType.Application);
public static readonly uint System_Contract_Destroy = Register("System.Contract.Destroy", Contract_Destroy, 0_01000000, TriggerType.Application);
public static readonly uint System_Storage_GetContext = Register("System.Storage.GetContext", Storage_GetContext, 0_00000400, TriggerType.Application);
public static readonly uint System_Contract_Destroy = Register("System.Contract.Destroy", Contract_Destroy, 0_01000000, TriggerType.Application, true);
public static readonly uint System_Storage_GetContext = Register("System.Storage.GetContext", Storage_GetContext, 0_00000400, TriggerType.Application, true);
Copy link
Member Author

Choose a reason for hiding this comment

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

@igormcoelho I increased the scope not only for the storages, for destroy, update, and create too

public static readonly uint System_Storage_GetReadOnlyContext = Register("System.Storage.GetReadOnlyContext", Storage_GetReadOnlyContext, 0_00000400, TriggerType.Application);
public static readonly uint System_Storage_Get = Register("System.Storage.Get", Storage_Get, 0_01000000, TriggerType.Application);
public static readonly uint System_Storage_Put = Register("System.Storage.Put", Storage_Put, GetStoragePrice, TriggerType.Application);
Expand Down Expand Up @@ -88,19 +88,21 @@ internal static bool Invoke(ApplicationEngine engine, uint method)
return false;
if (!descriptor.AllowedTriggers.HasFlag(engine.Trigger))
return false;
if (descriptor.RequireWriteAccess && engine.CurrentContext.GetState<ExecutionContextState>().ReadOnly)
return false;
return descriptor.Handler(engine);
}

private static uint Register(string method, Func<ApplicationEngine, bool> handler, long price, TriggerType allowedTriggers)
private static uint Register(string method, Func<ApplicationEngine, bool> handler, long price, TriggerType allowedTriggers, bool requireWriteAccess = false)
{
InteropDescriptor descriptor = new InteropDescriptor(method, handler, price, allowedTriggers);
InteropDescriptor descriptor = new InteropDescriptor(method, handler, price, allowedTriggers, requireWriteAccess);
methods.Add(descriptor.Hash, descriptor);
return descriptor.Hash;
}

private static uint Register(string method, Func<ApplicationEngine, bool> handler, Func<RandomAccessStack<StackItem>, long> priceCalculator, TriggerType allowedTriggers)
private static uint Register(string method, Func<ApplicationEngine, bool> handler, Func<RandomAccessStack<StackItem>, long> priceCalculator, TriggerType allowedTriggers, bool requireWriteAccess = false)
{
InteropDescriptor descriptor = new InteropDescriptor(method, handler, priceCalculator, allowedTriggers);
InteropDescriptor descriptor = new InteropDescriptor(method, handler, priceCalculator, allowedTriggers, requireWriteAccess);
methods.Add(descriptor.Hash, descriptor);
return descriptor.Hash;
}
Expand Down Expand Up @@ -537,9 +539,10 @@ private static bool Contract_Call(ApplicationEngine engine)

StackItem method = engine.CurrentContext.EvaluationStack.Pop();
StackItem args = engine.CurrentContext.EvaluationStack.Pop();
string methodStr = method.GetString();
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, methodStr))
Copy link
Member Author

Choose a reason for hiding this comment

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

@neo-project/core @erikzhang current Manifest could be null? i think that it is a MUST

Suggested change
if (currentManifest != null && !currentManifest.CanCall(contract.Manifest, methodStr))
if (currentManifest == null || !currentManifest.CanCall(contract.Manifest, methodStr))

return false;

if (engine.InvocationCounter.TryGetValue(contract.ScriptHash, out var counter))
Expand All @@ -552,6 +555,9 @@ private static bool Contract_Call(ApplicationEngine engine)
}

ExecutionContext context_new = engine.LoadScript(contract.Script, 1);
context_new.GetState<ExecutionContextState>().ReadOnly = currentManifest != null &&
(currentManifest.ReadOnlyMethods.IsWildcard || currentManifest.ReadOnlyMethods.Contains(methodStr));

context_new.EvaluationStack.Push(args);
context_new.EvaluationStack.Push(method);
return true;
Expand Down
Loading