Repository: vyperlang/vyper

Issue:
Reimplement `shift` using constantinople opcodes
With the inclusion of [EIP 145](https://eips.ethereum.org/EIPS/eip-145) in constantinople we can reimplement `shift` using `SHL`/`SHR`.


Discussion:
This is a good idea! :)
Would be a great way to test #1230
Also I think we should add `shl` and `shr` functions (or `shiftl`/`shiftr`). I don't know what `shift` does without checking the documentation, but `shl` and `shr` are pretty obvious.
@charles-cooper sometimes languages provide `shift(word, bits)` where `word` would be a `uint256` integer and `bits` would be a `int128` in our case. `bits` would have to be within the range [-255, 255] to be considered valid. A negative value for `bits` would be a right shift, and non-negative value would mean a left shift.

LISP (of which LLL is loosely based on) does this: http://clhs.lisp.se/Body/f_ash.htm
EIP 145 brings [three different opcodes](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-145.md)  for shifting to the forefront, so we might want to build a higher-level API that provides the right opcodes on the situation (particularly `SHR` and `SAR` depending on the signed-ness of the first operand)

`shift` would allow us to do that that.
I think choosing an opcode for the user based on the type is too magical and can lead to difficult to debug situations where they accidentally feed `uint128` to shift and get SAR when they meant to convert first and get SHR. The type system should help prevent subtle errors, not propagate them! (So vyper's SAR should only accept signed integers and SHR should only accept unsigned). At least, we should expose them to the user and then the user has a choice between the high level usage (shift) and lower level functions which have a one to one correspondence to opcodes.
Sounds like a great compromise!
Constantinople is in!
Meeting notes: 
- We have to investigate the 3 opcodes 
- If we change the semantics we need a VIP
Note to self, should look at this after #1783

Resolving patch:
diff --git a/vyper/functions/functions.py b/vyper/functions/functions.py
--- a/vyper/functions/functions.py
+++ b/vyper/functions/functions.py
@@ -8,6 +8,9 @@
     StructureException,
     TypeMismatchException,
 )
+from vyper.opcodes import (
+    version_check,
+)
 from vyper.parser.expr import (
     Expr,
 )
@@ -1063,19 +1066,35 @@ def bitwise_not(expr, args, kwargs, context):
 
 @signature('uint256', 'int128')
 def shift(expr, args, kwargs, context):
+
+    if args[1].typ.is_literal:
+        shift_abs = abs(args[1].value)
+    else:
+        shift_abs = ['sub', 0, '_s']
+
+    if version_check(begin="constantinople"):
+        left_shift = ['shl', '_s', '_v']
+        right_shift = ['shr', shift_abs, '_v']
+    else:
+        # If second argument is positive, left-shift so multiply by a power of two
+        # If it is negative, divide by a power of two
+        # node that if the abs of the second argument >= 256, then in the EVM
+        # 2**(second arg) = 0, and multiplying OR dividing by 0 gives 0
+        left_shift = ['mul', '_v', ['exp', 2, '_s']]
+        right_shift = ['div', '_v', ['exp', 2, shift_abs]]
+
+    if not args[1].typ.is_literal:
+        node_list = ['if', ['slt', '_s', 0], right_shift, left_shift]
+    elif args[1].value >= 0:
+        node_list = left_shift
+    else:
+        node_list = right_shift
+
     return LLLnode.from_list(
         [
             'with', '_v', args[0], [
-                'with', '_s', args[1], [
-                    # If second argument is positive, left-shift so multiply by a power of two
-                    # If it is negative, divide by a power of two
-                    # node that if the abs of the second argument >= 256, then in the EVM
-                    # 2**(second arg) = 0, and multiplying OR dividing by 0 gives 0
-                    'if',
-                    ['slt', '_s', 0],
-                    ['div', '_v', ['exp', 2, ['sub', 0, '_s']]],
-                    ['mul', '_v', ['exp', 2, '_s']]
-                ],
+                'with', '_s', args[1],
+                    node_list,
             ],
         ],
         typ=BaseType('uint256'),


Test patch:
diff --git a/tests/parser/functions/test_bitwise.py b/tests/parser/functions/test_bitwise.py
--- a/tests/parser/functions/test_bitwise.py
+++ b/tests/parser/functions/test_bitwise.py
@@ -1,5 +1,13 @@
-def test_test_bitwise(get_contract_with_gas_estimation):
-    test_bitwise = """
+import pytest
+
+from vyper.compiler import (
+    compile_code,
+)
+from vyper.opcodes import (
+    EVM_VERSIONS,
+)
+
+code = """
 @public
 def _bitwise_and(x: uint256, y: uint256) -> uint256:
     return bitwise_and(x, y)
@@ -21,7 +29,21 @@ def _shift(x: uint256, y: int128) -> uint256:
     return shift(x, y)
     """
 
-    c = get_contract_with_gas_estimation(test_bitwise)
+
+@pytest.mark.parametrize('evm_version', list(EVM_VERSIONS))
+def test_bitwise_opcodes(evm_version):
+    opcodes = compile_code(code, ['opcodes'], evm_version=evm_version)['opcodes']
+    if evm_version == "byzantium":
+        assert "SHL" not in opcodes
+        assert "SHR" not in opcodes
+    else:
+        assert "SHL" in opcodes
+        assert "SHR" in opcodes
+
+
+@pytest.mark.parametrize('evm_version', list(EVM_VERSIONS))
+def test_test_bitwise(get_contract_with_gas_estimation, evm_version):
+    c = get_contract_with_gas_estimation(code, evm_version=evm_version)
     x = 126416208461208640982146408124
     y = 7128468721412412459
     assert c._bitwise_and(x, y) == (x & y)
@@ -37,4 +59,19 @@ def _shift(x: uint256, y: int128) -> uint256:
     assert c._shift(x, -3) == x // 8
     assert c._shift(x, -256) == 0
 
-    print("Passed bitwise operation tests")
+
+@pytest.mark.parametrize('evm_version', list(EVM_VERSIONS))
+def test_literals(get_contract, evm_version):
+    code = """
+@public
+def left(x: uint256) -> uint256:
+    return shift(x, -3)
+
+@public
+def right(x: uint256) -> uint256:
+    return shift(x, 3)
+    """
+
+    c = get_contract(code, evm_version=evm_version)
+    assert c.left(80) == 10
+    assert c.right(80) == 640