From 2fac7efb34c4e9dc95ef45c95c8fe7733cae74d6 Mon Sep 17 00:00:00 2001
From: Christoph Burgdorf <christoph.burgdorf@gmail.com>
Date: Wed, 1 Aug 2018 11:52:36 +0200
Subject: [PATCH] Implement SAR opcode for Constantinople

Closes #1104
---
 eth/constants.py                       |   1 +
 eth/vm/forks/constantinople/opcodes.py |   5 ++
 eth/vm/logic/arithmetic.py             |  15 ++++
 eth/vm/mnemonics.py                    |   1 +
 eth/vm/opcode_values.py                |   1 +
 tests/core/opcodes/test_opcodes.py     | 113 +++++++++++++++++++++++++
 6 files changed, 136 insertions(+)

diff --git a/eth/constants.py b/eth/constants.py
index a5a5932f18..941b9effbd 100644
--- a/eth/constants.py
+++ b/eth/constants.py
@@ -13,6 +13,7 @@
 UINT_256_CEILING = 2**256
 UINT_255_MAX = 2**255 - 1
 UINT_255_CEILING = 2**255
+UINT_255_NEGATIVE_ONE = -1 + UINT_256_CEILING
 NULL_BYTE = b'\x00'
 EMPTY_WORD = NULL_BYTE * 32
 
diff --git a/eth/vm/forks/constantinople/opcodes.py b/eth/vm/forks/constantinople/opcodes.py
index a3a5392f81..a63821c0a1 100644
--- a/eth/vm/forks/constantinople/opcodes.py
+++ b/eth/vm/forks/constantinople/opcodes.py
@@ -32,6 +32,11 @@
         mnemonic=mnemonics.SHR,
         gas_cost=constants.GAS_VERYLOW,
     ),
+    opcode_values.SAR: as_opcode(
+        logic_fn=arithmetic.sar,
+        mnemonic=mnemonics.SAR,
+        gas_cost=constants.GAS_VERYLOW,
+    ),
 }
 
 CONSTANTINOPLE_OPCODES = merge(
diff --git a/eth/vm/logic/arithmetic.py b/eth/vm/logic/arithmetic.py
index a667ba4eae..f52aa628bd 100644
--- a/eth/vm/logic/arithmetic.py
+++ b/eth/vm/logic/arithmetic.py
@@ -205,3 +205,18 @@ def shr(computation):
         result = (value >> shift_length) & constants.UINT_256_MAX
 
     computation.stack_push(result)
+
+
+def sar(computation):
+    """
+    Arithmetic bitwise right shift
+    """
+    shift_length, value = computation.stack_pop(num_items=2, type_hint=constants.UINT256)
+    value = unsigned_to_signed(value)
+
+    if shift_length >= 256:
+        result = 0 if value >= 0 else constants.UINT_255_NEGATIVE_ONE
+    else:
+        result = (value >> shift_length) & constants.UINT_256_MAX
+
+    computation.stack_push(result)
diff --git a/eth/vm/mnemonics.py b/eth/vm/mnemonics.py
index a830f37f1d..09611a4126 100644
--- a/eth/vm/mnemonics.py
+++ b/eth/vm/mnemonics.py
@@ -15,6 +15,7 @@
 SIGNEXTEND = 'SIGNEXTEND'
 SHL = 'SHL'
 SHR = 'SHR'
+SAR = 'SAR'
 #
 # Comparisons
 #
diff --git a/eth/vm/opcode_values.py b/eth/vm/opcode_values.py
index 264fe2dbe7..baac7f4024 100644
--- a/eth/vm/opcode_values.py
+++ b/eth/vm/opcode_values.py
@@ -31,6 +31,7 @@
 BYTE = 0x1a
 SHL = 0x1b
 SHR = 0x1c
+SAR = 0x1d
 
 
 #
diff --git a/tests/core/opcodes/test_opcodes.py b/tests/core/opcodes/test_opcodes.py
index 897ace3cee..5ecd0f0e21 100644
--- a/tests/core/opcodes/test_opcodes.py
+++ b/tests/core/opcodes/test_opcodes.py
@@ -265,3 +265,116 @@ def test_shr(vm_class, val1, val2, expected):
 
     result = computation.stack_pop(type_hint=constants.UINT256)
     assert encode_hex(pad32(int_to_big_endian(result))) == expected
+
+
+@pytest.mark.parametrize(
+    # EIP: https://github.com/ethereum/EIPs/blob/master/EIPS/eip-145.md#sar-arithmetic-shift-right
+    'vm_class, val1, val2, expected',
+    (
+        (
+            ConstantinopleVM,
+            '0x0000000000000000000000000000000000000000000000000000000000000001',
+            '0x00',
+            '0x0000000000000000000000000000000000000000000000000000000000000001',
+        ),
+        (
+            ConstantinopleVM,
+            '0x0000000000000000000000000000000000000000000000000000000000000001',
+            '0x01',
+            '0x0000000000000000000000000000000000000000000000000000000000000000',
+        ),
+        (
+            ConstantinopleVM,
+            '0x8000000000000000000000000000000000000000000000000000000000000000',
+            '0x01',
+            '0xc000000000000000000000000000000000000000000000000000000000000000',
+        ),
+        (
+            ConstantinopleVM,
+            '0x8000000000000000000000000000000000000000000000000000000000000000',
+            '0xff',
+            '0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff',
+        ),
+        (
+            ConstantinopleVM,
+            '0x8000000000000000000000000000000000000000000000000000000000000000',
+            '0x0100',
+            '0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff',
+        ),
+        (
+            ConstantinopleVM,
+            '0x8000000000000000000000000000000000000000000000000000000000000000',
+            '0x0101',
+            '0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff',
+        ),
+        (
+            ConstantinopleVM,
+            '0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff',
+            '0x00',
+            '0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff',
+        ),
+        (
+            ConstantinopleVM,
+            '0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff',
+            '0x01',
+            '0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff',
+        ),
+        (
+            ConstantinopleVM,
+            '0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff',
+            '0xff',
+            '0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff',
+        ),
+        (
+            ConstantinopleVM,
+            '0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff',
+            '0x0100',
+            '0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff',
+        ),
+        (
+            ConstantinopleVM,
+            '0x0000000000000000000000000000000000000000000000000000000000000000',
+            '0x01',
+            '0x0000000000000000000000000000000000000000000000000000000000000000',
+        ),
+
+        (
+            ConstantinopleVM,
+            '0x4000000000000000000000000000000000000000000000000000000000000000',
+            '0xfe',
+            '0x0000000000000000000000000000000000000000000000000000000000000001',
+        ),
+        (
+            ConstantinopleVM,
+            '0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff',
+            '0xf8',
+            '0x000000000000000000000000000000000000000000000000000000000000007f',
+        ),
+        (
+            ConstantinopleVM,
+            '0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff',
+            '0xfe',
+            '0x0000000000000000000000000000000000000000000000000000000000000001',
+        ),
+        (
+            ConstantinopleVM,
+            '0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff',
+            '0xff',
+            '0x0000000000000000000000000000000000000000000000000000000000000000',
+        ),
+        (
+            ConstantinopleVM,
+            '0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff',
+            '0x0100',
+            '0x0000000000000000000000000000000000000000000000000000000000000000',
+        ),
+    )
+)
+def test_sar(vm_class, val1, val2, expected):
+    computation = prepare_computation(vm_class)
+    computation.stack_push(decode_hex(val1))
+    computation.stack_push(decode_hex(val2))
+    computation.opcodes[opcode_values.SAR](computation)
+
+    result = computation.stack_pop(type_hint=constants.UINT256)
+    assert encode_hex(pad32(int_to_big_endian(result))) == expected