Repository: vyperlang/vyper

Issue:
Cannot generate AST from ABI containing string or bytes
### Version Information
* vyper Version: 0.1.0b16
* OS: linux
* Python Version: 3.7

### What's your issue about?
When importing a `json` file as an interface, if any inputs or outputs use type `string` or `bytes`, there is a `TypeError` raised:

```python
  File "vyper/signatures/interface.py", line 72, in abi_type_to_ast
    slice=ast.Index(256)
TypeError: __init__() takes 1 positional argument but 2 were given
```

It seems `ast.Index` is incorrect being initiated for these types:

https://github.com/vyperlang/vyper/blob/d4c53204ce4bb3a314443f6d6deadbb2b2111cdd/vyper/signatures/interface.py#L59-L73



Discussion:
ABI encoder ftw

Resolving patch:
diff --git a/vyper/signatures/interface.py b/vyper/signatures/interface.py
--- a/vyper/signatures/interface.py
+++ b/vyper/signatures/interface.py
@@ -30,6 +30,10 @@
 from vyper.signatures.function_signature import (
     FunctionSignature,
 )
+from vyper.types.types import (
+    ByteArrayLike,
+    TupleLike,
+)
 from vyper.typing import (
     InterfaceImports,
     SourceCode,
@@ -56,25 +60,26 @@ def render_return(sig):
     return ""
 
 
-def abi_type_to_ast(atype):
+def abi_type_to_ast(atype, expected_size):
     if atype in ('int128', 'uint256', 'bool', 'address', 'bytes32'):
         return ast.Name(id=atype)
-    elif atype == 'decimal':
-        return ast.Name(id='int128')
-    elif atype == 'bytes':
-        return ast.Subscript(
-            value=ast.Name(id='bytes'),
-            slice=ast.Index(256)
-        )
-    elif atype == 'string':
+    elif atype == 'fixed168x10':
+        return ast.Name(id='decimal')
+    elif atype in ('bytes', 'string'):
+        # expected_size is the maximum length for inputs, minimum length for outputs
         return ast.Subscript(
-            value=ast.Name(id='string'),
-            slice=ast.Index(256)
+            value=ast.Name(id=atype),
+            slice=ast.Index(value=ast.Num(n=expected_size))
         )
     else:
         raise ParserException(f'Type {atype} not supported by vyper.')
 
 
+# Vyper defines a maximum length for bytes and string types, but Solidity does not.
+# To maximize interoperability, we internally considers these types to have a
+# a length of 1Mb (1024 * 1024 * 1 byte) for inputs, and 1 for outputs.
+# Ths approach solves the issue because Vyper allows for an implicit casting
+# from a lower length into a higher one.  (@iamdefinitelyahuman)
 def mk_full_signature_from_json(abi):
     funcs = [func for func in abi if func['type'] == 'function']
     sigs = []
@@ -85,18 +90,18 @@ def mk_full_signature_from_json(abi):
         for a in func['inputs']:
             arg = ast.arg(
                 arg=a['name'],
-                annotation=abi_type_to_ast(a['type']),
+                annotation=abi_type_to_ast(a['type'], 1048576),
                 lineno=0,
                 col_offset=0
             )
             args.append(arg)
 
         if len(func['outputs']) == 1:
-            returns = abi_type_to_ast(func['outputs'][0]['type'])
+            returns = abi_type_to_ast(func['outputs'][0]['type'], 1)
         elif len(func['outputs']) > 1:
             returns = ast.Tuple(
                 elts=[
-                    abi_type_to_ast(a['type'])
+                    abi_type_to_ast(a['type'], 1)
                     for a in func['outputs']
                 ]
             )
@@ -294,16 +299,18 @@ def check_valid_contract_interface(global_ctx, contract_sigs):
 
         for sig, func_sig in contract_sigs.items():
             if isinstance(func_sig, FunctionSignature):
-                # Remove units, as inteface signatures should not enforce units.
+                if func_sig.private:
+                    # private functions are not defined within interfaces
+                    continue
+                if sig not in funcs_left:
+                    # this function is not present within the interface
+                    continue
+                # Remove units, as interface signatures should not enforce units.
                 clean_sig_output_type = func_sig.output_type
                 if func_sig.output_type:
                     clean_sig_output_type = copy.deepcopy(func_sig.output_type)
                     clean_sig_output_type.unit = {}
-                if (
-                    sig in funcs_left and  # noqa: W504
-                    not func_sig.private and  # noqa: W504
-                    funcs_left[sig].output_type == clean_sig_output_type
-                ):
+                if _compare_outputs(funcs_left[sig].output_type, clean_sig_output_type):
                     del funcs_left[sig]
             if isinstance(func_sig, EventSignature) and func_sig.sig in funcs_left:
                 del funcs_left[func_sig.sig]
@@ -329,3 +336,19 @@ def check_valid_contract_interface(global_ctx, contract_sigs):
                 err_join = "\n\t".join(missing_events)
                 error_message += f'Missing interface events:\n\t{err_join}'
             raise StructureException(error_message)
+
+
+def _compare_outputs(a, b):
+    if isinstance(a, TupleLike):
+        # for tuples and structs, compare the length and individual members
+        if type(a) != type(b):
+            return False
+        if len(a.tuple_members()) != len(b.tuple_members()):
+            return False
+        compare = zip(a.tuple_members(), b.tuple_members())
+        return next((False for i in compare if not _compare_outputs(*i)), True)
+    if isinstance(a, ByteArrayLike):
+        # for string and bytes, only the type matters (not the length)
+        return type(a) == type(b)
+    # for all other types, check strict equality
+    return a == b


Test patch:
diff --git a/tests/parser/functions/test_interfaces.py b/tests/parser/functions/test_interfaces.py
--- a/tests/parser/functions/test_interfaces.py
+++ b/tests/parser/functions/test_interfaces.py
@@ -1,3 +1,7 @@
+from decimal import (
+    Decimal,
+)
+
 import pytest
 
 from vyper.compiler import (
@@ -277,60 +281,6 @@ def test():
     assert erc20.balanceOf(sender) == 1000
 
 
-def test_json_interface(get_contract):
-    code = """
-import folding as Folding
-
-implements: Folding
-
-@public
-def test(a: uint256) -> uint256:
-    return 1 + a
-
-
-@public
-def test2(a: uint256):
-    pass
-    """
-
-    interface_codes = {
-        'Folding': {
-            'type': 'json',
-            'code': [
-                {
-                    "name": "test",
-                    "outputs": [{
-                        "type": "uint256",
-                        "name": "out"
-                    }],
-                    "inputs": [{
-                        "type": "uint256",
-                        "name": "s"
-                    }],
-                    "constant": False,
-                    "payable": False,
-                    "type": "function",
-                },
-                {
-                    "name": "test2",
-                    "outputs": [],
-                    "inputs": [{
-                        "type": "uint256",
-                        "name": "s"
-                    }],
-                    "constant": False,
-                    "payable": False,
-                    "type": "function",
-                }
-            ]
-        }
-    }
-
-    c = get_contract(code, interface_codes=interface_codes)
-
-    assert c.test(2) == 3
-
-
 def test_units_interface(w3, get_contract):
     code = """
 import balanceof as BalanceOf
@@ -468,3 +418,50 @@ def bar(a: address) -> uint256:
     """
     c = get_contract(code)
     assert_tx_failed(lambda: c.bar(c.address))
+
+
+type_str_params = [
+    ('int128', -33),
+    ('uint256', 42),
+    ('bool', True),
+    ('address', "0x1234567890123456789012345678901234567890"),
+    ('bytes32', b"bytes32bytes32bytes32bytes32poop"),
+    ('decimal', Decimal("3.1337")),
+    ('bytes[4]', b"newp"),
+    ('string[6]', "potato"),
+]
+
+interface_test_code = """
+@public
+@constant
+def test_json(a: {0}) -> {0}:
+    return a
+    """
+
+
+@pytest.mark.parametrize("type_str", [i[0] for i in type_str_params])
+def test_json_interface_implements(type_str):
+    code = interface_test_code.format(type_str)
+
+    abi = compile_code(code, ['abi'])['abi']
+    code = f"import jsonabi as jsonabi\nimplements: jsonabi\n{code}"
+    compile_code(code, interface_codes={'jsonabi': {'type': 'json', 'code': abi}})
+
+
+@pytest.mark.parametrize("type_str,value", type_str_params)
+def test_json_interface_calls(get_contract, type_str, value):
+    code = interface_test_code.format(type_str)
+
+    abi = compile_code(code, ['abi'])['abi']
+    c1 = get_contract(code)
+
+    code = f"""
+import jsonabi as jsonabi
+
+@public
+@constant
+def test_call(a: address, b: {type_str}) -> {type_str}:
+    return jsonabi(a).test_json(b)
+    """
+    c2 = get_contract(code, interface_codes={'jsonabi': {'type': 'json', 'code': abi}})
+    assert c2.test_call(c1.address, value) == value