From 83fb255e471e0bf738ed9053e547d65321947e18 Mon Sep 17 00:00:00 2001
From: smblucker <smblucker@outlook.com>
Date: Mon, 5 Mar 2018 16:32:22 -0500
Subject: [PATCH] WIP implementation of REVERT opcode

Signed-off-by: smblucker <smblucker@outlook.com>
Signed-off-by: Silas Davis <silas@monax.io>
---
 execution/evm/asm/opcodes.go |  2 ++
 execution/evm/vm.go          | 20 ++++++++++++++++++++
 execution/evm/vm_test.go     | 22 ++++++++++++++++++++++
 3 files changed, 44 insertions(+)

diff --git a/execution/evm/asm/opcodes.go b/execution/evm/asm/opcodes.go
index 0a4e3531..f85f3b4d 100644
--- a/execution/evm/asm/opcodes.go
+++ b/execution/evm/asm/opcodes.go
@@ -182,6 +182,7 @@ const (
 	DELEGATECALL
 
 	// 0x70 range - other
+	REVERT       = 0xfd
 	SELFDESTRUCT = 0xff
 )
 
@@ -334,6 +335,7 @@ var opCodeNames = map[OpCode]string{
 	DELEGATECALL: "DELEGATECALL",
 
 	// 0x70 range - other
+	REVERT:       "REVERT",
 	SELFDESTRUCT: "SELFDESTRUCT",
 }
 
diff --git a/execution/evm/vm.go b/execution/evm/vm.go
index ed760c17..03a7249e 100644
--- a/execution/evm/vm.go
+++ b/execution/evm/vm.go
@@ -48,6 +48,7 @@ var (
 	ErrDataStackUnderflow     = errors.New("Data stack underflow")
 	ErrInvalidContract        = errors.New("Invalid contract")
 	ErrNativeContractCodeCopy = errors.New("Tried to copy native contract code")
+	ErrExecutionReverted      = errors.New("Execution reverted")
 )
 
 type ErrPermission struct {
@@ -773,6 +774,10 @@ func (vm *VM) call(caller, callee acm.MutableAccount, code, input []byte, value
 				stack.Push(newAccount.Address().Word256())
 			}
 
+			if err_ == ErrExecutionReverted {
+				return ret, nil
+			}
+
 		case CALL, CALLCODE, DELEGATECALL: // 0xF1, 0xF2, 0xF4
 			if !HasPermission(vm.state, callee, permission.Call) {
 				return nil, ErrPermission{"call"}
@@ -863,6 +868,10 @@ func (vm *VM) call(caller, callee acm.MutableAccount, code, input []byte, value
 				// TODO: we probably don't want to return the error - decide
 				//err = firstErr(err, callErr)
 				stack.Push(Zero256)
+
+				if callErr == ErrExecutionReverted {
+					memory.Write(retOffset, RightPadBytes(ret, int(retSize)))
+				}
 			} else {
 				stack.Push(One256)
 
@@ -891,6 +900,17 @@ func (vm *VM) call(caller, callee acm.MutableAccount, code, input []byte, value
 			vm.Debugf(" => [%v, %v] (%d) 0x%X\n", offset, size, len(output), output)
 			return output, nil
 
+		case REVERT: // 0xFD
+			offset, size := stack.PopBigInt(), stack.PopBigInt()
+			output, memErr := memory.Read(offset, size)
+			if memErr != nil {
+				vm.Debugf(" => Memory err: %s", memErr)
+				return nil, firstErr(err, ErrMemoryOutOfBounds)
+			}
+
+			vm.Debugf(" => [%v, %v] (%d) 0x%X\n", offset, size, len(output), output)
+			return output, ErrExecutionReverted
+
 		case SELFDESTRUCT: // 0xFF
 			addr := stack.Pop()
 			if useGasNegative(gas, GasGetAccount, &err) {
diff --git a/execution/evm/vm_test.go b/execution/evm/vm_test.go
index a6335dee..0d8ee817 100644
--- a/execution/evm/vm_test.go
+++ b/execution/evm/vm_test.go
@@ -158,6 +158,28 @@ func TestSubcurrency(t *testing.T) {
 	}
 }
 
+//This test case is taken from EIP-140 (https://github.com/ethereum/EIPs/blob/master/EIPS/eip-140.md);
+//it is meant to test the implementation of the REVERT opcode
+func TestRevert(t *testing.T) {
+	ourVm := NewVM(newAppState(), DefaultDynamicMemoryProvider, newParams(), acm.ZeroAddress, nil, logger)
+
+	// Create accounts
+	account1 := newAccount(1)
+	account2 := newAccount(1, 0, 1)
+
+	var gas uint64 = 100000
+
+	bytecode := MustSplice(PUSH32, 0x72, 0x65, 0x76, 0x65, 0x72, 0x74, 0x20, 0x6D, 0x65, 0x73, 0x73, 0x61,
+		0x67, 0x65, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+		0x00, 0x00, 0x00, PUSH1, 0x00, MSTORE, PUSH1, 0x0E, PUSH1, 0x00, REVERT)
+
+	start := time.Now()
+	output, err := ourVm.Call(account1, account2, bytecode, []byte{}, 0, &gas)
+	assert.Error(t, err, "Expected execution reverted error")
+	fmt.Printf("Output: %v Error: %v\n", output, err)
+	fmt.Println("Call took:", time.Since(start))
+}
+
 // Test sending tokens from a contract to another account
 func TestSendCall(t *testing.T) {
 	fakeAppState := newAppState()
-- 
GitLab