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 #1052

Closed
wants to merge 26 commits into from
Closed
Show file tree
Hide file tree
Changes from 17 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
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
5 changes: 5 additions & 0 deletions neo/SmartContract/ExecutionContextState.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,10 @@ public class ExecutionContextState
/// 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;
Copy link
Contributor

@igormcoelho igormcoelho Aug 26, 2019

Choose a reason for hiding this comment

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

I don't think this is a good idea @shargon... what is RequireWriteAccess? To Storage?
Could we remove this?
I thought that, if current ExecutionContextState is running on ReadOnly, then methods that require write access could simply fail (example: Storage.GetCurrentContext`).

Copy link
Member Author

Choose a reason for hiding this comment

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

We can rename the var, but not remove it, because is used for ban the syscall when is in readOnly mode

}

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);
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)
Copy link
Contributor

Choose a reason for hiding this comment

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

No need to check this @shargon... if something really needs write access, you can invoke it, and let it fail naturally when it tries to access something that requires writing.
Only thing we need to ensure is: if current context is ReadOnly, then next invocations context will also be ReadOnly, but this is simple, as we can do this when loading new context into stack.

Copy link
Member Author

Choose a reason for hiding this comment

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

I want to ban more syscalls like destroy and create, i think thats is better with this flag

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))
shargon marked this conversation as resolved.
Show resolved Hide resolved
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));
shargon marked this conversation as resolved.
Show resolved Hide resolved

context_new.EvaluationStack.Push(args);
context_new.EvaluationStack.Push(method);
return true;
Expand Down
12 changes: 6 additions & 6 deletions neo/SmartContract/Manifest/ContractManifest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,10 @@ public class ContractManifest : ISerializable
public WildCardContainer<UInt160> Trusts { get; set; }

/// <summary>
/// The safemethods field is an array containing a set of method names. It can also be assigned with a wildcard *. If it is a wildcard *, then it means that all methods of the contract are safe.
/// If a method is marked as safe, the user interface will not give any warnings when it is called by any other contract.
/// The read only methods field is an array containing a set of method names. It can also be assigned with a wildcard *. If it is a wildcard *, then it means that all methods of the contract are safe.
/// If a method is marked as read only, the user interface will not give any warnings when it is called by any other contract.
/// </summary>
public WildCardContainer<string> SafeMethods { get; set; }
public WildCardContainer<string> ReadOnlyMethods { get; set; }
shargon marked this conversation as resolved.
Show resolved Hide resolved

/// <summary>
/// Create Default Contract manifest
Expand All @@ -77,7 +77,7 @@ public static ContractManifest CreateDefault(UInt160 hash)
},
Features = ContractFeatures.NoProperty,
Groups = new ContractGroup[0],
SafeMethods = WildCardContainer<string>.Create(),
ReadOnlyMethods = WildCardContainer<string>.Create(),
Trusts = WildCardContainer<UInt160>.Create()
};
}
Expand Down Expand Up @@ -127,7 +127,7 @@ public JObject ToJson()
json["abi"] = Abi.ToJson();
json["permissions"] = Permissions.Select(p => p.ToJson()).ToArray();
json["trusts"] = Trusts.ToJson();
json["safeMethods"] = SafeMethods.ToJson();
json["readOnlyMethods"] = ReadOnlyMethods.ToJson();

return json;
}
Expand Down Expand Up @@ -161,7 +161,7 @@ private void DeserializeFromJson(JObject json)
Features = ContractFeatures.NoProperty;
Permissions = ((JArray)json["permissions"]).Select(u => ContractPermission.FromJson(u)).ToArray();
Trusts = WildCardContainer<UInt160>.FromJson(json["trusts"], u => UInt160.Parse(u.AsString()));
SafeMethods = WildCardContainer<string>.FromJson(json["safeMethods"], u => u.AsString());
ReadOnlyMethods = WildCardContainer<string>.FromJson(json["readOnlyMethods"], u => u.AsString());

if (json["features"]["storage"].AsBoolean()) Features |= ContractFeatures.HasStorage;
if (json["features"]["payable"].AsBoolean()) Features |= ContractFeatures.Payable;
Expand Down
2 changes: 1 addition & 1 deletion neo/SmartContract/Native/ContractMethodAttribute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ internal class ContractMethodAttribute : Attribute
public ContractParameterType ReturnType { get; }
public ContractParameterType[] ParameterTypes { get; set; } = new ContractParameterType[0];
public string[] ParameterNames { get; set; } = new string[0];
public bool SafeMethod { get; set; } = false;
public bool ReadOnly { get; set; } = false;

public ContractMethodAttribute(long price, ContractParameterType returnType)
{
Expand Down
Loading