core/vm: clean up code and containerOffset handling

This commit is contained in:
Marius van der Wijden 2025-02-07 14:07:03 +01:00
parent 5bfacf4ba2
commit 644823db48
4 changed files with 61 additions and 74 deletions

View file

@ -67,13 +67,10 @@ func isEOFVersion1(code []byte) bool {
// Container is an EOF container object. // Container is an EOF container object.
type Container struct { type Container struct {
types []*functionMetadata types []*functionMetadata
codeSectionOffsets []int codeSectionOffsets []int // contains all the offsets of the codeSections. The last item marks the end
codeSectionEnd int
subContainers []*Container subContainers []*Container
subContainerOffsets []int subContainerOffsets []int // contains all the offsets of the subContainers. The last item marks the end
subContainerEnd int dataSize int // might be more than len(data)
dataOffest int
dataSize int // might be more than len(data)
rawContainer []byte rawContainer []byte
} }
@ -108,48 +105,44 @@ func (meta *functionMetadata) checkStackMax(stackMax int) error {
return nil return nil
} }
func (c *Container) codeSectionSize(s int) int { // codeSectionAt returns the code section at index.
if s >= len(c.codeSectionOffsets) || s < 0 { // returns an empty slice if the index is out of bounds.
return 0 func (c *Container) codeSectionAt(index int) []byte {
} else if s == len(c.codeSectionOffsets)-1 { if index >= len(c.codeSectionOffsets)-1 || index < 0 {
return c.codeSectionEnd - c.codeSectionOffsets[s]
}
return c.codeSectionOffsets[s+1] - c.codeSectionOffsets[s]
}
func (c *Container) codeSectionBytes(s int) []byte {
if s >= len(c.codeSectionOffsets) || s < 0 {
return c.rawContainer[0:0] return c.rawContainer[0:0]
} else if s == len(c.codeSectionOffsets)-1 {
return c.rawContainer[c.codeSectionOffsets[s]:c.codeSectionEnd]
} }
return c.rawContainer[c.codeSectionOffsets[s]:c.codeSectionOffsets[s+1]] return c.rawContainer[c.codeSectionOffsets[index]:c.codeSectionOffsets[index+1]]
} }
func (c *Container) subContainerSize(s int) int { // subContainerAt returns the sub container at index.
if s >= len(c.subContainerOffsets) || s < 0 { // returns an empty slice if the index is out of bounds.
return 0 func (c *Container) subContainerAt(index int) []byte {
} else if s == len(c.subContainerOffsets)-1 { if index >= len(c.subContainerOffsets)-1 || index < 0 {
return c.subContainerEnd - c.subContainerOffsets[s]
}
return c.subContainerOffsets[s+1] - c.subContainerOffsets[s]
}
func (c *Container) subContainerBytes(s int) []byte {
if s >= len(c.subContainerOffsets) || s < 0 {
return c.rawContainer[0:0] return c.rawContainer[0:0]
} else if s == len(c.subContainerOffsets)-1 {
return c.rawContainer[c.subContainerOffsets[s]:c.subContainerEnd]
} }
return c.rawContainer[c.subContainerOffsets[s]:c.subContainerOffsets[s+1]] return c.rawContainer[c.subContainerOffsets[index]:c.subContainerOffsets[index+1]]
}
func (c *Container) dataOffset() int {
if len(c.subContainerOffsets) > 0 {
return c.subContainerOffsets[len(c.subContainerOffsets)-1]
}
return c.codeSectionOffsets[len(c.codeSectionOffsets)-1]
} }
func (c *Container) dataLen() int { func (c *Container) dataLen() int {
return len(c.rawContainer) - c.dataOffest return len(c.rawContainer) - c.dataOffset()
}
func (c *Container) getDataAt(offset, length uint64) []byte {
return getData(c.rawContainer, uint64(c.dataOffset())+offset, length)
} }
// MarshalBinary encodes an EOF container into binary format. // MarshalBinary encodes an EOF container into binary format.
func (c *Container) MarshalBinary() []byte { func (c *Container) MarshalBinary() []byte {
// Drop the end markers
codeSectionOffsets := c.codeSectionOffsets[:len(c.codeSectionOffsets)-1]
// Build EOF prefix. // Build EOF prefix.
b := make([]byte, 2) b := make([]byte, 2)
copy(b, eofMagic) copy(b, eofMagic)
@ -159,9 +152,9 @@ func (c *Container) MarshalBinary() []byte {
b = append(b, kindTypes) b = append(b, kindTypes)
b = binary.BigEndian.AppendUint16(b, uint16(len(c.types)*4)) b = binary.BigEndian.AppendUint16(b, uint16(len(c.types)*4))
b = append(b, kindCode) b = append(b, kindCode)
b = binary.BigEndian.AppendUint16(b, uint16(len(c.codeSectionOffsets))) b = binary.BigEndian.AppendUint16(b, uint16(len(codeSectionOffsets)))
for s := range c.codeSectionOffsets { for s := range codeSectionOffsets {
b = binary.BigEndian.AppendUint16(b, uint16(c.codeSectionSize(s))) b = binary.BigEndian.AppendUint16(b, uint16(len(c.codeSectionAt(s))))
} }
var encodedContainer [][]byte var encodedContainer [][]byte
if len(c.subContainers) != 0 { if len(c.subContainers) != 0 {
@ -181,13 +174,13 @@ func (c *Container) MarshalBinary() []byte {
for _, ty := range c.types { for _, ty := range c.types {
b = append(b, []byte{ty.inputs, ty.outputs, byte(ty.maxStackHeight >> 8), byte(ty.maxStackHeight & 0x00ff)}...) b = append(b, []byte{ty.inputs, ty.outputs, byte(ty.maxStackHeight >> 8), byte(ty.maxStackHeight & 0x00ff)}...)
} }
for s := range c.codeSectionOffsets { for s := range codeSectionOffsets {
b = append(b, c.codeSectionBytes(s)...) b = append(b, c.codeSectionAt(s)...)
} }
for _, section := range encodedContainer { for _, section := range encodedContainer {
b = append(b, section...) b = append(b, section...)
} }
b = append(b, c.rawContainer[c.dataOffest:]...) b = append(b, c.rawContainer[c.dataOffset():]...)
return b return b
} }
@ -333,8 +326,8 @@ func (c *Container) unmarshalContainer(b []byte, isInitcode bool, topLevel bool)
codeSectionOffsets[i] = idx codeSectionOffsets[i] = idx
idx += size idx += size
} }
c.codeSectionOffsets = codeSectionOffsets // add the end marker to the codeSection offsets
c.codeSectionEnd = idx c.codeSectionOffsets = append(codeSectionOffsets, idx)
// Parse the optional container sizes. // Parse the optional container sizes.
if len(containerSizes) != 0 { if len(containerSizes) != 0 {
if len(containerSizes) > maxContainerSections { if len(containerSizes) > maxContainerSections {
@ -360,15 +353,14 @@ func (c *Container) unmarshalContainer(b []byte, isInitcode bool, topLevel bool)
idx += size idx += size
} }
c.subContainers = subContainers c.subContainers = subContainers
c.subContainerEnd = idx // add the end marker to the subContainer offsets
c.subContainerOffsets = subContainerOffsets c.subContainerOffsets = append(subContainerOffsets, idx)
} }
//Parse data section. //Parse data section.
if topLevel && len(b) != idx+dataSize { if topLevel && len(b) != idx+dataSize {
return errTruncatedTopLevelContainer return errTruncatedTopLevelContainer
} }
c.dataOffest = idx
c.rawContainer = b c.rawContainer = b
@ -398,7 +390,7 @@ func (c *Container) validateSubContainer(jt *JumpTable, refBy int) error {
// should not mean 2 and 3 should be visited twice // should not mean 2 and 3 should be visited twice
var ( var (
index = toVisit[0] index = toVisit[0]
code = c.codeSectionBytes(index) code = c.codeSectionAt(index)
) )
if _, ok := visited[index]; !ok { if _, ok := visited[index]; !ok {
res, err := validateCode(code, index, c, jt, refBy == refByEOFCreate) res, err := validateCode(code, index, c, jt, refBy == refByEOFCreate)
@ -430,7 +422,7 @@ func (c *Container) validateSubContainer(jt *JumpTable, refBy int) error {
toVisit = toVisit[1:] toVisit = toVisit[1:]
} }
// Make sure every code section is visited at least once. // Make sure every code section is visited at least once.
if len(visited) != len(c.codeSectionOffsets) { if len(visited) != len(c.codeSectionOffsets)-1 {
return errUnreachableCode return errUnreachableCode
} }
for idx, container := range c.subContainers { for idx, container := range c.subContainers {
@ -507,6 +499,8 @@ func sum(list []int) (s int) {
} }
func (c *Container) String() string { func (c *Container) String() string {
// Drop the end markers
codeSectionOffsets := c.codeSectionOffsets[:len(c.codeSectionOffsets)-1]
var output = []string{ var output = []string{
"Header", "Header",
fmt.Sprintf(" - EOFMagic: %02x", eofMagic), fmt.Sprintf(" - EOFMagic: %02x", eofMagic),
@ -514,14 +508,13 @@ func (c *Container) String() string {
fmt.Sprintf(" - KindType: %02x", kindTypes), fmt.Sprintf(" - KindType: %02x", kindTypes),
fmt.Sprintf(" - TypesSize: %04x", len(c.types)*4), fmt.Sprintf(" - TypesSize: %04x", len(c.types)*4),
fmt.Sprintf(" - KindCode: %02x", kindCode), fmt.Sprintf(" - KindCode: %02x", kindCode),
fmt.Sprintf(" - Number of code sections: %d", len(codeSectionOffsets)),
fmt.Sprintf(" - KindData: %02x", kindData), fmt.Sprintf(" - KindData: %02x", kindData),
fmt.Sprintf(" - DataSize: %04x", c.dataLen()), fmt.Sprintf(" - DataSize: %04x", c.dataSize),
fmt.Sprintf(" - Number of code sections: %d", len(c.codeSectionOffsets)),
} }
for i := range c.codeSectionOffsets { for i := range codeSectionOffsets {
output = append(output, fmt.Sprintf(" - Code section %d length: %04x", i, c.codeSectionSize(i))) output = append(output, fmt.Sprintf(" - Code section %d length: %04x", i, len(c.codeSectionAt(i))))
} }
output = append(output, fmt.Sprintf(" - Number of subcontainers: %d", len(c.subContainers))) output = append(output, fmt.Sprintf(" - Number of subcontainers: %d", len(c.subContainers)))
if len(c.subContainers) > 0 { if len(c.subContainers) > 0 {
for i, section := range c.subContainers { for i, section := range c.subContainers {
@ -533,12 +526,12 @@ func (c *Container) String() string {
output = append(output, fmt.Sprintf(" - Type %v: %x", i, output = append(output, fmt.Sprintf(" - Type %v: %x", i,
[]byte{typ.inputs, typ.outputs, byte(typ.maxStackHeight >> 8), byte(typ.maxStackHeight & 0x00ff)})) []byte{typ.inputs, typ.outputs, byte(typ.maxStackHeight >> 8), byte(typ.maxStackHeight & 0x00ff)}))
} }
for i := range c.codeSectionOffsets { for i := range codeSectionOffsets {
output = append(output, fmt.Sprintf(" - Code section %d: %#x", i, c.codeSectionBytes(i))) output = append(output, fmt.Sprintf(" - Code section %d: %#x", i, c.codeSectionAt(i)))
} }
for i, section := range c.subContainers { for i, section := range c.subContainers {
output = append(output, fmt.Sprintf(" - Subcontainer %d: %x", i, section.MarshalBinary())) output = append(output, fmt.Sprintf(" - Subcontainer %d: %x", i, section.MarshalBinary()))
} }
output = append(output, fmt.Sprintf(" - Data: %#x", c.rawContainer[c.dataOffest:])) output = append(output, fmt.Sprintf(" - Data: %#x", c.rawContainer[c.dataOffset():]))
return strings.Join(output, "\n") return strings.Join(output, "\n")
} }

View file

@ -134,10 +134,11 @@ func opEOFCreate(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) (
if int(idx) >= len(scope.Contract.Container.subContainerOffsets) { if int(idx) >= len(scope.Contract.Container.subContainerOffsets) {
return nil, fmt.Errorf("invalid subcontainer") return nil, fmt.Errorf("invalid subcontainer")
} }
subContainer := scope.Contract.Container.subContainerAt(int(idx))
// Deduct hashing charge // Deduct hashing charge
// Since size <= params.MaxInitCodeSize, these multiplication cannot overflow // Since size <= params.MaxInitCodeSize, these multiplication cannot overflow
hashingCharge := (params.Keccak256WordGas) * ((uint64(scope.Contract.Container.subContainerSize(int(idx))) + 31) / 32) hashingCharge := (params.Keccak256WordGas) * ((uint64(len(subContainer)) + 31) / 32)
if ok := scope.Contract.UseGas(hashingCharge, interpreter.evm.Config.Tracer, tracing.GasChangeUnspecified); !ok { if ok := scope.Contract.UseGas(hashingCharge, interpreter.evm.Config.Tracer, tracing.GasChangeUnspecified); !ok {
return nil, ErrGasUintOverflow return nil, ErrGasUintOverflow
} }
@ -154,7 +155,7 @@ func opEOFCreate(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) (
scope.Contract.UseGas(gas, interpreter.evm.Config.Tracer, tracing.GasChangeCallContractCreation2) scope.Contract.UseGas(gas, interpreter.evm.Config.Tracer, tracing.GasChangeCallContractCreation2)
// Skip the immediate // Skip the immediate
*pc += 1 *pc += 1
res, addr, returnGas, suberr := interpreter.evm.EOFCreate(scope.Contract, input, scope.Contract.Container.subContainerBytes(int(idx)), gas, &value, &salt) res, addr, returnGas, suberr := interpreter.evm.EOFCreate(scope.Contract, input, subContainer, gas, &value, &salt)
if suberr != nil { if suberr != nil {
stackvalue.Clear() stackvalue.Clear()
} else { } else {
@ -185,7 +186,7 @@ func opReturnContract(pc *uint64, interpreter *EVMInterpreter, scope *ScopeConte
return nil, fmt.Errorf("invalid subcontainer") return nil, fmt.Errorf("invalid subcontainer")
} }
ret := scope.Memory.GetPtr(offset.Uint64(), size.Uint64()) ret := scope.Memory.GetPtr(offset.Uint64(), size.Uint64())
containerCode := scope.Contract.Container.subContainerBytes(int(idx)) containerCode := scope.Contract.Container.subContainerAt(int(idx))
if len(containerCode) == 0 { if len(containerCode) == 0 {
return nil, errors.New("nonexistant subcontainer") return nil, errors.New("nonexistant subcontainer")
} }
@ -225,7 +226,7 @@ func opDataLoad(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([
stackItem.Clear() stackItem.Clear()
scope.Stack.push(&stackItem) scope.Stack.push(&stackItem)
} else { } else {
data := getData(scope.Contract.Container.rawContainer, uint64(scope.Contract.Container.dataOffest)+offset, 32) data := scope.Contract.Container.getDataAt(offset, 32)
scope.Stack.push(stackItem.SetBytes(data)) scope.Stack.push(stackItem.SetBytes(data))
} }
return nil, nil return nil, nil
@ -236,7 +237,7 @@ func opDataLoadN(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) (
var ( var (
offset = uint64(binary.BigEndian.Uint16(scope.Contract.Code[*pc+1:])) offset = uint64(binary.BigEndian.Uint16(scope.Contract.Code[*pc+1:]))
) )
data := getData(scope.Contract.Container.rawContainer, uint64(scope.Contract.Container.dataOffest)+offset, 32) data := scope.Contract.Container.getDataAt(offset, 32)
scope.Stack.push(new(uint256.Int).SetBytes(data)) scope.Stack.push(new(uint256.Int).SetBytes(data))
*pc += 2 // move past 2 byte immediate *pc += 2 // move past 2 byte immediate
return nil, nil return nil, nil
@ -258,7 +259,7 @@ func opDataCopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([
) )
// These values are checked for overflow during memory expansion calculation // These values are checked for overflow during memory expansion calculation
// (the memorySize function on the opcode). // (the memorySize function on the opcode).
data := getData(scope.Contract.Container.rawContainer, uint64(scope.Contract.Container.dataOffest)+offset.Uint64(), size.Uint64()) data := scope.Contract.Container.getDataAt(offset.Uint64(), size.Uint64())
scope.Memory.Set(memOffset.Uint64(), size.Uint64(), data) scope.Memory.Set(memOffset.Uint64(), size.Uint64(), data)
return nil, nil return nil, nil
} }

View file

@ -40,10 +40,9 @@ func MakeTestContainer(
idx += len(code) idx += len(code)
testBytes = append(testBytes, code...) testBytes = append(testBytes, code...)
} }
codeSectionEnd := idx codeSectionOffsets = append(codeSectionOffsets, idx)
var subContainerOffsets []int var subContainerOffsets []int
subContainerEnd := 0
if len(subContainers) > 0 { if len(subContainers) > 0 {
subContainerOffsets = make([]int, len(subContainers)) subContainerOffsets = make([]int, len(subContainers))
for _, subContainer := range subContainers { for _, subContainer := range subContainers {
@ -52,7 +51,8 @@ func MakeTestContainer(
idx += len(containerBytes) idx += len(containerBytes)
testBytes = append(testBytes, containerBytes...) testBytes = append(testBytes, containerBytes...)
} }
subContainerEnd = idx // set the subContainer end marker
subContainerOffsets = append(subContainerOffsets, idx)
} }
testBytes = append(testBytes, data...) testBytes = append(testBytes, data...)
@ -60,11 +60,8 @@ func MakeTestContainer(
return Container{ return Container{
types: types, types: types,
codeSectionOffsets: codeSectionOffsets, codeSectionOffsets: codeSectionOffsets,
codeSectionEnd: codeSectionEnd,
subContainers: subContainers, subContainers: subContainers,
subContainerOffsets: subContainerOffsets, subContainerOffsets: subContainerOffsets,
subContainerEnd: subContainerEnd,
dataOffest: subContainerEnd,
dataSize: dataSize, dataSize: dataSize,
rawContainer: testBytes, rawContainer: testBytes,
} }
@ -78,9 +75,7 @@ func TestEOFMarshaling(t *testing.T) {
{ {
want: Container{ want: Container{
types: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 1}}, types: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 1}},
codeSectionOffsets: []int{19}, // 604200 codeSectionOffsets: []int{19, 22}, // 604200, endMarker
codeSectionEnd: 22,
dataOffest: 22,
dataSize: 3, dataSize: 3,
rawContainer: common.Hex2Bytes("ef000101000402000100030400030000800001604200010203"), rawContainer: common.Hex2Bytes("ef000101000402000100030400030000800001604200010203"),
}, },
@ -92,9 +87,7 @@ func TestEOFMarshaling(t *testing.T) {
{inputs: 2, outputs: 3, maxStackHeight: 4}, {inputs: 2, outputs: 3, maxStackHeight: 4},
{inputs: 1, outputs: 1, maxStackHeight: 1}, {inputs: 1, outputs: 1, maxStackHeight: 1},
}, },
codeSectionOffsets: []int{31, 34, 39}, // 604200, 6042604200, 00 codeSectionOffsets: []int{31, 34, 39, 40}, // 604200, 6042604200, 00, endMarker
codeSectionEnd: 40,
dataOffest: 40,
rawContainer: common.Hex2Bytes("ef000101000c02000300030005000104000000008000010203000401010001604200604260420000"), rawContainer: common.Hex2Bytes("ef000101000c02000300030005000104000000008000010203000401010001604200604260420000"),
}, },
}, },

BIN
statetest

Binary file not shown.