github.com/datachainlab/burrow@v0.25.0/execution/evm/memory.go (about) 1 package evm 2 3 import ( 4 "fmt" 5 "math" 6 "math/big" 7 8 "github.com/hyperledger/burrow/execution/errors" 9 ) 10 11 const ( 12 defaultInitialMemoryCapacity = 0x100000 // 1 MiB 13 defaultMaximumMemoryCapacity = 0x1000000 // 16 MiB 14 ) 15 16 // Change the length of this zero array to tweak the size of the block of zeros 17 // written to the backing slice at a time when it is grown. A larger number may 18 // lead to fewer calls to append to achieve the desired capacity although it is 19 // unlikely to make a lot of difference. 20 var zeroBlock []byte = make([]byte, 32) 21 22 // Interface for a bounded linear memory indexed by a single *big.Int parameter 23 // for each byte in the memory. 24 type Memory interface { 25 // Read a value from the memory store starting at offset 26 // (index of first byte will equal offset). The value will be returned as a 27 // length-bytes byte slice. Returns an error if the memory cannot be read or 28 // is not allocated. 29 // 30 // The value returned should be copy of any underlying memory, not a reference 31 // to the underlying store. 32 Read(offset, length *big.Int) []byte 33 // Write a value to the memory starting at offset (the index of the first byte 34 // written will equal offset). The value is provided as bytes to be written 35 // consecutively to the memory store. Return an error if the memory cannot be 36 // written or allocated. 37 Write(offset *big.Int, value []byte) 38 // Returns the current capacity of the memory. For dynamically allocating 39 // memory this capacity can be used as a write offset that is guaranteed to be 40 // unused. Solidity in particular makes this assumption when using MSIZE to 41 // get the current allocated memory. 42 Capacity() *big.Int 43 } 44 45 // Get a new DynamicMemory (note that although we take a maximumCapacity of uint64 we currently 46 // limit the maximum to int32 at runtime because we are using a single slice which we cannot guarantee 47 // to be indexable above int32 or all validators 48 func NewDynamicMemory(initialCapacity, maximumCapacity uint64, errSink errors.Sink) Memory { 49 return &dynamicMemory{ 50 slice: make([]byte, initialCapacity), 51 maximumCapacity: maximumCapacity, 52 errSink: errSink, 53 } 54 } 55 56 func DefaultDynamicMemoryProvider(errSink errors.Sink) Memory { 57 return NewDynamicMemory(defaultInitialMemoryCapacity, defaultMaximumMemoryCapacity, errSink) 58 } 59 60 // Implements a bounded dynamic memory that relies on Go's (pretty good) dynamic 61 // array allocation via a backing slice 62 type dynamicMemory struct { 63 slice []byte 64 maximumCapacity uint64 65 errSink errors.Sink 66 } 67 68 func (mem *dynamicMemory) Read(offset, length *big.Int) []byte { 69 // Ensures positive and not too wide 70 if !offset.IsUint64() { 71 mem.pushErr(fmt.Errorf("offset %v does not fit inside an unsigned 64-bit integer", offset)) 72 return nil 73 } 74 // Ensures positive and not too wide 75 if !length.IsUint64() { 76 mem.pushErr(fmt.Errorf("length %v does not fit inside an unsigned 64-bit integer", offset)) 77 return nil 78 } 79 output, err := mem.read(offset.Uint64(), length.Uint64()) 80 if err != nil { 81 mem.pushErr(err) 82 return nil 83 } 84 return output 85 } 86 87 func (mem *dynamicMemory) Write(offset *big.Int, value []byte) { 88 // Ensures positive and not too wide 89 if !offset.IsUint64() { 90 mem.pushErr(fmt.Errorf("offset %v does not fit inside an unsigned 64-bit integer", offset)) 91 return 92 } 93 err := mem.write(offset.Uint64(), value) 94 if err != nil { 95 mem.pushErr(err) 96 } 97 } 98 99 func (mem *dynamicMemory) Capacity() *big.Int { 100 return big.NewInt(int64(len(mem.slice))) 101 } 102 103 func (mem *dynamicMemory) read(offset, length uint64) ([]byte, error) { 104 capacity := offset + length 105 err := mem.ensureCapacity(capacity) 106 if err != nil { 107 return nil, err 108 } 109 value := make([]byte, length) 110 copy(value, mem.slice[offset:capacity]) 111 return value, nil 112 } 113 114 func (mem *dynamicMemory) write(offset uint64, value []byte) error { 115 capacity := offset + uint64(len(value)) 116 err := mem.ensureCapacity(capacity) 117 if err != nil { 118 return err 119 } 120 copy(mem.slice[offset:capacity], value) 121 return nil 122 } 123 124 func (mem *dynamicMemory) pushErr(err error) { 125 mem.errSink.PushError(err) 126 } 127 128 // Ensures the current memory store can hold newCapacity. Will only grow the 129 // memory (will not shrink). 130 func (mem *dynamicMemory) ensureCapacity(newCapacity uint64) error { 131 // Maximum length of a slice that allocates memory is the same as the native int max size 132 // We could rethink this limit, but we don't want different validators to disagree on 133 // transaction validity so we pick the lowest common denominator 134 if newCapacity > math.MaxInt32 { 135 // If we ever did want more than an int32 of space then we would need to 136 // maintain multiple pages of memory 137 return fmt.Errorf("cannot address memory beyond a maximum index "+ 138 "with int32 width (%v bytes)", math.MaxInt32) 139 } 140 newCapacityInt := int(newCapacity) 141 // We're already big enough so return 142 if newCapacityInt <= len(mem.slice) { 143 return nil 144 } 145 if newCapacity > mem.maximumCapacity { 146 return fmt.Errorf("cannot grow memory because it would exceed the "+ 147 "current maximum limit of %v bytes", mem.maximumCapacity) 148 } 149 // Ensure the backing array of slice is big enough 150 // Grow the memory one word at time using the pre-allocated zeroBlock to avoid 151 // unnecessary allocations. Use append to make use of any spare capacity in 152 // the slice's backing array. 153 for newCapacityInt > cap(mem.slice) { 154 // We'll trust Go exponentially grow our arrays (at first). 155 mem.slice = append(mem.slice, zeroBlock...) 156 } 157 // Now we've ensured the backing array of the slice is big enough we can 158 // just re-slice (even if len(mem.slice) < newCapacity) 159 mem.slice = mem.slice[:newCapacity] 160 return nil 161 }