Repository: vyperlang/vyper

Issue:
Method_id function should return bytes.
### What's your issue about?
Currently the `method_id` function return a custom BaseType which makes it not possible to interact with other parts of vyper.

Consider using either `bytes[4]` or 'bytes32'.
Or even better add parameter which supports both?

```python
method_id('transfer(address,uint256)', bytes32)
```

### How can it be fixed?

Fill this in if you know how to fix it.

#### Cute Animal Picture
^.^


Discussion:
Starting

Resolving patch:
diff --git a/vyper/functions/functions.py b/vyper/functions/functions.py
--- a/vyper/functions/functions.py
+++ b/vyper/functions/functions.py
@@ -147,7 +147,7 @@ def concat(expr, context):
     if len(args) < 2:
         raise StructureException("Concat expects at least two arguments", expr)
     for expr_arg, arg in zip(expr.args, args):
-        if not isinstance(arg.typ, ByteArrayType) and not is_base_type(arg.typ, 'bytes32') and not is_base_type(arg.typ, 'method_id'):
+        if not isinstance(arg.typ, ByteArrayType) and not is_base_type(arg.typ, 'bytes32'):
             raise TypeMismatchException("Concat expects byte arrays or bytes32 objects", expr_arg)
     # Maximum length of the output
     total_maxlen = sum([arg.typ.maxlen if isinstance(arg.typ, ByteArrayType) else 32 for arg in args])
@@ -182,10 +182,6 @@ def concat(expr, context):
                                 # Change the position to start at the correct
                                 # place to paste the next value
                                 ['set', '_poz', ['add', '_poz', length]]]])
-        elif isinstance(arg.typ, BaseType) and arg.typ.typ == "method_id":
-            seq.append(['seq',
-                            ['mstore', ['add', placeholder_node, 32], arg.value * 2**224],
-                            ['set', '_poz', ['add', '_poz', 4]]])
         else:
             seq.append(['seq',
                             ['mstore', ['add', placeholder_node, 32], unwrap_location(arg)],
@@ -233,10 +229,22 @@ def _sha3(expr, args, kwargs, context):
     )
 
 
-@signature('str_literal')
+@signature('str_literal', 'name_literal')
 def method_id(expr, args, kwargs, context):
+    if b' ' in args[0]:
+        raise TypeMismatchException('Invalid function signature no spaces allowed.')
     method_id = fourbytes_to_int(sha3(args[0])[:4])
-    return LLLnode(method_id, typ=BaseType('method_id'), pos=getpos(expr))
+    if args[1] == 'bytes32':
+        return LLLnode(method_id, typ=BaseType('bytes32'), pos=getpos(expr))
+    elif args[1] == 'bytes[4]':
+        placeholder = LLLnode.from_list(context.new_placeholder(ByteArrayType(4)))
+        return LLLnode.from_list(
+            ['seq',
+                ['mstore', ['add', placeholder, 4], method_id],
+                ['mstore', placeholder, 4], placeholder],
+            typ=ByteArrayType(4), location='memory', pos=getpos(expr))
+    else:
+        raise StructureException('Can only produce bytes32 or bytes[4] as outputs')
 
 
 @signature('bytes32', 'uint256', 'uint256', 'uint256')
diff --git a/vyper/functions/signature.py b/vyper/functions/signature.py
--- a/vyper/functions/signature.py
+++ b/vyper/functions/signature.py
@@ -51,6 +51,8 @@ def process_arg(index, arg, expected_arg_typelist, function_name, context):
         elif expected_arg == 'name_literal':
             if isinstance(arg, ast.Name):
                 return arg.id
+            elif isinstance(arg, ast.Subscript) and arg.value.id == 'bytes':
+                return 'bytes[%s]' % arg.slice.value.n
         elif expected_arg == '*':
             return arg
         elif expected_arg == 'bytes':


Test patch:
diff --git a/tests/parser/functions/test_method_id.py b/tests/parser/functions/test_method_id.py
--- a/tests/parser/functions/test_method_id.py
+++ b/tests/parser/functions/test_method_id.py
@@ -6,9 +6,53 @@ def double(x: int128) -> int128:
 
 @public
 def returnten() -> int128:
-    ans: bytes[32] = raw_call(self, concat(method_id("double(int128)"), convert(5, 'bytes32')), gas=50000, outsize=32)
+    ans: bytes[32] = raw_call(self, concat(method_id("double(int128)", bytes[4]), convert(5, 'bytes32')), gas=50000, outsize=32)
     return convert(convert(ans, 'bytes32'), 'int128')
     """
     c = get_contract_with_gas_estimation(method_id_test)
     assert c.returnten() == 10
     print("Passed method ID test")
+
+
+def test_method_id_bytes32(get_contract):
+    code = """
+@public
+def sig() -> bytes32:
+    return method_id('transfer(address,uint256)', bytes32)
+    """
+    c = get_contract(code)
+    sig = c.sig()
+
+    assert len(sig) == 32
+    assert sig[-4:] == b"\xa9\x05\x9c\xbb"
+
+
+def test_method_id_bytes4(get_contract):
+    code = """
+@public
+def sig() -> bytes[4]:
+    return method_id('transfer(address,uint256)', bytes[4])
+    """
+    c = get_contract(code)
+    sig = c.sig()
+
+    # assert len(sig) == 4
+    assert sig == b"\xa9\x05\x9c\xbb"
+
+
+def test_method_id_invalid_space(get_contract, assert_compile_failed):
+    code = """
+@public
+def sig() -> bytes32:
+    return method_id('transfer(address, uint256)', bytes32)
+    """
+    assert_compile_failed(lambda: get_contract(code))
+
+
+def test_method_id_invalid_type(get_contract, assert_compile_failed):
+    code = """
+@public
+def sig() -> int128:
+    return method_id('transfer(address,uint256)', int128)
+    """
+    assert_compile_failed(lambda: get_contract(code))