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

Feature manifest and permission system #766

Merged
merged 66 commits into from
May 24, 2019
Merged
Show file tree
Hide file tree
Changes from 57 commits
Commits
Show all changes
66 commits
Select commit Hold shift + click to select a range
9e101ab
Classes
shargon May 20, 2019
0ab7368
Check Valid group signature
shargon May 20, 2019
f548702
Is Allowed
shargon May 20, 2019
143297b
Reuse some classes
shargon May 20, 2019
dfed28b
Fix IsAllowed
shargon May 20, 2019
5ce76cc
Trusts and SafeMethods
shargon May 20, 2019
6b26077
Process trust and Group
shargon May 20, 2019
8781770
Clean code
shargon May 20, 2019
a1c6f72
Add comments
shargon May 20, 2019
5b1aaec
Add more comments
shargon May 20, 2019
34aa3ba
WillCardContainer
shargon May 21, 2019
ed75324
Group as Array
shargon May 21, 2019
f7f36e8
Willcard in ContractPermission
shargon May 21, 2019
2058fd0
Fix trusts
shargon May 21, 2019
deb3707
Clean code
shargon May 21, 2019
6d1d627
Merge branch 'master' into feature-manifest
shargon May 21, 2019
44f5ce0
Merge branch 'master' into feature-manifest
shargon May 21, 2019
1f8b316
Typo fix
shargon May 21, 2019
0137897
Integrate ContractManifest
shargon May 21, 2019
7736247
Integrate in Contract.Call
shargon May 21, 2019
3a5b0ba
Clone Manifest
shargon May 21, 2019
453675c
full manifest in json
shargon May 21, 2019
55fd385
Default value in constructor
shargon May 21, 2019
cd306e8
Serialize ContractManifest
shargon May 21, 2019
2b40a8d
Default Abi for Native Contract
shargon May 21, 2019
6b36687
Fix return for PolicyContract
shargon May 21, 2019
c402d0b
Nep5Token Abi
shargon May 21, 2019
f33b0e8
Clean code
shargon May 21, 2019
ce0aa85
Abi for GastToken
shargon May 21, 2019
89051bf
Abi for NeoToken
shargon May 21, 2019
93d6ae8
Default entry point in the static method
shargon May 21, 2019
768df1f
MaxLength for ContractManifest
shargon May 21, 2019
0729674
Migrate too
shargon May 21, 2019
7842694
Merge branch 'master' into feature-manifest
shargon May 22, 2019
524a745
Parsing json
shargon May 22, 2019
548acf0
Merge remote-tracking branch 'shargon/feature-manifest' into feature-…
shargon May 22, 2019
16d1ff5
Unit test for json
shargon May 22, 2019
c09a795
Validate ContractManifest
shargon May 22, 2019
47b3efc
Neo.Contract.UpdateManifest
shargon May 22, 2019
1cb3650
Deserialize
shargon May 22, 2019
2b9c5dc
Check hash of the manifest in update
shargon May 22, 2019
c18cd5b
Rename classes
shargon May 22, 2019
31feebc
Erik recommendations
shargon May 23, 2019
3f0772b
Move default entry point
shargon May 23, 2019
ecb338f
Revert Helper.cs
erikzhang May 23, 2019
f94021b
fix tests
erikzhang May 23, 2019
1d4e281
Change the type of `ContractManifestGroup.PubKey` to `ECPoint`
erikzhang May 23, 2019
6bc60f1
Move classes to namespace `Neo.SmartContract.Manifest`, and rename
erikzhang May 23, 2019
8d38246
Remove `Equals()`
erikzhang May 23, 2019
93cd013
Fix `DefaultEntryPoint`
erikzhang May 23, 2019
4dacb2c
Fix `ToJson()` and `ContractManifest.CreateDefault()`
erikzhang May 23, 2019
5400351
Add `ContractManifest.DeserializeFromJson()`
erikzhang May 23, 2019
cc4a340
optimize
erikzhang May 23, 2019
dab02a5
Groups never be null
shargon May 23, 2019
38fc8d7
Not allowed to be set to null
erikzhang May 23, 2019
26ff492
Combine `Neo.Contract.Migrate` and `Neo.Contract.UpdateManifest`
erikzhang May 23, 2019
d00903e
Ensure `contract.Manifest.Hash == contract.ScriptHash` in `Contract_C…
erikzhang May 23, 2019
12a1576
rename
erikzhang May 23, 2019
162758e
Fix `Contract_Call`
erikzhang May 23, 2019
2cdb847
Implement `ContractPermission` correctly
erikzhang May 23, 2019
84c38f9
Simplify `IsValid()`
erikzhang May 23, 2019
fcdab61
Update ContractGroup.cs
shargon May 23, 2019
7c7a82d
Change max length of ContractManifest
shargon May 23, 2019
efac87f
Add `events` for ABI of `Nep5Token`
erikzhang May 23, 2019
a3a1b0d
format
erikzhang May 23, 2019
b44ec29
Update WildCardContainer.cs
erikzhang May 23, 2019
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
85 changes: 85 additions & 0 deletions neo.UnitTests/UT_ContractManifest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Neo.Cryptography.ECC;
using Neo.SmartContract.Manifest;

namespace Neo.UnitTests
{
[TestClass]
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"":""*"",""trusts"":[],""safeMethods"":[]}";
var manifest = ContractManifest.Parse(json);

Assert.AreEqual(manifest.ToString(), json);
Assert.AreEqual(manifest.ToString(), ContractManifest.CreateDefault(UInt160.Zero).ToString());
vncoelho marked this conversation as resolved.
Show resolved Hide resolved
Assert.IsTrue(manifest.IsValid());
}

[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"":""*"",""trusts"":[],""safeMethods"":[]}";
var manifest = ContractManifest.Parse(json);
Assert.AreEqual(manifest.ToJson().ToString(), json);

var check = ContractManifest.CreateDefault(UInt160.Zero);
check.Features = ContractFeatures.HasStorage | ContractFeatures.Payable;
Assert.AreEqual(manifest.ToString(), check.ToString());
}

[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 manifest = ContractManifest.Parse(json);
Assert.AreEqual(manifest.ToString(), json);

var check = ContractManifest.CreateDefault(UInt160.Zero);
check.Permissions = WildCardContainer<ContractPermission>.Create(new ContractPermission()
{
Contract = UInt160.Zero,
Methods = WildCardContainer<string>.Create("method1", "method2")
});
Assert.AreEqual(manifest.ToString(), check.ToString());
}

[TestMethod]
public void ParseFromJson_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"":""*"",""trusts"":[],""safeMethods"":[""balanceOf""]}";
var manifest = ContractManifest.Parse(json);
Assert.AreEqual(manifest.ToString(), json);

var check = ContractManifest.CreateDefault(UInt160.Zero);
check.SafeMethods = 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"":""*"",""trusts"":[""0x0000000000000000000000000000000000000001""],""safeMethods"":[]}";
var manifest = ContractManifest.Parse(json);
Assert.AreEqual(manifest.ToString(), json);

var check = ContractManifest.CreateDefault(UInt160.Zero);
check.Trusts = WildCardContainer<UInt160>.Create(UInt160.Parse("0x0000000000000000000000000000000000000001"));
Assert.AreEqual(manifest.ToString(), check.ToString());
}

[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"":""*"",""trusts"":[],""safeMethods"":[]}";
var manifest = ContractManifest.Parse(json);
Assert.AreEqual(manifest.ToString(), json);

var check = ContractManifest.CreateDefault(UInt160.Zero);
check.Groups = new ContractGroup[] { new ContractGroup() { PubKey = ECPoint.Parse("03b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c", ECCurve.Secp256r1), Signature = "41414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141".HexToBytes() } };
Assert.AreEqual(manifest.ToString(), check.ToString());
}
}
}
19 changes: 3 additions & 16 deletions neo.UnitTests/UT_InteropPrices.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
using FluentAssertions;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Neo.Ledger;
using Neo.SmartContract;
using Neo.SmartContract.Manifest;
using Neo.VM;
using System.Reflection;
using System;
using System.Reflection;

namespace Neo.UnitTests
{
Expand Down Expand Up @@ -106,7 +106,7 @@ public void ApplicationEngineVariablePrices()
MethodInfo GetPriceForSysCall = typeof(ApplicationEngine).GetMethod("GetPriceForSysCall", BindingFlags.InvokeMethod | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance, null, new Type[]{}, null);

// Neo.Contract.Create: f66ca56e (requires push properties on fourth position)
byte[] SyscallContractCreateHash00 = new byte[]{(byte)ContractPropertyState.NoProperty, 0x00, 0x00, 0x00, 0x68, 0xf6, 0x6c, 0xa5, 0x6e};
byte[] SyscallContractCreateHash00 = new byte[]{(byte)ContractFeatures.NoProperty, 0x00, 0x00, 0x00, 0x68, 0xf6, 0x6c, 0xa5, 0x6e};
using ( ApplicationEngine ae = new ApplicationEngine(TriggerType.Application, null, null, 0) )
{
Debugger debugger = new Debugger(ae);
Expand All @@ -131,19 +131,6 @@ public void ApplicationEngineVariablePrices()
GetPriceForSysCall.Invoke(ae, new object[]{}).Should().Be(500L * 100000000L / 100000); // assuming private ae.ratio = 100000
}

// Neo.Contract.Migrate: 471b6290 (requires push properties on fourth position)
byte[] SyscallContractMigrateHash00 = new byte[]{(byte)ContractPropertyState.NoProperty, 0x00, 0x00, 0x00, 0x68, 0x47, 0x1b, 0x62, 0x90};
using ( ApplicationEngine ae = new ApplicationEngine(TriggerType.Application, null, null, 0) )
{
Debugger debugger = new Debugger(ae);
ae.LoadScript(SyscallContractMigrateHash00);
debugger.StepInto(); // push 0 - ContractPropertyState.NoProperty
debugger.StepInto(); // push 0
debugger.StepInto(); // push 0
debugger.StepInto(); // push 0
GetPriceForSysCall.Invoke(ae, new object[]{}).Should().Be(100L * 100000000L / 100000); // assuming private ae.ratio = 100000
}

// System.Storage.Put: e63f1884 (requires push key and value)
byte[] SyscallStoragePutHash = new byte[]{0x53, 0x53, 0x00, 0x68, 0xe6, 0x3f, 0x18, 0x84};
using ( ApplicationEngine ae = new ApplicationEngine(TriggerType.Application, null, null, 0) )
Expand Down
7 changes: 7 additions & 0 deletions neo/IO/Json/IJsonSerializable.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace Neo.IO.Json
{
public interface IJsonSerializable
{
JObject ToJson();
}
}
24 changes: 12 additions & 12 deletions neo/Ledger/ContractState.cs
Original file line number Diff line number Diff line change
@@ -1,17 +1,16 @@
using Neo.IO;
using Neo.IO.Json;
using Neo.SmartContract;
using Neo.SmartContract.Manifest;
using System.IO;

namespace Neo.Ledger
{
public class ContractState : ICloneable<ContractState>, ISerializable
{
public byte[] Script;
public ContractPropertyState ContractProperties;

public bool HasStorage => ContractProperties.HasFlag(ContractPropertyState.HasStorage);
public bool Payable => ContractProperties.HasFlag(ContractPropertyState.Payable);
public ContractManifest Manifest;

private UInt160 _scriptHash;
public UInt160 ScriptHash
Expand All @@ -26,43 +25,44 @@ public UInt160 ScriptHash
}
}

int ISerializable.Size => Script.GetVarSize() + sizeof(ContractParameterType);
public bool HasStorage => Manifest.Features.HasFlag(ContractFeatures.HasStorage);
public bool Payable => Manifest.Features.HasFlag(ContractFeatures.Payable);

int ISerializable.Size => Script.GetVarSize() + Manifest.ToJson().ToString().GetVarSize();

ContractState ICloneable<ContractState>.Clone()
{
return new ContractState
{
Script = Script,
ContractProperties = ContractProperties
Manifest = Manifest.Clone()
};
}

void ISerializable.Deserialize(BinaryReader reader)
{
Script = reader.ReadVarBytes();
ContractProperties = (ContractPropertyState)reader.ReadByte();
Manifest = reader.ReadSerializable<ContractManifest>();
}

void ICloneable<ContractState>.FromReplica(ContractState replica)
{
Script = replica.Script;
ContractProperties = replica.ContractProperties;
Manifest = replica.Manifest.Clone();
}

void ISerializable.Serialize(BinaryWriter writer)
{
writer.WriteVarBytes(Script);
writer.Write((byte)ContractProperties);
writer.Write(Manifest);
}

public JObject ToJson()
{
JObject json = new JObject();
var json = new JObject();
json["hash"] = ScriptHash.ToString();
json["script"] = Script.ToHexString();
json["properties"] = new JObject();
json["properties"]["storage"] = HasStorage;
json["properties"]["payable"] = Payable;
json["manifest"] = Manifest.ToJson();
return json;
}
}
Expand Down
7 changes: 4 additions & 3 deletions neo/SmartContract/ApplicationEngine.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using Neo.Ledger;
using Neo.Network.P2P.Payloads;
using Neo.Persistence;
using Neo.SmartContract.Manifest;
using Neo.SmartContract.Native;
using Neo.VM;
using System;
Expand Down Expand Up @@ -101,13 +102,13 @@ protected virtual long GetPriceForSysCall()
return 100 * n;
}
if (method == InteropService.Neo_Contract_Create ||
method == InteropService.Neo_Contract_Migrate)
method == InteropService.Neo_Contract_Update)
{
long fee = 100L;

ContractPropertyState contract_properties = (ContractPropertyState)(byte)CurrentContext.EvaluationStack.Peek(3).GetBigInteger();
ContractFeatures contract_properties = (ContractFeatures)(byte)CurrentContext.EvaluationStack.Peek(3).GetBigInteger();

if (contract_properties.HasFlag(ContractPropertyState.HasStorage))
if (contract_properties.HasFlag(ContractFeatures.HasStorage))
{
fee += 400L;
}
Expand Down
1 change: 1 addition & 0 deletions neo/SmartContract/ContractParameterType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ public enum ContractParameterType : byte

InteropInterface = 0xf0,

Any = 0xfe,
Void = 0xff
}
}
65 changes: 44 additions & 21 deletions neo/SmartContract/InteropService.NEO.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using Neo.Network.P2P.Payloads;
using Neo.SmartContract.Enumerators;
using Neo.SmartContract.Iterators;
using Neo.SmartContract.Manifest;
using Neo.SmartContract.Native;
using Neo.VM;
using Neo.VM.Types;
Expand All @@ -27,7 +28,7 @@ static partial class InteropService
public static readonly uint Neo_Witness_GetVerificationScript = Register("Neo.Witness.GetVerificationScript", Witness_GetVerificationScript, 100);
public static readonly uint Neo_Account_IsStandard = Register("Neo.Account.IsStandard", Account_IsStandard, 100);
public static readonly uint Neo_Contract_Create = Register("Neo.Contract.Create", Contract_Create);
public static readonly uint Neo_Contract_Migrate = Register("Neo.Contract.Migrate", Contract_Migrate);
public static readonly uint Neo_Contract_Update = Register("Neo.Contract.Update", Contract_Update);
public static readonly uint Neo_Contract_GetScript = Register("Neo.Contract.GetScript", Contract_GetScript, 1);
public static readonly uint Neo_Contract_IsPayable = Register("Neo.Contract.IsPayable", Contract_IsPayable, 1);
public static readonly uint Neo_Storage_Find = Register("Neo.Storage.Find", Storage_Find, 1);
Expand All @@ -51,12 +52,13 @@ private static bool Native_Deploy(ApplicationEngine engine)
{
if (engine.Trigger != TriggerType.Application) return false;
if (engine.Snapshot.PersistingBlock.Index != 0) return false;

foreach (NativeContract contract in NativeContract.Contracts)
{
engine.Snapshot.Contracts.Add(contract.Hash, new ContractState
{
Script = contract.Script,
ContractProperties = contract.Properties
Manifest = contract.Manifest
});
contract.Initialize(engine);
}
Expand Down Expand Up @@ -226,45 +228,58 @@ private static bool Contract_Create(ApplicationEngine engine)
if (engine.Trigger != TriggerType.Application) return false;
byte[] script = engine.CurrentContext.EvaluationStack.Pop().GetByteArray();
if (script.Length > 1024 * 1024) return false;
ContractPropertyState contract_properties = (ContractPropertyState)(byte)engine.CurrentContext.EvaluationStack.Pop().GetBigInteger();

var manifest = engine.CurrentContext.EvaluationStack.Pop().GetString();
if (manifest.Length > ContractManifest.MaxLength) return false;

UInt160 hash = script.ToScriptHash();
ContractState contract = engine.Snapshot.Contracts.TryGet(hash);
if (contract == null)
if (contract != null) return false;
contract = new ContractState
{
contract = new ContractState
{
Script = script,
ContractProperties = contract_properties
};
engine.Snapshot.Contracts.Add(hash, contract);
}
Script = script,
Manifest = ContractManifest.Parse(manifest)
};

if (!contract.Manifest.IsValid()) return false;
if (contract.Manifest.Hash != contract.ScriptHash) return false;
erikzhang marked this conversation as resolved.
Show resolved Hide resolved

engine.Snapshot.Contracts.Add(hash, contract);
engine.CurrentContext.EvaluationStack.Push(StackItem.FromInterface(contract));
return true;
}

private static bool Contract_Migrate(ApplicationEngine engine)
private static bool Contract_Update(ApplicationEngine engine)
{
if (engine.Trigger != TriggerType.Application) return false;
erikzhang marked this conversation as resolved.
Show resolved Hide resolved

byte[] script = engine.CurrentContext.EvaluationStack.Pop().GetByteArray();
if (script.Length > 1024 * 1024) return false;
ContractPropertyState contract_properties = (ContractPropertyState)(byte)engine.CurrentContext.EvaluationStack.Pop().GetBigInteger();
UInt160 hash = script.ToScriptHash();
ContractState contract = engine.Snapshot.Contracts.TryGet(hash);
if (contract == null)
var manifest = engine.CurrentContext.EvaluationStack.Pop().GetString();
if (manifest.Length > ContractManifest.MaxLength) return false;

var contract = engine.Snapshot.Contracts.TryGet(engine.CurrentScriptHash);
if (contract is null) return false;

if (script.Length > 0)
{
UInt160 hash_new = script.ToScriptHash();
if (hash_new.Equals(engine.CurrentScriptHash)) return false;
if (engine.Snapshot.Contracts.TryGet(hash_new) != null) return false;
contract = new ContractState
{
Script = script,
ContractProperties = contract_properties
Manifest = contract.Manifest
};
engine.Snapshot.Contracts.Add(hash, contract);
contract.Manifest.Abi.Hash = hash_new;
vncoelho marked this conversation as resolved.
Show resolved Hide resolved
engine.Snapshot.Contracts.Add(hash_new, contract);
if (contract.HasStorage)
{
foreach (var pair in engine.Snapshot.Storages.Find(engine.CurrentScriptHash.ToArray()).ToArray())
{
engine.Snapshot.Storages.Add(new StorageKey
{
ScriptHash = hash,
ScriptHash = hash_new,
Key = pair.Key.Key
}, new StorageItem
{
Expand All @@ -273,9 +288,17 @@ private static bool Contract_Migrate(ApplicationEngine engine)
});
}
}
Contract_Destroy(engine);
}
engine.CurrentContext.EvaluationStack.Push(StackItem.FromInterface(contract));
return Contract_Destroy(engine);
if (manifest.Length > 0)
{
contract = engine.Snapshot.Contracts.GetAndChange(contract.ScriptHash);
contract.Manifest = ContractManifest.Parse(manifest);
if (!contract.Manifest.IsValid()) return false;
if (contract.Manifest.Hash != contract.ScriptHash) return false;
}

return true;
}

private static bool Contract_GetScript(ApplicationEngine engine)
Expand Down
Loading