diff --git a/logging/loggers/burrow_format_logger.go b/logging/loggers/burrow_format_logger.go index ec522b3d8..2af1b4032 100644 --- a/logging/loggers/burrow_format_logger.go +++ b/logging/loggers/burrow_format_logger.go @@ -20,6 +20,7 @@ import ( "github.com/hyperledger/burrow/logging/structure" kitlog "github.com/go-kit/kit/log" + "github.com/hyperledger/burrow/word256" ) // Logger that implements some formatting conventions for burrow and burrow-client @@ -50,7 +51,10 @@ func burrowFormatKeyValueMapper(key, value interface{}) (interface{}, interface{ switch v := value.(type) { case []byte: return key, fmt.Sprintf("%X", v) + case word256.Word256: + return burrowFormatKeyValueMapper(key, v.Bytes()) } + } return key, value } diff --git a/manager/burrow-mint/evm/log_event_test.go b/manager/burrow-mint/evm/log_event_test.go index 91431adc7..7dd569a63 100644 --- a/manager/burrow-mint/evm/log_event_test.go +++ b/manager/burrow-mint/evm/log_event_test.go @@ -47,7 +47,7 @@ func TestLog4(t *testing.T) { st.accounts[account1.Address.String()] = account1 st.accounts[account2.Address.String()] = account2 - ourVm := NewVM(st, newParams(), Zero256, nil) + ourVm := NewVM(st, DefaultDynamicMemoryProvider, newParams(), Zero256, nil) eventSwitch := events.NewEventSwitch() _, err := eventSwitch.Start() diff --git a/manager/burrow-mint/evm/memory.go b/manager/burrow-mint/evm/memory.go new file mode 100644 index 000000000..fc24dbd7d --- /dev/null +++ b/manager/burrow-mint/evm/memory.go @@ -0,0 +1,115 @@ +package vm + +import ( + "fmt" + "math" +) + +const ( + defaultInitialMemoryCapacity = 0x100000 // 1 MiB + defaultMaximumMemoryCapacity = 0x1000000 // 16 MiB +) + +// Change the length of this zero array to tweak the size of the block of zeros +// written to the backing slice at a time when it is grown. A larger number may +// lead to less calls to append to achieve the desired capacity although it is +// 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 +// for each byte in the memory. +type Memory interface { + // Read a value from the memory store starting at offset + // (index of first byte will equal offset). The value will be returned as a + // length-bytes byte slice. Returns an error if the memory cannot be read or + // is not allocated. + // + // The value returned should be copy of any underlying memory, not a reference + // to the underlying store. + Read(offset, length int64) ([]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 + // 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 +} + +func NewDynamicMemory(initialCapacity, maximumCapacity int64) Memory { + return &dynamicMemory{ + slice: make([]byte, initialCapacity), + maximumCapacity: maximumCapacity, + } +} + +func DefaultDynamicMemoryProvider() Memory { + return NewDynamicMemory(defaultInitialMemoryCapacity, defaultMaximumMemoryCapacity) +} + +// Implements a bounded dynamic memory that relies on Go's (pretty good) dynamic +// array allocation via a backing slice +type dynamicMemory struct { + slice []byte + maximumCapacity int64 +} + +func (mem *dynamicMemory) Read(offset, length int64) ([]byte, error) { + capacity := offset + length + err := mem.ensureCapacity(capacity) + if err != nil { + return nil, err + } + value := make([]byte, length) + copy(value, mem.slice[offset:capacity]) + return value, nil +} + +func (mem *dynamicMemory) Write(offset int64, value []byte) error { + capacity := offset + int64(len(value)) + err := mem.ensureCapacity(capacity) + if err != nil { + return err + } + copy(mem.slice[offset:capacity], value) + return nil +} + +func (mem *dynamicMemory) Capacity() int64 { + return 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 { + if newCapacity > math.MaxInt32 { + // If we ever did want to 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) + } + newCapacityInt := int(newCapacity) + // We're already big enough so return + if newCapacityInt <= len(mem.slice) { + return nil + } + if newCapacity > mem.maximumCapacity { + return fmt.Errorf("Cannot grow memory because it would exceed the "+ + "current maximum limit of %v bytes", mem.maximumCapacity) + } + // Ensure the backing array of slice is big enough + // Grow the memory one word at time using the pre-allocated zeroBlock to avoid + // unnecessary allocations. Use append to make use of any spare capacity in + // the slice's backing array. + for newCapacityInt > cap(mem.slice) { + // We'll trust Go exponentially grow our arrays (at first). + mem.slice = append(mem.slice, zeroBlock...) + } + // Now we've ensured the backing array of the slice is big enough we can + // just re-slice (even if len(mem.slice) < newCapacity) + mem.slice = mem.slice[:newCapacity] + return nil +} diff --git a/manager/burrow-mint/evm/memory_test.go b/manager/burrow-mint/evm/memory_test.go new file mode 100644 index 000000000..2477b32d8 --- /dev/null +++ b/manager/burrow-mint/evm/memory_test.go @@ -0,0 +1,120 @@ +package vm + +import ( + "testing" + + "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}) + assert.Equal(t, []byte{1, 0, 0, 1}, mem.slice) + assert.Equal(t, 4, cap(mem.slice), "Slice capacity should not grow") +} + +// 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) + 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) + 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) + 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}) + 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}) + assert.Error(t, err) +} + +func TestDynamicMemory_WriteRead(t *testing.T) { + mem := NewDynamicMemory(1, 0x10000000).(*dynamicMemory) + // Text is out of copyright + bytesToWrite := []byte(`He paused. He felt the rhythm of the verse about him in the room. +How melancholy it was! Could he, too, write like that, express the +melancholy of his soul in verse? There were so many things he wanted +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) + assert.NoError(t, err) + assert.Equal(t, append(make([]byte, offset), bytesToWrite...), mem.slice) + assert.Equal(t, offset+len(bytesToWrite), len(mem.slice)) + + // Read them back + value, err := mem.Read(int64(offset), 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}) + assert.NoError(t, err) + assert.Equal(t, []byte{0, 0, 0, 0, 1, 2, 3, 4}, mem.slice) +} + +func TestDynamicMemory_Capacity(t *testing.T) { + mem := NewDynamicMemory(1, 0x10000000).(*dynamicMemory) + + assert.Equal(t, int64(1), mem.Capacity()) + + capacity := int64(1234) + err := mem.ensureCapacity(capacity) + assert.NoError(t, err) + assert.Equal(t, capacity, mem.Capacity()) + + capacity = int64(123456789) + err = mem.ensureCapacity(capacity) + assert.NoError(t, err) + assert.Equal(t, capacity, mem.Capacity()) + + // Check doesn't shrink or err + err = mem.ensureCapacity(12) + assert.NoError(t, err) + assert.Equal(t, capacity, mem.Capacity()) +} + +func TestDynamicMemory_ensureCapacity(t *testing.T) { + mem := NewDynamicMemory(4, 16).(*dynamicMemory) + // Check we can grow within bounds + err := mem.ensureCapacity(8) + assert.NoError(t, err) + expected := make([]byte, 8) + assert.Equal(t, expected, mem.slice) + + // Check we can grow to bounds + err = mem.ensureCapacity(16) + assert.NoError(t, err) + expected = make([]byte, 16) + assert.Equal(t, expected, mem.slice) + + err = mem.ensureCapacity(1) + assert.NoError(t, err) + assert.Equal(t, 16, len(mem.slice)) + + err = mem.ensureCapacity(17) + assert.Error(t, err, "Should not be possible to grow over capacity") + +} diff --git a/manager/burrow-mint/evm/opcodes/opcodes.go b/manager/burrow-mint/evm/opcodes/opcodes.go index 0debc94ec..a8139d82f 100644 --- a/manager/burrow-mint/evm/opcodes/opcodes.go +++ b/manager/burrow-mint/evm/opcodes/opcodes.go @@ -244,9 +244,7 @@ var opCodeToString = map[OpCode]string{ EXTCODECOPY: "EXTCODECOPY", // 0x50 range - 'storage' and execution - POP: "POP", - //DUP: "DUP", - //SWAP: "SWAP", + POP: "POP", MLOAD: "MLOAD", MSTORE: "MSTORE", MSTORE8: "MSTORE8", diff --git a/manager/burrow-mint/evm/snative.go b/manager/burrow-mint/evm/snative.go index ab7e68a43..d256ddf23 100644 --- a/manager/burrow-mint/evm/snative.go +++ b/manager/burrow-mint/evm/snative.go @@ -87,10 +87,10 @@ func SNativeContracts() map[string]*SNativeContractDescription { `, "addRole", []abi.Arg{ - arg("_account", abi.AddressTypeName), - arg("_role", roleTypeName), + abiArg("_account", abi.AddressTypeName), + abiArg("_role", roleTypeName), }, - ret("result", abi.BoolTypeName), + abiReturn("result", abi.BoolTypeName), ptypes.AddRole, addRole}, @@ -102,10 +102,10 @@ func SNativeContracts() map[string]*SNativeContractDescription { `, "removeRole", []abi.Arg{ - arg("_account", abi.AddressTypeName), - arg("_role", roleTypeName), + abiArg("_account", abi.AddressTypeName), + abiArg("_role", roleTypeName), }, - ret("result", abi.BoolTypeName), + abiReturn("result", abi.BoolTypeName), ptypes.RmRole, removeRole}, @@ -117,10 +117,10 @@ func SNativeContracts() map[string]*SNativeContractDescription { `, "hasRole", []abi.Arg{ - arg("_account", abi.AddressTypeName), - arg("_role", roleTypeName), + abiArg("_account", abi.AddressTypeName), + abiArg("_role", roleTypeName), }, - ret("result", abi.BoolTypeName), + abiReturn("result", abi.BoolTypeName), ptypes.HasRole, hasRole}, @@ -133,11 +133,11 @@ func SNativeContracts() map[string]*SNativeContractDescription { `, "setBase", []abi.Arg{ - arg("_account", abi.AddressTypeName), - arg("_permission", permFlagTypeName), - arg("_set", abi.BoolTypeName), + abiArg("_account", abi.AddressTypeName), + abiArg("_permission", permFlagTypeName), + abiArg("_set", abi.BoolTypeName), }, - ret("result", permFlagTypeName), + abiReturn("result", permFlagTypeName), ptypes.SetBase, setBase}, @@ -149,9 +149,9 @@ func SNativeContracts() map[string]*SNativeContractDescription { `, "unsetBase", []abi.Arg{ - arg("_account", abi.AddressTypeName), - arg("_permission", permFlagTypeName)}, - ret("result", permFlagTypeName), + abiArg("_account", abi.AddressTypeName), + abiArg("_permission", permFlagTypeName)}, + abiReturn("result", permFlagTypeName), ptypes.UnsetBase, unsetBase}, @@ -163,9 +163,9 @@ func SNativeContracts() map[string]*SNativeContractDescription { `, "hasBase", []abi.Arg{ - arg("_account", abi.AddressTypeName), - arg("_permission", permFlagTypeName)}, - ret("result", abi.BoolTypeName), + abiArg("_account", abi.AddressTypeName), + abiArg("_permission", permFlagTypeName)}, + abiReturn("result", abi.BoolTypeName), ptypes.HasBase, hasBase}, @@ -177,9 +177,9 @@ func SNativeContracts() map[string]*SNativeContractDescription { `, "setGlobal", []abi.Arg{ - arg("_permission", permFlagTypeName), - arg("_set", abi.BoolTypeName)}, - ret("result", permFlagTypeName), + abiArg("_permission", permFlagTypeName), + abiArg("_set", abi.BoolTypeName)}, + abiReturn("result", permFlagTypeName), ptypes.SetGlobal, setGlobal}, ), @@ -324,14 +324,14 @@ func (function *SNativeFunctionDescription) NArgs() int { return len(function.Args) } -func arg(name string, abiTypeName abi.TypeName) abi.Arg { +func abiArg(name string, abiTypeName abi.TypeName) abi.Arg { return abi.Arg{ Name: name, TypeName: abiTypeName, } } -func ret(name string, abiTypeName abi.TypeName) abi.Return { +func abiReturn(name string, abiTypeName abi.TypeName) abi.Return { return abi.Return{ Name: name, TypeName: abiTypeName, diff --git a/manager/burrow-mint/evm/vm.go b/manager/burrow-mint/evm/vm.go index 26d4d196c..60ae0f64e 100644 --- a/manager/burrow-mint/evm/vm.go +++ b/manager/burrow-mint/evm/vm.go @@ -20,7 +20,6 @@ import ( "fmt" "math/big" - "github.com/hyperledger/burrow/common/math/integral" "github.com/hyperledger/burrow/common/sanity" . "github.com/hyperledger/burrow/manager/burrow-mint/evm/opcodes" "github.com/hyperledger/burrow/manager/burrow-mint/evm/sha3" @@ -57,8 +56,7 @@ func (err ErrPermission) Error() string { const ( dataStackCapacity = 1024 - callStackCapacity = 100 // TODO ensure usage. - memoryCapacity = 1024 * 1024 // 1 MB + callStackCapacity = 100 // TODO ensure usage. ) type Debug bool @@ -76,23 +74,26 @@ func (d Debug) Printf(s string, a ...interface{}) { } type VM struct { - appState AppState - params Params - origin Word256 - txid []byte + appState AppState + memoryProvider func() Memory + params Params + origin Word256 + txid []byte callDepth int evc events.Fireable } -func NewVM(appState AppState, params Params, origin Word256, txid []byte) *VM { +func NewVM(appState AppState, memoryProvider func() Memory, params Params, + origin Word256, txid []byte) *VM { return &VM{ - appState: appState, - params: params, - origin: origin, - callDepth: 0, - txid: txid, + appState: appState, + memoryProvider: memoryProvider, + params: params, + origin: origin, + callDepth: 0, + txid: txid, } } @@ -211,7 +212,7 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value int64, gas var ( pc int64 = 0 stack = NewStack(dataStackCapacity, gas, &err) - memory = make([]byte, memoryCapacity) + memory = vm.memoryProvider() ) for { @@ -488,8 +489,9 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value int64, gas return nil, err } offset, size := stack.Pop64(), stack.Pop64() - data, ok := subslice(memory, offset, size) - if !ok { + data, memErr := memory.Read(offset, size) + if memErr != nil { + dbg.Printf(" => Memory err: %s", memErr) return nil, firstErr(err, ErrMemoryOutOfBounds) } data = sha3.Sha3(data) @@ -547,11 +549,11 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value int64, gas if !ok { return nil, firstErr(err, ErrInputOutOfBounds) } - dest, ok := subslice(memory, memOff, length) - if !ok { + memErr := memory.Write(memOff, data) + if memErr != nil { + dbg.Printf(" => Memory err: %s", memErr) return nil, firstErr(err, ErrMemoryOutOfBounds) } - copy(dest, data) dbg.Printf(" => [%v, %v, %v] %X\n", memOff, inputOff, length, data) case CODESIZE: // 0x38 @@ -567,11 +569,11 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value int64, gas if !ok { return nil, firstErr(err, ErrCodeOutOfBounds) } - dest, ok := subslice(memory, memOff, length) - if !ok { + memErr := memory.Write(memOff, data) + if memErr != nil { + dbg.Printf(" => Memory err: %s", memErr) return nil, firstErr(err, ErrMemoryOutOfBounds) } - copy(dest, data) dbg.Printf(" => [%v, %v, %v] %X\n", memOff, codeOff, length, data) case GASPRICE_DEPRECATED: // 0x3A @@ -617,11 +619,11 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value int64, gas if !ok { return nil, firstErr(err, ErrCodeOutOfBounds) } - dest, ok := subslice(memory, memOff, length) - if !ok { + memErr := memory.Write(memOff, data) + if memErr != nil { + dbg.Printf(" => Memory err: %s", memErr) return nil, firstErr(err, ErrMemoryOutOfBounds) } - copy(dest, data) dbg.Printf(" => [%v, %v, %v] %X\n", memOff, codeOff, length, data) case BLOCKHASH: // 0x40 @@ -652,28 +654,30 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value int64, gas case MLOAD: // 0x51 offset := stack.Pop64() - data, ok := subslice(memory, offset, 32) - if !ok { + data, memErr := memory.Read(offset, 32) + if memErr != nil { + dbg.Printf(" => Memory err: %s", memErr) return nil, firstErr(err, ErrMemoryOutOfBounds) } stack.Push(LeftPadWord256(data)) - dbg.Printf(" => 0x%X\n", data) + dbg.Printf(" => 0x%X @ 0x%X\n", data, offset) case MSTORE: // 0x52 offset, data := stack.Pop64(), stack.Pop() - dest, ok := subslice(memory, offset, 32) - if !ok { + memErr := memory.Write(offset, data.Bytes()) + if memErr != nil { + dbg.Printf(" => Memory err: %s", memErr) return nil, firstErr(err, ErrMemoryOutOfBounds) } - copy(dest, data[:]) - dbg.Printf(" => 0x%X\n", data) + dbg.Printf(" => 0x%X @ 0x%X\n", data, offset) case MSTORE8: // 0x53 offset, val := stack.Pop64(), byte(stack.Pop64()&0xFF) - if len(memory) <= int(offset) { + memErr := memory.Write(offset, []byte{val}) + if memErr != nil { + dbg.Printf(" => Memory err: %s", memErr) return nil, firstErr(err, ErrMemoryOutOfBounds) } - memory[offset] = val dbg.Printf(" => [%v] 0x%X\n", offset, val) case SLOAD: // 0x54 @@ -710,7 +714,12 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value int64, gas stack.Push64(pc) case MSIZE: // 0x59 - stack.Push64(int64(len(memory))) + // Note: Solidity will write to this offset expecting to find guaranteed + // free memory to be allocated for it if a subsequent MSTORE is made to + // this offset. + capacity := memory.Capacity() + stack.Push64(capacity) + dbg.Printf(" => 0x%X\n", capacity) case GAS: // 0x5A stack.Push64(*gas) @@ -750,11 +759,11 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value int64, gas for i := 0; i < n; i++ { topics[i] = stack.Pop() } - data, ok := subslice(memory, offset, size) - if !ok { + data, memErr := memory.Read(offset, size) + if memErr != nil { + dbg.Printf(" => Memory err: %s", memErr) return nil, firstErr(err, ErrMemoryOutOfBounds) } - data = copyslice(data) if vm.evc != nil { eventID := txs.EventStringLogEvent(callee.Address.Postfix(20)) fmt.Printf("eventID: %s\n", eventID) @@ -774,8 +783,9 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value int64, gas } contractValue := stack.Pop64() offset, size := stack.Pop64(), stack.Pop64() - input, ok := subslice(memory, offset, size) - if !ok { + input, memErr := memory.Read(offset, size) + if memErr != nil { + dbg.Printf(" => Memory err: %s", memErr) return nil, firstErr(err, ErrMemoryOutOfBounds) } @@ -785,7 +795,6 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value int64, gas } // TODO charge for gas to create account _ the code length * GasCreateByte - newAccount := vm.appState.CreateAccount(callee) // Run the input to get the contract code. @@ -817,11 +826,11 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value int64, gas dbg.Printf(" => %X\n", addr) // Get the arguments from the memory - args, ok := subslice(memory, inOffset, inSize) - if !ok { + args, memErr := memory.Read(inOffset, inSize) + if memErr != nil { + dbg.Printf(" => Memory err: %s", memErr) return nil, firstErr(err, ErrMemoryOutOfBounds) } - args = copyslice(args) // Ensure that gasLimit is reasonable if *gas < gasLimit { @@ -885,11 +894,15 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value int64, gas stack.Push(Zero256) } else { stack.Push(One256) - dest, ok := subslice(memory, retOffset, retSize) - if !ok { + + // Should probably only be necessary when there is no return value and + // ret is empty, but since EVM expects retSize to be respected this will + // defensively pad or truncate the portion of ret to be returned. + memErr := memory.Write(retOffset, RightPadBytes(ret, int(retSize))) + if memErr != nil { + dbg.Printf(" => Memory err: %s", memErr) return nil, firstErr(err, ErrMemoryOutOfBounds) } - copy(dest, ret) } // Handle remaining gas. @@ -899,12 +912,12 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value int64, gas case RETURN: // 0xF3 offset, size := stack.Pop64(), stack.Pop64() - ret, ok := subslice(memory, offset, size) - if !ok { + output, memErr := memory.Read(offset, size) + if memErr != nil { + dbg.Printf(" => Memory err: %s", memErr) return nil, firstErr(err, ErrMemoryOutOfBounds) } - dbg.Printf(" => [%v, %v] (%d) 0x%X\n", offset, size, len(ret), ret) - output = copyslice(ret) + dbg.Printf(" => [%v, %v] (%d) 0x%X\n", offset, size, len(output), output) return output, nil case SELFDESTRUCT: // 0xFF @@ -937,6 +950,16 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value int64, gas } } +// TODO: [Silas] this function seems extremely dubious to me. It was being used +// in circumstances where its behaviour did not match the intention. It's bounds +// check is strange (treats a read at data length as a zero read of arbitrary length) +// I have left it in for now to be conservative about where its behaviour is being used +// +// Returns a subslice from offset of length length and a bool +// (true iff slice was possible). If the subslice +// extends past the end of data it returns A COPY of the segment at the end of +// data padded with zeroes on the right. If offset == len(data) it returns all +// zeroes. if offset > len(data) it returns a false func subslice(data []byte, offset, length int64) (ret []byte, ok bool) { size := int64(len(data)) if size < offset { @@ -950,18 +973,6 @@ func subslice(data []byte, offset, length int64) (ret []byte, ok bool) { return } -func copyslice(src []byte) (dest []byte) { - dest = make([]byte, len(src)) - copy(dest, src) - return dest -} - -func rightMostBytes(data []byte, n int) []byte { - size := integral.MinInt(len(data), n) - offset := len(data) - size - return data[offset:] -} - func codeGetOp(code []byte, n int64) OpCode { if int64(len(code)) <= n { return OpCode(0) // stop diff --git a/manager/burrow-mint/evm/vm_test.go b/manager/burrow-mint/evm/vm_test.go index dc316ec68..a7e36a708 100644 --- a/manager/burrow-mint/evm/vm_test.go +++ b/manager/burrow-mint/evm/vm_test.go @@ -65,7 +65,7 @@ func makeBytes(n int) []byte { // Runs a basic loop func TestVM(t *testing.T) { - ourVm := NewVM(newAppState(), newParams(), Zero256, nil) + ourVm := NewVM(newAppState(), DefaultDynamicMemoryProvider, newParams(), Zero256, nil) // Create accounts account1 := &Account{ @@ -91,7 +91,7 @@ func TestVM(t *testing.T) { } func TestJumpErr(t *testing.T) { - ourVm := NewVM(newAppState(), newParams(), Zero256, nil) + ourVm := NewVM(newAppState(), DefaultDynamicMemoryProvider, newParams(), Zero256, nil) // Create accounts account1 := &Account{ @@ -134,7 +134,7 @@ func TestSubcurrency(t *testing.T) { st.accounts[account1.Address.String()] = account1 st.accounts[account2.Address.String()] = account2 - ourVm := NewVM(st, newParams(), Zero256, nil) + ourVm := NewVM(st, DefaultDynamicMemoryProvider, newParams(), Zero256, nil) var gas int64 = 1000 code_parts := []string{"620f42403355", @@ -156,7 +156,7 @@ func TestSubcurrency(t *testing.T) { // Test sending tokens from a contract to another account func TestSendCall(t *testing.T) { fakeAppState := newAppState() - ourVm := NewVM(fakeAppState, newParams(), Zero256, nil) + ourVm := NewVM(fakeAppState, DefaultDynamicMemoryProvider, newParams(), Zero256, nil) // Create accounts account1 := &Account{ @@ -199,7 +199,7 @@ func TestSendCall(t *testing.T) { // and then run it with 1 gas unit less, expecting a failure func TestDelegateCallGas(t *testing.T) { appState := newAppState() - ourVm := NewVM(appState, newParams(), Zero256, nil) + ourVm := NewVM(appState, DefaultDynamicMemoryProvider, newParams(), Zero256, nil) inOff := 0 inSize := 0 // no call data @@ -255,6 +255,66 @@ func TestDelegateCallGas(t *testing.T) { assert.Error(t, err, "Should have insufficient funds for call") } +func TestMemoryBounds(t *testing.T) { + appState := newAppState() + memoryProvider := func() Memory { + return NewDynamicMemory(1024, 2048) + } + ourVm := NewVM(appState, memoryProvider, newParams(), Zero256, nil) + caller, _ := makeAccountWithCode(appState, "caller", nil) + callee, _ := makeAccountWithCode(appState, "callee", nil) + gas := int64(100000) + // This attempts to store a value at the memory boundary and return it + word := One256 + output, err := ourVm.call(caller, callee, + Bytecode(pushWord(word), storeAtEnd(), MLOAD, storeAtEnd(), returnAfterStore()), + nil, 0, &gas) + assert.NoError(t, err) + assert.Equal(t, word.Bytes(), output) + + // Same with number + word = Int64ToWord256(232234234432) + output, err = ourVm.call(caller, callee, + Bytecode(pushWord(word), storeAtEnd(), MLOAD, storeAtEnd(), returnAfterStore()), + nil, 0, &gas) + assert.NoError(t, err) + assert.Equal(t, word.Bytes(), output) + + // Now test a series of boundary stores + code := pushWord(word) + for i := 0; i < 10; i++ { + code = Bytecode(code, storeAtEnd(), MLOAD) + } + output, err = ourVm.call(caller, callee, Bytecode(code, storeAtEnd(), returnAfterStore()), + nil, 0, &gas) + assert.NoError(t, err) + assert.Equal(t, word.Bytes(), output) + + // Same as above but we should breach the upper memory limit set in memoryProvider + code = pushWord(word) + for i := 0; i < 100; i++ { + code = Bytecode(code, storeAtEnd(), MLOAD) + } + output, err = ourVm.call(caller, callee, Bytecode(code, storeAtEnd(), returnAfterStore()), + nil, 0, &gas) + assert.Error(t, err, "Should hit memory out of bounds") +} + +// These code segment helpers exercise the MSTORE MLOAD MSTORE cycle to test +// both of the memory operations. Each MSTORE is done on the memory boundary +// (at MSIZE) which Solidity uses to find guaranteed unallocated memory. + +// storeAtEnd expects the value to be stored to be on top of the stack, it then +// stores that value at the current memory boundary +func storeAtEnd() []byte { + // Pull in MSIZE (to carry forward to MLOAD), swap in value to store, store it at MSIZE + return Bytecode(MSIZE, SWAP1, DUP2, MSTORE) +} + +func returnAfterStore() []byte { + return Bytecode(PUSH1, 32, DUP2, RETURN) +} + // Store the top element of the stack (which is a 32-byte word) in memory // and return it. Useful for a simple return value. func return1() []byte { @@ -346,6 +406,37 @@ func callContractCode(addr []byte) []byte { PUSH1, retOff, RETURN) } +func pushInt64(i int64) []byte { + return pushWord(Int64ToWord256(i)) +} + +// Produce bytecode for a PUSH, b_1, ..., b_N where the N is number of bytes +// contained in the unpadded word +func pushWord(word Word256) []byte { + leadingZeros := byte(0) + for leadingZeros < 32 { + if word[leadingZeros] == 0 { + leadingZeros++ + } else { + return Bytecode(byte(PUSH32)-leadingZeros, word[leadingZeros:]) + } + } + return Bytecode(PUSH1, 0) +} + +func TestPushWord(t *testing.T) { + word := Int64ToWord256(int64(2133213213)) + assert.Equal(t, Bytecode(PUSH4, 0x7F, 0x26, 0x40, 0x1D), pushWord(word)) + word[0] = 1 + assert.Equal(t, Bytecode(PUSH32, + 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0x7F, 0x26, 0x40, 0x1D), pushWord(word)) + assert.Equal(t, Bytecode(PUSH1, 0), pushWord(Word256{})) + assert.Equal(t, Bytecode(PUSH1, 1), pushWord(Int64ToWord256(1))) +} + func TestBytecode(t *testing.T) { assert.Equal(t, Bytecode(1, 2, 3, 4, 5, 6), diff --git a/manager/burrow-mint/pipe.go b/manager/burrow-mint/pipe.go index 709105828..b8315dd5e 100644 --- a/manager/burrow-mint/pipe.go +++ b/manager/burrow-mint/pipe.go @@ -434,7 +434,8 @@ func (pipe *burrowMintPipe) Call(fromAddress, toAddress, data []byte) (*rpc_tm_t GasLimit: gasLimit, } - vmach := vm.NewVM(txCache, params, caller.Address, nil) + vmach := vm.NewVM(txCache, vm.DefaultDynamicMemoryProvider, params, + caller.Address, nil) gas := gasLimit ret, err := vmach.Call(caller, callee, callee.Code, data, 0, &gas) if err != nil { @@ -461,7 +462,8 @@ func (pipe *burrowMintPipe) CallCode(fromAddress, code, data []byte) (*rpc_tm_ty GasLimit: gasLimit, } - vmach := vm.NewVM(txCache, params, caller.Address, nil) + vmach := vm.NewVM(txCache, vm.DefaultDynamicMemoryProvider, params, + caller.Address, nil) gas := gasLimit ret, err := vmach.Call(caller, callee, code, data, 0, &gas) if err != nil { diff --git a/manager/burrow-mint/state/execution.go b/manager/burrow-mint/state/execution.go index edda1cb7f..50745e1a3 100644 --- a/manager/burrow-mint/state/execution.go +++ b/manager/burrow-mint/state/execution.go @@ -515,7 +515,8 @@ func ExecTx(blockCache *BlockCache, tx txs.Tx, runCall bool, evc events.Fireable // Write caller/callee to txCache. txCache.UpdateAccount(caller) txCache.UpdateAccount(callee) - vmach := vm.NewVM(txCache, params, caller.Address, txs.TxHash(_s.ChainID, tx)) + vmach := vm.NewVM(txCache, vm.DefaultDynamicMemoryProvider, params, + caller.Address, txs.TxHash(_s.ChainID, tx)) vmach.SetFireable(evc) // NOTE: Call() transfers the value from caller to callee iff call succeeds. ret, err = vmach.Call(caller, callee, code, tx.Data, value, &gas) diff --git a/manager/burrow-mint/transactor.go b/manager/burrow-mint/transactor.go index f368f79cf..3f73737b1 100644 --- a/manager/burrow-mint/transactor.go +++ b/manager/burrow-mint/transactor.go @@ -85,7 +85,8 @@ func (this *transactor) Call(fromAddress, toAddress, data []byte) ( GasLimit: gasLimit, } - vmach := vm.NewVM(txCache, params, caller.Address, nil) + vmach := vm.NewVM(txCache, vm.DefaultDynamicMemoryProvider, params, + caller.Address, nil) vmach.SetFireable(this.eventSwitch) gas := gasLimit ret, err := vmach.Call(caller, callee, callee.Code, data, 0, &gas) @@ -118,7 +119,8 @@ func (this *transactor) CallCode(fromAddress, code, data []byte) ( GasLimit: gasLimit, } - vmach := vm.NewVM(txCache, params, caller.Address, nil) + vmach := vm.NewVM(txCache, vm.DefaultDynamicMemoryProvider, params, + caller.Address, nil) gas := gasLimit ret, err := vmach.Call(caller, callee, code, data, 0, &gas) if err != nil {