Repository: vyperlang/vyper

Issue:
Add sha256 function
## Simple Summary
Currently Vyper doesn't 

## Abstract
Add sha256 function, which hcalls 

## Motivation
Currently `sha256` precompile isn't exposed as a function, this VIP adds the functionality.

## Specification

```python
def sha256(bytelike: string) -> bytes32:
      # bytelike can be one of  byte_literal, str_literal, bytes, string, bytes32
      # ...
```

This function will make a call to the pre-compile at  address `0x000...0002`, and return the output as bytes.

## Backwards Compatibility
Feature, forwards compatible.

## Dependencies
No dependencies.

## Copyright
Copyright and related rights waived via [CC0](https://creativecommons.org/publicdomain/zero/1.0/)



Discussion:
There is currently workaround on the deposit contract: https://github.com/ethereum/deposit_contract/pull/14  using raw call, so quite keen on getting this in on b9 or b10. 

Resolving patch:
diff --git a/vyper/functions/functions.py b/vyper/functions/functions.py
--- a/vyper/functions/functions.py
+++ b/vyper/functions/functions.py
@@ -1,4 +1,5 @@
 import ast
+import hashlib
 
 from vyper.exceptions import (
     ConstancyViolationException,
@@ -54,6 +55,11 @@
 )
 
 
+SHA256_ADDRESS = 2
+SHA256_BASE_GAS = 60
+SHA256_PER_WORD_GAS = 12
+
+
 def enforce_units(typ, obj, expected):
     if not are_units_compatible(typ, expected):
         raise TypeMismatchException("Invalid units", obj)
@@ -301,12 +307,16 @@ def concat(expr, context):
     )
 
 
-@signature(('str_literal', 'bytes', 'string', 'bytes32'))
+@signature(('bytes_literal', 'str_literal', 'bytes', 'string', 'bytes32'))
 def _sha3(expr, args, kwargs, context):
     sub = args[0]
     # Can hash literals
     if isinstance(sub, bytes):
-        return LLLnode.from_list(bytes_to_int(sha3(sub)), typ=BaseType('bytes32'), pos=getpos(expr))
+        return LLLnode.from_list(
+            bytes_to_int(sha3(sub)),
+            typ=BaseType('bytes32'),
+            pos=getpos(expr)
+        )
     # Can hash bytes32 objects
     if is_base_type(sub.typ, 'bytes32'):
         return LLLnode.from_list(
@@ -350,6 +360,99 @@ def _sha3(expr, args, kwargs, context):
     )
 
 
+def _make_sha256_call(inp_start, inp_len, out_start, out_len):
+    return [
+        'assert', [
+            'call',
+            ['gas'],  # gas
+            SHA256_ADDRESS,  # address
+            0,  # value
+            inp_start,
+            inp_len,
+            out_start,
+            out_len
+        ]
+    ]
+
+
+@signature(('bytes_literal', 'str_literal', 'bytes', 'string', 'bytes32'))
+def sha256(expr, args, kwargs, context):
+    sub = args[0]
+    # Literal input
+    if isinstance(sub, bytes):
+        return LLLnode.from_list(
+            bytes_to_int(hashlib.sha256(sub).digest()),
+            typ=BaseType('bytes32'),
+            pos=getpos(expr)
+        )
+    # bytes32 input
+    elif is_base_type(sub.typ, 'bytes32'):
+        return LLLnode.from_list(
+            [
+                'seq',
+                ['mstore', MemoryPositions.FREE_VAR_SPACE, sub],
+                _make_sha256_call(
+                    inp_start=MemoryPositions.FREE_VAR_SPACE,
+                    inp_len=32,
+                    out_start=MemoryPositions.FREE_VAR_SPACE,
+                    out_len=32
+                ),
+                ['mload', MemoryPositions.FREE_VAR_SPACE]  # push value onto stack
+            ],
+            typ=BaseType('bytes32'),
+            pos=getpos(expr),
+            add_gas_estimate=SHA256_BASE_GAS + 1 * SHA256_PER_WORD_GAS
+        )
+    # bytearay-like input
+    if sub.location == "storage":
+        # Copy storage to memory
+        placeholder = context.new_placeholder(sub.typ)
+        placeholder_node = LLLnode.from_list(placeholder, typ=sub.typ, location='memory')
+        copier = make_byte_array_copier(
+            placeholder_node,
+            LLLnode.from_list('_sub', typ=sub.typ, location=sub.location),
+        )
+        return LLLnode.from_list(
+            [
+                'with', '_sub', sub, [
+                    'seq',
+                    copier,
+                    _make_sha256_call(
+                        inp_start=['add', placeholder, 32],
+                        inp_len=['mload', placeholder],
+                        out_start=MemoryPositions.FREE_VAR_SPACE,
+                        out_len=32
+                    ),
+                    ['mload', MemoryPositions.FREE_VAR_SPACE]
+                ],
+            ],
+            typ=BaseType('bytes32'),
+            pos=getpos(expr),
+            add_gas_estimate=SHA256_BASE_GAS + sub.typ.maxlen * SHA256_PER_WORD_GAS
+        )
+    elif sub.location == "memory":
+        return LLLnode.from_list(
+            [
+                'with', '_sub', sub, [
+                    'seq',
+                    _make_sha256_call(
+                        inp_start=['add', '_sub', 32],
+                        inp_len=['mload', '_sub'],
+                        out_start=MemoryPositions.FREE_VAR_SPACE,
+                        out_len=32
+                    ),
+                    ['mload', MemoryPositions.FREE_VAR_SPACE]
+                ]
+            ],
+            typ=BaseType('bytes32'),
+            pos=getpos(expr),
+            add_gas_estimate=SHA256_BASE_GAS + sub.typ.maxlen * SHA256_PER_WORD_GAS
+        )
+    else:
+        # This should never happen, but just left here for future compiler-writers.
+        raise Exception("Unsupported location: %s" % sub.location)  # pragma: no test
+
+
 @signature('str_literal', 'name_literal')
 def method_id(expr, args, kwargs, context):
     if b' ' in args[0]:
@@ -1142,6 +1245,7 @@ def _clear():
     'len': _len,
     'concat': concat,
     'sha3': _sha3,
+    'sha256': sha256,
     'method_id': method_id,
     'keccak256': _sha3,
     'ecrecover': ecrecover,
diff --git a/vyper/functions/signature.py b/vyper/functions/signature.py
--- a/vyper/functions/signature.py
+++ b/vyper/functions/signature.py
@@ -55,6 +55,9 @@ def process_arg(index, arg, expected_arg_typelist, function_name, context):
                         )
                     bytez += bytes([ord(c)])
                 return bytez
+        elif expected_arg == 'bytes_literal':
+            if isinstance(arg, ast.Bytes):
+                return arg.s
         elif expected_arg == 'name_literal':
             if isinstance(arg, ast.Name):
                 return arg.id


Test patch:
diff --git a/tests/parser/functions/test_sha256.py b/tests/parser/functions/test_sha256.py
new file mode 100644
--- /dev/null
+++ b/tests/parser/functions/test_sha256.py
@@ -0,0 +1,75 @@
+import hashlib
+
+
+def test_sha256_string_literal(get_contract_with_gas_estimation):
+    code = """
+@public
+def bar() -> bytes32:
+    return sha256("test")
+    """
+
+    c = get_contract_with_gas_estimation(code)
+
+    assert c.bar() == hashlib.sha256(b"test").digest()
+
+
+def test_sha256_literal_bytes(get_contract_with_gas_estimation):
+    code = """
+@public
+def bar() -> (bytes32 , bytes32):
+    x: bytes32 = sha256("test")
+    y: bytes32 = sha256(b"test")
+    return x, y
+    """
+    c = get_contract_with_gas_estimation(code)
+    h = hashlib.sha256(b"test").digest()
+    assert c.bar() == [h, h]
+
+
+def test_sha256_bytes32(get_contract_with_gas_estimation):
+    code = """
+@public
+def bar(a: bytes32) -> bytes32:
+    return sha256(a)
+    """
+
+    c = get_contract_with_gas_estimation(code)
+
+    test_val = 8 * b"bBaA"
+    assert c.bar(test_val) == hashlib.sha256(test_val).digest()
+
+
+def test_sha256_bytearraylike(get_contract_with_gas_estimation):
+    code = """
+@public
+def bar(a: string[100]) -> bytes32:
+    return sha256(a)
+    """
+
+    c = get_contract_with_gas_estimation(code)
+
+    test_val = "test me! test me!"
+    assert c.bar(test_val) == hashlib.sha256(test_val.encode()).digest()
+    test_val = "fun"
+    assert c.bar(test_val) == hashlib.sha256(test_val.encode()).digest()
+
+
+def test_sha256_bytearraylike_storage(get_contract_with_gas_estimation):
+    code = """
+a: public(bytes[100])
+
+@public
+def set(b: bytes[100]):
+    self.a = b
+
+@public
+def bar() -> bytes32:
+    return sha256(self.a)
+    """
+
+    c = get_contract_with_gas_estimation(code)
+
+    test_val = b"test me! test me!"
+    c.set(test_val, transact={})
+    assert c.a() == test_val
+    assert c.bar() == hashlib.sha256(test_val).digest()
diff --git a/tests/parser/functions/test_sha3.py b/tests/parser/functions/test_sha3.py
--- a/tests/parser/functions/test_sha3.py
+++ b/tests/parser/functions/test_sha3.py
@@ -4,6 +4,10 @@ def test_hash_code(get_contract_with_gas_estimation, keccak):
 def foo(inp: bytes[100]) -> bytes32:
     return sha3(inp)
 
+@public
+def foob() -> bytes32:
+    return sha3(b"inp")
+
 @public
 def bar() -> bytes32:
     return sha3("inp")
@@ -14,6 +18,7 @@ def bar() -> bytes32:
         assert '0x' + c.foo(inp).hex() == keccak(inp).hex()
 
     assert '0x' + c.bar().hex() == keccak(b"inp").hex()
+    assert '0x' + c.foob().hex() == keccak(b"inp").hex()
 
 
 def test_hash_code2(get_contract_with_gas_estimation):