From 05c73b77065d7339199c58581261d7cd778460cb Mon Sep 17 00:00:00 2001
From: Silas Davis <silas@monax.io>
Date: Fri, 2 Mar 2018 18:31:05 +0000
Subject: [PATCH] Use big.Int on Memory interface This also checks for UInt64
 containment and prevents panic from negative offsets via a check on
 forwarding to the underlying implementation (which now uses uint64s)

Signed-off-by: Silas Davis <silas@monax.io>
---
 execution/evm/memory.go      | 53 +++++++++++++++++++++++++++---------
 execution/evm/memory_test.go | 38 ++++++++++++++------------
 execution/evm/vm.go          | 28 +++++++++----------
 3 files changed, 74 insertions(+), 45 deletions(-)

diff --git a/execution/evm/memory.go b/execution/evm/memory.go
index 6eb8456c..12668614 100644
--- a/execution/evm/memory.go
+++ b/execution/evm/memory.go
@@ -3,6 +3,7 @@ package evm
 import (
 	"fmt"
 	"math"
+	"math/big"
 )
 
 const (
@@ -16,7 +17,7 @@ const (
 // unlikely to make a lot of difference.
 var zeroBlock []byte = make([]byte, 32)
 
-// Interface for a bounded linear memory indexed by a single int64 parameter
+// Interface for a bounded linear memory indexed by a single *big.Int parameter
 // for each byte in the memory.
 type Memory interface {
 	// Read a value from the memory store starting at offset
@@ -26,20 +27,23 @@ type Memory interface {
 	//
 	// The value returned should be copy of any underlying memory, not a reference
 	// to the underlying store.
-	Read(offset, length int64) ([]byte, error)
+	Read(offset, length *big.Int) ([]byte, error)
 	// Write a value to the memory starting at offset (the index of the first byte
 	// written will equal offset). The value is provided as bytes to be written
 	// consecutively to the memory store. Return an error if the memory cannot be
 	// written or allocated.
-	Write(offset int64, value []byte) error
+	Write(offset *big.Int, value []byte) error
 	// Returns the current capacity of the memory. For dynamically allocating
 	// memory this capacity can be used as a write offset that is guaranteed to be
 	// unused. Solidity in particular makes this assumption when using MSIZE to
 	// get the current allocated memory.
-	Capacity() int64
+	Capacity() *big.Int
 }
 
-func NewDynamicMemory(initialCapacity, maximumCapacity int64) Memory {
+// Get a new DynamicMemory (note that although we take a maximumCapacity of uint64 we currently
+// limit the maximum to int32 at runtime because we are using a single slice which we cannot guarantee
+// to be indexable above int32 or all validators
+func NewDynamicMemory(initialCapacity, maximumCapacity uint64) Memory {
 	return &dynamicMemory{
 		slice:           make([]byte, initialCapacity),
 		maximumCapacity: maximumCapacity,
@@ -54,10 +58,22 @@ func DefaultDynamicMemoryProvider() Memory {
 // array allocation via a backing slice
 type dynamicMemory struct {
 	slice           []byte
-	maximumCapacity int64
+	maximumCapacity uint64
 }
 
-func (mem *dynamicMemory) Read(offset, length int64) ([]byte, error) {
+func (mem *dynamicMemory) Read(offset, length *big.Int) ([]byte, error) {
+	// Ensures positive and not too wide
+	if !offset.IsUint64() {
+		return nil, fmt.Errorf("offset %v does not fit inside an unsigned 64-bit integer", offset)
+	}
+	// Ensures positive and not too wide
+	if !length.IsUint64() {
+		return nil, fmt.Errorf("length %v does not fit inside an unsigned 64-bit integer", offset)
+	}
+	return mem.read(offset.Uint64(), length.Uint64())
+}
+
+func (mem *dynamicMemory) read(offset, length uint64) ([]byte, error) {
 	capacity := offset + length
 	err := mem.ensureCapacity(capacity)
 	if err != nil {
@@ -68,8 +84,16 @@ func (mem *dynamicMemory) Read(offset, length int64) ([]byte, error) {
 	return value, nil
 }
 
-func (mem *dynamicMemory) Write(offset int64, value []byte) error {
-	capacity := offset + int64(len(value))
+func (mem *dynamicMemory) Write(offset *big.Int, value []byte) error {
+	// Ensures positive and not too wide
+	if !offset.IsUint64() {
+		return fmt.Errorf("offset %v does not fit inside an unsigned 64-bit integer", offset)
+	}
+	return mem.write(offset.Uint64(), value)
+}
+
+func (mem *dynamicMemory) write(offset uint64, value []byte) error {
+	capacity := offset + uint64(len(value))
 	err := mem.ensureCapacity(capacity)
 	if err != nil {
 		return err
@@ -78,18 +102,21 @@ func (mem *dynamicMemory) Write(offset int64, value []byte) error {
 	return nil
 }
 
-func (mem *dynamicMemory) Capacity() int64 {
-	return int64(len(mem.slice))
+func (mem *dynamicMemory) Capacity() *big.Int {
+	return big.NewInt(int64(len(mem.slice)))
 }
 
 // Ensures the current memory store can hold newCapacity. Will only grow the
 // memory (will not shrink).
-func (mem *dynamicMemory) ensureCapacity(newCapacity int64) error {
+func (mem *dynamicMemory) ensureCapacity(newCapacity uint64) error {
+	// Maximum length of a slice that allocates memory is the same as the native int max size
+	// We could rethink this limit, but we don't want different validators to disagree on
+	// transaction validity so we pick the lowest common denominator
 	if newCapacity > math.MaxInt32 {
 		// If we ever did want more than an int32 of space then we would need to
 		// maintain multiple pages of memory
 		return fmt.Errorf("cannot address memory beyond a maximum index "+
-			"of Int32 type (%v bytes)", math.MaxInt32)
+			"with int32 width (%v bytes)", math.MaxInt32)
 	}
 	newCapacityInt := int(newCapacity)
 	// We're already big enough so return
diff --git a/execution/evm/memory_test.go b/execution/evm/memory_test.go
index 923cdda4..c60a8583 100644
--- a/execution/evm/memory_test.go
+++ b/execution/evm/memory_test.go
@@ -3,14 +3,16 @@ package evm
 import (
 	"testing"
 
+	"math/big"
+
 	"github.com/stretchr/testify/assert"
 )
 
 // Test static memory allocation with maximum == initial capacity - memory should not grow
 func TestDynamicMemory_StaticAllocation(t *testing.T) {
 	mem := NewDynamicMemory(4, 4).(*dynamicMemory)
-	mem.Write(0, []byte{1})
-	mem.Write(1, []byte{0, 0, 1})
+	mem.Write(big.NewInt(0), []byte{1})
+	mem.Write(big.NewInt(1), []byte{0, 0, 1})
 	assert.Equal(t, []byte{1, 0, 0, 1}, mem.slice)
 	assert.Equal(t, 4, cap(mem.slice), "Slice capacity should not grow")
 }
@@ -18,31 +20,31 @@ func TestDynamicMemory_StaticAllocation(t *testing.T) {
 // Test reading beyond the current capacity - memory should grow
 func TestDynamicMemory_ReadAhead(t *testing.T) {
 	mem := NewDynamicMemory(4, 8).(*dynamicMemory)
-	value, err := mem.Read(2, 4)
+	value, err := mem.Read(big.NewInt(2), big.NewInt(4))
 	assert.NoError(t, err)
 	// Value should be size requested
 	assert.Equal(t, []byte{0, 0, 0, 0}, value)
 	// Slice should have grown to that plus offset
 	assert.Equal(t, []byte{0, 0, 0, 0, 0, 0}, mem.slice)
 
-	value, err = mem.Read(2, 6)
+	value, err = mem.Read(big.NewInt(2), big.NewInt(6))
 	assert.NoError(t, err)
 	assert.Equal(t, []byte{0, 0, 0, 0, 0, 0}, value)
 	assert.Equal(t, []byte{0, 0, 0, 0, 0, 0, 0, 0}, mem.slice)
 
 	// Check cannot read out of bounds
-	_, err = mem.Read(2, 7)
+	_, err = mem.Read(big.NewInt(2), big.NewInt(7))
 	assert.Error(t, err)
 }
 
 // Test writing beyond the current capacity - memory should grow
 func TestDynamicMemory_WriteAhead(t *testing.T) {
 	mem := NewDynamicMemory(4, 8).(*dynamicMemory)
-	err := mem.Write(4, []byte{1, 2, 3, 4})
+	err := mem.Write(big.NewInt(4), []byte{1, 2, 3, 4})
 	assert.NoError(t, err)
 	assert.Equal(t, []byte{0, 0, 0, 0, 1, 2, 3, 4}, mem.slice)
 
-	err = mem.Write(4, []byte{1, 2, 3, 4, 5})
+	err = mem.Write(big.NewInt(4), []byte{1, 2, 3, 4, 5})
 	assert.Error(t, err)
 }
 
@@ -56,21 +58,21 @@ to describe: his sensation of a few hours before on Grattan Bridge, for
 example. If he could get back again into that mood....`)
 
 	// Write the bytes
-	offset := 0x1000000
-	err := mem.Write(int64(offset), bytesToWrite)
+	offset := big.NewInt(0x1000000)
+	err := mem.Write(offset, bytesToWrite)
 	assert.NoError(t, err)
-	assert.Equal(t, append(make([]byte, offset), bytesToWrite...), mem.slice)
-	assert.Equal(t, offset+len(bytesToWrite), len(mem.slice))
+	assert.Equal(t, append(make([]byte, offset.Uint64()), bytesToWrite...), mem.slice)
+	assert.Equal(t, offset.Uint64()+uint64(len(bytesToWrite)), uint64(len(mem.slice)))
 
 	// Read them back
-	value, err := mem.Read(int64(offset), int64(len(bytesToWrite)))
+	value, err := mem.Read(offset, big.NewInt(int64(len(bytesToWrite))))
 	assert.NoError(t, err)
 	assert.Equal(t, bytesToWrite, value)
 }
 
 func TestDynamicMemory_ZeroInitialMemory(t *testing.T) {
 	mem := NewDynamicMemory(0, 16).(*dynamicMemory)
-	err := mem.Write(4, []byte{1, 2, 3, 4})
+	err := mem.Write(big.NewInt(4), []byte{1, 2, 3, 4})
 	assert.NoError(t, err)
 	assert.Equal(t, []byte{0, 0, 0, 0, 1, 2, 3, 4}, mem.slice)
 }
@@ -78,15 +80,15 @@ func TestDynamicMemory_ZeroInitialMemory(t *testing.T) {
 func TestDynamicMemory_Capacity(t *testing.T) {
 	mem := NewDynamicMemory(1, 0x10000000).(*dynamicMemory)
 
-	assert.Equal(t, int64(1), mem.Capacity())
+	assert.Equal(t, big.NewInt(1), mem.Capacity())
 
-	capacity := int64(1234)
-	err := mem.ensureCapacity(capacity)
+	capacity := big.NewInt(1234)
+	err := mem.ensureCapacity(capacity.Uint64())
 	assert.NoError(t, err)
 	assert.Equal(t, capacity, mem.Capacity())
 
-	capacity = int64(123456789)
-	err = mem.ensureCapacity(capacity)
+	capacity = big.NewInt(123456789)
+	err = mem.ensureCapacity(capacity.Uint64())
 	assert.NoError(t, err)
 	assert.Equal(t, capacity, mem.Capacity())
 
diff --git a/execution/evm/vm.go b/execution/evm/vm.go
index 3513e5ac..fe1f51cb 100644
--- a/execution/evm/vm.go
+++ b/execution/evm/vm.go
@@ -425,7 +425,7 @@ func (vm *VM) call(caller, callee acm.MutableAccount, code, input []byte, value
 			if useGasNegative(gas, GasSha3, &err) {
 				return nil, err
 			}
-			offset, size := stack.Pop64(), stack.Pop64()
+			offset, size := stack.PopBigInt(), stack.PopBigInt()
 			data, memErr := memory.Read(offset, size)
 			if memErr != nil {
 				vm.Debugf(" => Memory err: %s", memErr)
@@ -482,7 +482,7 @@ func (vm *VM) call(caller, callee acm.MutableAccount, code, input []byte, value
 			vm.Debugf(" => %d\n", len(input))
 
 		case CALLDATACOPY: // 0x37
-			memOff := stack.Pop64()
+			memOff := stack.PopBigInt()
 			inputOff := stack.Pop64()
 			length := stack.Pop64()
 			data, ok := subslice(input, inputOff, length)
@@ -502,7 +502,7 @@ func (vm *VM) call(caller, callee acm.MutableAccount, code, input []byte, value
 			vm.Debugf(" => %d\n", l)
 
 		case CODECOPY: // 0x39
-			memOff := stack.Pop64()
+			memOff := stack.PopBigInt()
 			codeOff := stack.Pop64()
 			length := stack.Pop64()
 			data, ok := subslice(code, codeOff, length)
@@ -558,7 +558,7 @@ func (vm *VM) call(caller, callee acm.MutableAccount, code, input []byte, value
 				return nil, firstErr(err, ErrUnknownAddress)
 			}
 			code := acc.Code()
-			memOff := stack.Pop64()
+			memOff := stack.PopBigInt()
 			codeOff := stack.Pop64()
 			length := stack.Pop64()
 			data, ok := subslice(code, codeOff, length)
@@ -599,8 +599,8 @@ func (vm *VM) call(caller, callee acm.MutableAccount, code, input []byte, value
 			vm.Debugf(" => 0x%X\n", popped)
 
 		case MLOAD: // 0x51
-			offset := stack.Pop64()
-			data, memErr := memory.Read(offset, 32)
+			offset := stack.PopBigInt()
+			data, memErr := memory.Read(offset, BigWord256Length)
 			if memErr != nil {
 				vm.Debugf(" => Memory err: %s", memErr)
 				return nil, firstErr(err, ErrMemoryOutOfBounds)
@@ -609,7 +609,7 @@ func (vm *VM) call(caller, callee acm.MutableAccount, code, input []byte, value
 			vm.Debugf(" => 0x%X @ 0x%X\n", data, offset)
 
 		case MSTORE: // 0x52
-			offset, data := stack.Pop64(), stack.Pop()
+			offset, data := stack.PopBigInt(), stack.Pop()
 			memErr := memory.Write(offset, data.Bytes())
 			if memErr != nil {
 				vm.Debugf(" => Memory err: %s", memErr)
@@ -618,7 +618,7 @@ func (vm *VM) call(caller, callee acm.MutableAccount, code, input []byte, value
 			vm.Debugf(" => 0x%X @ 0x%X\n", data, offset)
 
 		case MSTORE8: // 0x53
-			offset, val := stack.Pop64(), byte(stack.Pop64()&0xFF)
+			offset, val := stack.PopBigInt(), byte(stack.Pop64()&0xFF)
 			memErr := memory.Write(offset, []byte{val})
 			if memErr != nil {
 				vm.Debugf(" => Memory err: %s", memErr)
@@ -670,7 +670,7 @@ func (vm *VM) call(caller, callee acm.MutableAccount, code, input []byte, value
 			// free memory to be allocated for it if a subsequent MSTORE is made to
 			// this offset.
 			capacity := memory.Capacity()
-			stack.Push64(capacity)
+			stack.PushBigInt(capacity)
 			vm.Debugf(" => 0x%X\n", capacity)
 
 		case GAS: // 0x5A
@@ -707,7 +707,7 @@ func (vm *VM) call(caller, callee acm.MutableAccount, code, input []byte, value
 		case LOG0, LOG1, LOG2, LOG3, LOG4:
 			n := int(op - LOG0)
 			topics := make([]Word256, n)
-			offset, size := stack.Pop64(), stack.Pop64()
+			offset, size := stack.PopBigInt(), stack.PopBigInt()
 			for i := 0; i < n; i++ {
 				topics[i] = stack.Pop()
 			}
@@ -733,7 +733,7 @@ func (vm *VM) call(caller, callee acm.MutableAccount, code, input []byte, value
 				return nil, ErrPermission{"create_contract"}
 			}
 			contractValue := stack.PopU64()
-			offset, size := stack.Pop64(), stack.Pop64()
+			offset, size := stack.PopBigInt(), stack.PopBigInt()
 			input, memErr := memory.Read(offset, size)
 			if memErr != nil {
 				vm.Debugf(" => Memory err: %s", memErr)
@@ -773,8 +773,8 @@ func (vm *VM) call(caller, callee acm.MutableAccount, code, input []byte, value
 			if op != DELEGATECALL {
 				value = stack.PopU64()
 			}
-			inOffset, inSize := stack.Pop64(), stack.Pop64()   // inputs
-			retOffset, retSize := stack.Pop64(), stack.Pop64() // outputs
+			inOffset, inSize := stack.PopBigInt(), stack.PopBigInt() // inputs
+			retOffset, retSize := stack.PopBigInt(), stack.Pop64()   // outputs
 			vm.Debugf(" => %X\n", addr)
 
 			// Get the arguments from the memory
@@ -868,7 +868,7 @@ func (vm *VM) call(caller, callee acm.MutableAccount, code, input []byte, value
 			vm.Debugf("resume %s (%v)\n", callee.Address(), gas)
 
 		case RETURN: // 0xF3
-			offset, size := stack.Pop64(), stack.Pop64()
+			offset, size := stack.PopBigInt(), stack.PopBigInt()
 			output, memErr := memory.Read(offset, size)
 			if memErr != nil {
 				vm.Debugf(" => Memory err: %s", memErr)
-- 
GitLab