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
Changes from 1 commit
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
Prev Previous commit
Next Next commit
Implement ContractPermission correctly
erikzhang committed May 23, 2019
commit 2cdb847f48eab452084c5d489226e9750ffaed11
21 changes: 12 additions & 9 deletions neo.UnitTests/UT_ContractManifest.cs
Original file line number Diff line number Diff line change
@@ -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"":""*"",""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"":[],""safeMethods"":[]}";
var manifest = ContractManifest.Parse(json);

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

@@ -38,18 +38,21 @@ public void ParseFromJson_Permissions()
Assert.AreEqual(manifest.ToString(), json);

var check = ContractManifest.CreateDefault(UInt160.Zero);
check.Permissions = WildCardContainer<ContractPermission>.Create(new ContractPermission()
check.Permissions = new[]
{
Contract = UInt160.Zero,
Methods = WildCardContainer<string>.Create("method1", "method2")
});
new ContractPermission()
{
Contract = ContractPermissionDescriptor.Create(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 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 manifest = ContractManifest.Parse(json);
Assert.AreEqual(manifest.ToString(), json);

@@ -61,7 +64,7 @@ public void ParseFromJson_SafeMethods()
[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 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 manifest = ContractManifest.Parse(json);
Assert.AreEqual(manifest.ToString(), json);

@@ -73,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"":""*"",""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"":[],""safeMethods"":[]}";
Copy link
Member

Choose a reason for hiding this comment

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

How does these groups really work? A contract can be part of more than one group in this current design, right?
And why the signature?

Copy link
Member

Choose a reason for hiding this comment

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

Without signature, you can join any group, which is not by design.

Copy link
Member

Choose a reason for hiding this comment

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

Ok, I got it, in the current design, how is able to sign for entering? Is it a multisig with 1 single threshold?

Copy link
Member

Choose a reason for hiding this comment

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

Just put the signature in the json.

Copy link
Member

Choose a reason for hiding this comment

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

The group identifier is the PublicKey, right? 03b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c

Which signatures will be allowed?
If we have 10 members of the group, should we create a MultiSig 1/10? For each new member, append an additional PubKey?

Sorry for the trivial questions, @erikzhang, trying to get the idea here...aehauheau

Copy link
Member

Choose a reason for hiding this comment

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

Please check the specification here: #287

Where pubkey represents the public key of the group and signature is the signature of the contract hash.

var manifest = ContractManifest.Parse(json);
Assert.AreEqual(manifest.ToString(), json);

7 changes: 0 additions & 7 deletions neo/IO/Json/IJsonSerializable.cs

This file was deleted.

24 changes: 5 additions & 19 deletions neo/SmartContract/Manifest/ContractManifest.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
using Neo.IO;
using Neo.IO.Json;
using System;
using System.IO;
using System.Linq;

@@ -45,7 +44,7 @@ public class ContractManifest : ISerializable
/// <summary>
/// The permissions field is an array containing a set of Permission objects. It describes which contracts may be invoked and which methods are called.
/// </summary>
public WildCardContainer<ContractPermission> Permissions { get; set; }
public ContractPermission[] Permissions { get; set; }

/// <summary>
/// The trusts field is an array containing a set of contract hashes or group public keys. It can also be assigned with a wildcard *. If it is a wildcard *, then it means that it trusts any contract.
@@ -68,7 +67,7 @@ public static ContractManifest CreateDefault(UInt160 hash)
{
return new ContractManifest()
{
Permissions = WildCardContainer<ContractPermission>.CreateWildcard(),
Permissions = new[] { ContractPermission.DefaultPermission },
Abi = new ContractAbi()
{
Hash = hash,
@@ -91,20 +90,7 @@ public static ContractManifest CreateDefault(UInt160 hash)
/// <returns>Return true or false</returns>
public bool CanCall(ContractManifest manifest, string method)
{
if (Groups.Any(a => manifest.Groups.Any(b => a.PubKey.Equals(b.PubKey))))
{
// Same group
return true;
}

if (manifest.Trusts != null && !manifest.Trusts.IsWildcard && !manifest.Trusts.Contains(Hash))
Copy link
Member Author

Choose a reason for hiding this comment

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

now Trusts is not used

Copy link
Member

@erikzhang erikzhang May 23, 2019

Choose a reason for hiding this comment

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

trusts is for displaying warnings by client or wallet only.

Copy link
Member Author

Choose a reason for hiding this comment

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

Then trusts and safeMethods is only for user interfaces?

Copy link
Member

Choose a reason for hiding this comment

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

Yes.

{
// null == * wildcard
// You don't have rights in the contract
return false;
}

return Permissions == null || Permissions.IsWildcard || Permissions.Any(u => u.IsAllowed(manifest.Hash, method));
return Permissions.Any(u => u.IsAllowed(manifest, method));
}

/// <summary>
@@ -139,7 +125,7 @@ public JObject ToJson()
json["groups"] = new JArray(Groups.Select(u => u.ToJson()).ToArray());
json["features"] = feature;
json["abi"] = Abi.ToJson();
json["permissions"] = Permissions.ToJson();
json["permissions"] = Permissions.Select(p => p.ToJson()).ToArray();
json["trusts"] = Trusts.ToJson();
json["safeMethods"] = SafeMethods.ToJson();

@@ -173,7 +159,7 @@ private void DeserializeFromJson(JObject json)
Abi = ContractAbi.FromJson(json["abi"]);
Groups = ((JArray)json["groups"]).Select(u => ContractGroup.FromJson(u)).ToArray();
Features = ContractFeatures.NoProperty;
Permissions = WildCardContainer<ContractPermission>.FromJson(json["permissions"], ContractPermission.FromJson);
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());

30 changes: 19 additions & 11 deletions neo/SmartContract/Manifest/ContractPermission.cs
Original file line number Diff line number Diff line change
@@ -7,20 +7,26 @@ namespace Neo.SmartContract.Manifest
/// <summary>
/// The permissions field is an array containing a set of Permission objects. It describes which contracts may be invoked and which methods are called.
/// </summary>
public class ContractPermission : IJsonSerializable
public class ContractPermission
{
/// <summary>
/// The contract field indicates the contract to be invoked. It can be a hash of a contract, a public key of a group, or a wildcard *.
/// If it specifies a hash of a contract, then the contract will be invoked; If it specifies a public key of a group, then any contract in this group will be invoked; If it specifies a wildcard*, then any contract will be invoked.
/// </summary>
public UInt160 Contract { get; set; }
public ContractPermissionDescriptor Contract { get; set; }

/// <summary>
/// The methods field is an array containing a set of methods to be called. It can also be assigned with a wildcard *. If it is a wildcard *, then it means that any method can be called.
/// If a contract invokes a contract or method that is not declared in the manifest at runtime, the invocation will fail.
/// </summary>
public WildCardContainer<string> Methods { get; set; }

public static readonly ContractPermission DefaultPermission = new ContractPermission
{
Contract = ContractPermissionDescriptor.CreateWildcard(),
Methods = WildCardContainer<string>.CreateWildcard()
};

/// <summary>
/// Parse ContractPermission from json
/// </summary>
@@ -30,7 +36,7 @@ public static ContractPermission FromJson(JObject json)
{
return new ContractPermission
{
Contract = UInt160.Parse(json["contract"].AsString()),
Contract = ContractPermissionDescriptor.FromJson(json["contract"]),
Methods = WildCardContainer<string>.FromJson(json["methods"], u => u.AsString()),
};
}
@@ -41,26 +47,28 @@ public static ContractPermission FromJson(JObject json)
public JObject ToJson()
{
var json = new JObject();
json["contract"] = Contract.ToString();
json["contract"] = Contract.ToJson();
json["methods"] = Methods.ToJson();
return json;
}

/// <summary>
/// Return true if is allowed
/// </summary>
/// <param name="contractHash">Contract hash</param>
/// <param name="manifest">The manifest of which contract we are calling</param>
/// <param name="method">Method</param>
/// <returns>Return true or false</returns>
public bool IsAllowed(UInt160 contractHash, string method)
public bool IsAllowed(ContractManifest manifest, string method)
{
if (!Contract.Equals(contractHash))
if (Contract.IsHash)
{
// 0x00 = * wildcard
if (Contract != UInt160.Zero) return false;
if (!Contract.Hash.Equals(manifest.Hash)) return false;
}

return Methods == null || Methods.IsWildcard || Methods.Contains(method);
else if (Contract.IsGroup)
{
if (manifest.Groups.All(p => !p.PubKey.Equals(Contract.Group))) return false;
Copy link
Member

Choose a reason for hiding this comment

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

Does All work as expected or Sum would also work?

Copy link
Member

Choose a reason for hiding this comment

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

What sum?

Copy link
Member

@vncoelho vncoelho May 23, 2019

Choose a reason for hiding this comment

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

It is checking, for all groups, if any of them is not equal.

Maybe this is strange, because one of yours groups may not allow you to enter, but another one can allow you to interact with the desired contract.

Copy link
Member

Choose a reason for hiding this comment

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

All not Any.

Copy link
Member

Choose a reason for hiding this comment

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

If p.PubKey.Equals(Contract.Group) is false for any of the groups of have it will probably return false, no?

Copy link
Member

Choose a reason for hiding this comment

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

All

Copy link
Member

Choose a reason for hiding this comment

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

If p.PubKey.Equals(Contract.Group) is false for ALL of the groups, it will return false.

}
return Methods.IsWildcard || Methods.Contains(method);
}
}
}
56 changes: 56 additions & 0 deletions neo/SmartContract/Manifest/ContractPermissionDescriptor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
using Neo.Cryptography.ECC;
using Neo.IO.Json;
using System;

namespace Neo.SmartContract.Manifest
{
public class ContractPermissionDescriptor
{
public UInt160 Hash { get; }
public ECPoint Group { get; }

public bool IsHash => Hash != null;
public bool IsGroup => Group != null;
public bool IsWildcard => Hash is null && Group is null;

private ContractPermissionDescriptor(UInt160 hash, ECPoint group)
{
this.Hash = hash;
this.Group = group;
}

public static ContractPermissionDescriptor Create(UInt160 hash)
{
return new ContractPermissionDescriptor(hash, null);
}

public static ContractPermissionDescriptor Create(ECPoint group)
{
return new ContractPermissionDescriptor(null, group);
}

public static ContractPermissionDescriptor CreateWildcard()
{
return new ContractPermissionDescriptor(null, null);
}

public static ContractPermissionDescriptor FromJson(JObject json)
{
string str = json.AsString();
if (str.Length == 42)
return Create(UInt160.Parse(str));
if (str.Length == 66)
return Create(ECPoint.Parse(str, ECCurve.Secp256r1));
if (str == "*")
return CreateWildcard();
throw new FormatException();
}

public JObject ToJson()
{
if (IsHash) return Hash.ToString();
if (IsGroup) return Group.ToString();
return "*";
}
}
}
11 changes: 1 addition & 10 deletions neo/SmartContract/Manifest/WildCardContainer.cs
Original file line number Diff line number Diff line change
@@ -70,16 +70,7 @@ public IEnumerator<T> GetEnumerator()
public JObject ToJson()
{
if (IsWildcard) return "*";
return _data.Select(p =>
{
switch (p)
{
case IJsonSerializable serializable:
return serializable.ToJson();
default:
return p.ToString();
}
}).ToArray();
return _data.Select(p => (JObject)p.ToString()).ToArray();
}
}
}