Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
30 changes: 30 additions & 0 deletions src/Neo/SmartContract/Manifest/ContractAbi.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ namespace Neo.SmartContract.Manifest
public class ContractAbi : IInteroperable
{
private IReadOnlyDictionary<(string, int), ContractMethodDescriptor>? _methodDictionary;
private const int STATE_UNCHECK = 0;
private const int STATE_CHECKING = 1;
private const int STATE_CHECK = 2;

/// <summary>
/// Gets the methods in the ABI.
Expand Down Expand Up @@ -111,6 +114,27 @@ public static ContractAbi FromJson(JObject json)
return abi;
}

private static bool HasCircularReference(string name, IReadOnlyDictionary<string, ExtendedType> namedTypes, Dictionary<string, int> states)
{
if (!states.TryGetValue(name, out var state))
state = STATE_UNCHECK;

if (state == STATE_CHECKING) return true;
if (state == STATE_CHECK) return false;

states[name] = STATE_CHECKING;

var next = namedTypes[name].NamedType;
if (next is not null && namedTypes.ContainsKey(next))
{
if (HasCircularReference(next, namedTypes, states))
return true;
}

states[name] = STATE_CHECK;
return false;
}

internal void ValidateExtendedTypes()
{
ISet<string> knownNamedTypes = NamedTypes != null
Expand All @@ -119,9 +143,15 @@ internal void ValidateExtendedTypes()

if (NamedTypes != null)
{
var states = new Dictionary<string, int>(NamedTypes.Count, StringComparer.Ordinal);
foreach (var (name, type) in NamedTypes)
{
ExtendedType.EnsureValidNamedTypeIdentifier(name);
if (HasCircularReference(name, NamedTypes, states))
{
throw new FormatException($"Circular reference in namedtypes starting at '{name}'");
}

type.ValidateForNamedTypeDefinition(name, knownNamedTypes);
}
}
Expand Down
194 changes: 193 additions & 1 deletion tests/Neo.UnitTests/SmartContract/Manifest/UT_ExtendedType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -308,7 +308,7 @@ public void FromStackItem_ShouldThrow_WhenTypeMissing()
}

[TestMethod]
public void FromJson_Circular_Reference_ShouldThrow()
public void FromJson_Circular_Reference_A_A_ShouldThrow()
{
var json = (JObject)JToken.Parse(@"
{
Expand All @@ -326,5 +326,197 @@ public void FromJson_Circular_Reference_ShouldThrow()

Assert.ThrowsExactly<FormatException>(() => ContractAbi.FromJson(json));
}

[TestMethod]
public void FromJson_Circular_Reference_A_B_B_A_ShouldThrow()
{
var json = (JObject)JToken.Parse(@"
{
""methods"": [
{ ""name"": ""_deploy"", ""parameters"": [], ""returntype"": ""Void"", ""offset"": 0, ""safe"": true}
],
""events"": [],
""namedtypes"": {
""boo"": {
""type"": ""Array"",
""namedtype"": ""alice""
},
""alice"": {
""type"": ""Array"",
""namedtype"": ""boo""
}
}
}");

Assert.ThrowsExactly<FormatException>(() => ContractAbi.FromJson(json));
}

[TestMethod]
public void FromJson_Circular_Reference_A_B_C_A_ShouldThrow()
{
var json = (JObject)JToken.Parse(@"
{
""methods"": [
{ ""name"": ""_deploy"", ""parameters"": [], ""returntype"": ""Void"", ""offset"": 0, ""safe"": true}
],
""events"": [],
""namedtypes"": {
""boo"": {
""type"": ""Array"",
""namedtype"": ""alice""
},
""alice"": {
""type"": ""Array"",
""namedtype"": ""toc""
},
""toc"": {
""type"": ""Array"",
""namedtype"": ""boo""
}
}
}");

Assert.ThrowsExactly<FormatException>(() => ContractAbi.FromJson(json));
}

[TestMethod]
public void FromJson_Circular_Reference_A_B_C_B_ShouldThrow()
{
var json = (JObject)JToken.Parse(@"
{
""methods"": [
{ ""name"": ""_deploy"", ""parameters"": [], ""returntype"": ""Void"", ""offset"": 0, ""safe"": true}
],
""events"": [],
""namedtypes"": {
""boo"": {
""type"": ""Array"",
""namedtype"": ""alice""
},
""alice"": {
""type"": ""Array"",
""namedtype"": ""toc""
},
""toc"": {
""type"": ""Array"",
""namedtype"": ""alice""
}
}
}");

Assert.ThrowsExactly<FormatException>(() => ContractAbi.FromJson(json));
}

[TestMethod]
public void FromJson_No_Circular_Reference_A_B_C()
{
var json = (JObject)JToken.Parse(@"
{
""methods"": [
{ ""name"": ""_deploy"", ""parameters"": [], ""returntype"": ""Void"", ""offset"": 0, ""safe"": true}
],
""events"": [],
""namedtypes"": {
""boo"": {
""type"": ""Array"",
""namedtype"": ""alice""
},
""alice"": {
""type"": ""Array"",
""namedtype"": ""toc""
},
""toc"":{
""type"": ""Array"",
""value"": {""type"": ""Integer"" }
}
}
}");

var result = ContractAbi.FromJson(json);
Assert.IsTrue(result.NamedTypes.ContainsKey("boo"));
Assert.IsTrue(result.NamedTypes.ContainsKey("alice"));
Assert.IsTrue(result.NamedTypes.ContainsKey("toc"));
}

[TestMethod]
public void FromJson_Circular_Reference_A_NO_B_A_C_A()
{
// A => No namedtype
// B => A
// C => A
var json = (JObject)JToken.Parse(@"
{
""methods"": [
{ ""name"": ""_deploy"", ""parameters"": [], ""returntype"": ""Void"", ""offset"": 0, ""safe"": true}
],
""events"": [],
""namedtypes"": {
""boo"": {
""type"": ""Array"",
""value"": {""type"": ""Integer"" }
},
""alice"": {
""type"": ""Array"",
""namedtype"": ""boo""
},
""toc"": {
""type"": ""Array"",
""namedtype"": ""boo""
}
}
}");

var result = ContractAbi.FromJson(json);
Assert.IsTrue(result.NamedTypes.ContainsKey("boo"));
Assert.IsTrue(result.NamedTypes.ContainsKey("alice"));
Assert.IsTrue(result.NamedTypes.ContainsKey("toc"));
}

[TestMethod]
public void FromJson_Circular_Reference_A_VALUE_A()
{
var json = (JObject)JToken.Parse(@"
{
""methods"": [
{ ""name"": ""_deploy"", ""parameters"": [], ""returntype"": ""Void"", ""offset"": 0, ""safe"": true}
],
""events"": [],
""namedtypes"": {
""boo"": {
""type"": ""Array"",
""value"": {""type"":""Array"",""namedtype"": ""boo"" }
}
}
}");

var result = ContractAbi.FromJson(json);
Assert.IsTrue(result.NamedTypes.ContainsKey("boo"));
}

[TestMethod]
public void FromJson_Circular_Reference_A_VALUE_B_B_VALUE_A()
{
var json = (JObject)JToken.Parse(@"
{
""methods"": [
{ ""name"": ""_deploy"", ""parameters"": [], ""returntype"": ""Void"", ""offset"": 0, ""safe"": true}
],
""events"": [],
""namedtypes"": {
""boo"": {
""type"": ""Array"",
""value"": {""type"":""Array"",""namedtype"": ""alice"" }
},
""alice"": {
""type"": ""Array"",
""value"": {""type"":""Array"",""namedtype"": ""boo"" }
}
}
}");

var result = ContractAbi.FromJson(json);
Assert.IsTrue(result.NamedTypes.ContainsKey("boo"));
Assert.IsTrue(result.NamedTypes.ContainsKey("alice"));
}
}
}