mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-08-19 02:12:23 +00:00
core/vm: refactor + speed up eof validation
core/vm: some clarifications in the eof code core/vm: clarifications + minor speedup core/vm: clarifications + lint + minor speedup core/vm, core/asm: support eof in asm instruction iteration core/vm: comment out unused core/vm: remove gasfunctions
This commit is contained in:
parent
77484b4cf6
commit
d3f736faf0
15 changed files with 207 additions and 196 deletions
|
|
@ -215,7 +215,6 @@ func parseAndValidate(s string, isInitCode bool) (*vm.Container, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func parse(b []byte, isInitCode bool) (*vm.Container, error) {
|
func parse(b []byte, isInitCode bool) (*vm.Container, error) {
|
||||||
|
|
||||||
var c vm.Container
|
var c vm.Container
|
||||||
if err := c.UnmarshalBinary(b, isInitCode); err != nil {
|
if err := c.UnmarshalBinary(b, isInitCode); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
|
||||||
|
|
@ -50,13 +50,13 @@ func FuzzEofParsing(f *testing.F) {
|
||||||
if err := c.UnmarshalBinary(data, true); err == nil {
|
if err := c.UnmarshalBinary(data, true); err == nil {
|
||||||
c.ValidateCode(&jt, true)
|
c.ValidateCode(&jt, true)
|
||||||
if have := c.MarshalBinary(); !bytes.Equal(have, data) {
|
if have := c.MarshalBinary(); !bytes.Equal(have, data) {
|
||||||
f.Fatal("Unmarshal-> Marshal failure!")
|
t.Fatal("Unmarshal-> Marshal failure!")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err := c.UnmarshalBinary(data, false); err == nil {
|
if err := c.UnmarshalBinary(data, false); err == nil {
|
||||||
c.ValidateCode(&jt, false)
|
c.ValidateCode(&jt, false)
|
||||||
if have := c.MarshalBinary(); !bytes.Equal(have, data) {
|
if have := c.MarshalBinary(); !bytes.Equal(have, data) {
|
||||||
f.Fatal("Unmarshal-> Marshal failure!")
|
t.Fatal("Unmarshal-> Marshal failure!")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !bytes.Equal(cpy, data) {
|
if !bytes.Equal(cpy, data) {
|
||||||
|
|
@ -131,7 +131,6 @@ func testEofParse(t *testing.T, isInitCode bool, wantFile string) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
line++
|
line++
|
||||||
|
|
||||||
}
|
}
|
||||||
corpus.Close()
|
corpus.Close()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -65,11 +65,11 @@ func (it *instructionIterator) Next() bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
it.op = vm.OpCode(it.code[it.pc])
|
it.op = vm.OpCode(it.code[it.pc])
|
||||||
if it.op.IsPush() {
|
|
||||||
a := uint64(it.op) - uint64(vm.PUSH0)
|
if a := vm.Immediates(it.op); a > 0 {
|
||||||
u := it.pc + 1 + a
|
u := it.pc + 1 + uint64(a)
|
||||||
if uint64(len(it.code)) <= it.pc || uint64(len(it.code)) < u {
|
if uint64(len(it.code)) <= it.pc || uint64(len(it.code)) < u {
|
||||||
it.error = fmt.Errorf("incomplete push instruction at %v", it.pc)
|
it.error = fmt.Errorf("incomplete instruction at %v", it.pc)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
it.arg = it.code[it.pc+1 : u]
|
it.arg = it.code[it.pc+1 : u]
|
||||||
|
|
|
||||||
|
|
@ -29,10 +29,11 @@ func TestInstructionIterator(t *testing.T) {
|
||||||
code string
|
code string
|
||||||
wantErr string
|
wantErr string
|
||||||
}{
|
}{
|
||||||
{2, "61000000", ""}, // valid code
|
{2, "61000000", ""}, // valid code
|
||||||
{0, "6100", "incomplete push instruction at 0"}, // invalid code
|
{0, "6100", "incomplete instruction at 0"}, // invalid code
|
||||||
{2, "5900", ""}, // push0
|
{2, "5900", ""}, // push0
|
||||||
{0, "", ""}, // empty
|
{0, "", ""}, // empty
|
||||||
|
{2, "d1aabb00", ""}, // DATALOADN(aabb),STOP
|
||||||
|
|
||||||
} {
|
} {
|
||||||
var (
|
var (
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,9 @@ func eofCodeBitmapInternal(code, bits bitvec) bitvec {
|
||||||
pc++
|
pc++
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
case op >= PUSH1 && op <= PUSH32:
|
case op < PUSH1:
|
||||||
|
continue
|
||||||
|
case op <= PUSH32:
|
||||||
numbits = uint16(op - PUSH1 + 1)
|
numbits = uint16(op - PUSH1 + 1)
|
||||||
case op == RJUMP || op == RJUMPI || op == CALLF || op == JUMPF || op == DATALOADN:
|
case op == RJUMP || op == RJUMPI || op == CALLF || op == JUMPF || op == DATALOADN:
|
||||||
numbits = 2
|
numbits = 2
|
||||||
|
|
|
||||||
|
|
@ -535,6 +535,13 @@ func enable4762(jt *JumpTable) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// enableEOF applies the EOF changes.
|
// enableEOF applies the EOF changes.
|
||||||
|
// OBS! For EOF, there are two changes:
|
||||||
|
// 1. Two separate jumptables are required. One, EOF-jumptable, is used by
|
||||||
|
// eof contracts. This one contains things like RJUMP.
|
||||||
|
// 2. The regular non-eof jumptable also needs to be modified, specifically to
|
||||||
|
// modify how EXTCODECOPY works under the hood.
|
||||||
|
//
|
||||||
|
// This method _only_ deals with case 1.
|
||||||
func enableEOF(jt *JumpTable) {
|
func enableEOF(jt *JumpTable) {
|
||||||
// Deprecate opcodes
|
// Deprecate opcodes
|
||||||
undefined := &operation{
|
undefined := &operation{
|
||||||
|
|
|
||||||
|
|
@ -66,12 +66,12 @@ 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
|
||||||
code [][]byte
|
codeSections [][]byte
|
||||||
sections []*Container
|
subContainers []*Container
|
||||||
containerCode [][]byte
|
subContainerCodes [][]byte
|
||||||
data []byte
|
data []byte
|
||||||
dataSize int // might be more than len(data)
|
dataSize int // might be more than len(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
// functionMetadata is an EOF function signature.
|
// functionMetadata is an EOF function signature.
|
||||||
|
|
@ -92,15 +92,15 @@ 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.code)))
|
b = binary.BigEndian.AppendUint16(b, uint16(len(c.codeSections)))
|
||||||
for _, code := range c.code {
|
for _, codeSection := range c.codeSections {
|
||||||
b = binary.BigEndian.AppendUint16(b, uint16(len(code)))
|
b = binary.BigEndian.AppendUint16(b, uint16(len(codeSection)))
|
||||||
}
|
}
|
||||||
var encodedContainer [][]byte
|
var encodedContainer [][]byte
|
||||||
if len(c.sections) != 0 {
|
if len(c.subContainers) != 0 {
|
||||||
b = append(b, kindContainer)
|
b = append(b, kindContainer)
|
||||||
b = binary.BigEndian.AppendUint16(b, uint16(len(c.sections)))
|
b = binary.BigEndian.AppendUint16(b, uint16(len(c.subContainers)))
|
||||||
for _, section := range c.sections {
|
for _, section := range c.subContainers {
|
||||||
encoded := section.MarshalBinary()
|
encoded := section.MarshalBinary()
|
||||||
b = binary.BigEndian.AppendUint16(b, uint16(len(encoded)))
|
b = binary.BigEndian.AppendUint16(b, uint16(len(encoded)))
|
||||||
encodedContainer = append(encodedContainer, encoded)
|
encodedContainer = append(encodedContainer, encoded)
|
||||||
|
|
@ -114,7 +114,7 @@ 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 _, code := range c.code {
|
for _, code := range c.codeSections {
|
||||||
b = append(b, code...)
|
b = append(b, code...)
|
||||||
}
|
}
|
||||||
for _, section := range encodedContainer {
|
for _, section := range encodedContainer {
|
||||||
|
|
@ -228,7 +228,7 @@ func (c *Container) unmarshalSubContainer(b []byte, isInitcode bool, topLevel bo
|
||||||
|
|
||||||
// Parse types section.
|
// Parse types section.
|
||||||
idx := offsetTerminator + 1
|
idx := offsetTerminator + 1
|
||||||
var types []*functionMetadata
|
var types = make([]*functionMetadata, 0, typesSize/4)
|
||||||
for i := 0; i < typesSize/4; i++ {
|
for i := 0; i < typesSize/4; i++ {
|
||||||
sig := &functionMetadata{
|
sig := &functionMetadata{
|
||||||
inputs: b[idx+i*4],
|
inputs: b[idx+i*4],
|
||||||
|
|
@ -253,45 +253,44 @@ func (c *Container) unmarshalSubContainer(b []byte, isInitcode bool, topLevel bo
|
||||||
|
|
||||||
// Parse code sections.
|
// Parse code sections.
|
||||||
idx += typesSize
|
idx += typesSize
|
||||||
code := make([][]byte, len(codeSizes))
|
codeSections := make([][]byte, len(codeSizes))
|
||||||
for i, size := range codeSizes {
|
for i, size := range codeSizes {
|
||||||
if size == 0 {
|
if size == 0 {
|
||||||
return fmt.Errorf("%w for section %d: size must not be 0", ErrInvalidCodeSize, i)
|
return fmt.Errorf("%w for section %d: size must not be 0", ErrInvalidCodeSize, i)
|
||||||
}
|
}
|
||||||
code[i] = b[idx : idx+size]
|
codeSections[i] = b[idx : idx+size]
|
||||||
idx += size
|
idx += size
|
||||||
}
|
}
|
||||||
c.code = code
|
c.codeSections = codeSections
|
||||||
|
|
||||||
// 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 {
|
||||||
return fmt.Errorf("%w number of container section exceed: %v: have %v", ErrInvalidContainerSectionSize, maxContainerSections, len(containerSizes))
|
return fmt.Errorf("%w number of container section exceed: %v: have %v", ErrInvalidContainerSectionSize, maxContainerSections, len(containerSizes))
|
||||||
}
|
}
|
||||||
containerCode := make([][]byte, 0, len(containerSizes))
|
subContainerCodes := make([][]byte, 0, len(containerSizes))
|
||||||
container := make([]*Container, 0, len(containerSizes))
|
subContainers := make([]*Container, 0, len(containerSizes))
|
||||||
for i, size := range containerSizes {
|
for i, size := range containerSizes {
|
||||||
if size == 0 || idx+size > len(b) {
|
if size == 0 || idx+size > len(b) {
|
||||||
return fmt.Errorf("%w for section %d: size must not be 0", ErrInvalidContainerSectionSize, i)
|
return fmt.Errorf("%w for section %d: size must not be 0", ErrInvalidContainerSectionSize, i)
|
||||||
}
|
}
|
||||||
c := new(Container)
|
subC := new(Container)
|
||||||
end := min(idx+size, len(b))
|
end := min(idx+size, len(b))
|
||||||
if err := c.unmarshalSubContainer(b[idx:end], isInitcode, false); err != nil {
|
if err := subC.unmarshalSubContainer(b[idx:end], isInitcode, false); err != nil {
|
||||||
if topLevel {
|
if topLevel {
|
||||||
return fmt.Errorf("%w in sub container %d", err, i)
|
return fmt.Errorf("%w in sub container %d", err, i)
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
container = append(container, c)
|
subContainers = append(subContainers, subC)
|
||||||
containerCode = append(containerCode, b[idx:end])
|
subContainerCodes = append(subContainerCodes, b[idx:end])
|
||||||
|
|
||||||
idx += size
|
idx += size
|
||||||
}
|
}
|
||||||
c.sections = container
|
c.subContainers = subContainers
|
||||||
c.containerCode = containerCode
|
c.subContainerCodes = subContainerCodes
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse data section.
|
//Parse data section.
|
||||||
end := len(b)
|
end := len(b)
|
||||||
if !isInitcode {
|
if !isInitcode {
|
||||||
end = min(idx+dataSize, len(b))
|
end = min(idx+dataSize, len(b))
|
||||||
|
|
@ -327,7 +326,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.code[index]
|
code = c.codeSections[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)
|
||||||
|
|
@ -359,10 +358,10 @@ 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.code) {
|
if len(visited) != len(c.codeSections) {
|
||||||
return ErrUnreachableCode
|
return ErrUnreachableCode
|
||||||
}
|
}
|
||||||
for idx, container := range c.sections {
|
for idx, container := range c.subContainers {
|
||||||
reference, ok := subContainerVisited[idx]
|
reference, ok := subContainerVisited[idx]
|
||||||
if !ok {
|
if !ok {
|
||||||
return ErrOrphanedSubcontainer
|
return ErrOrphanedSubcontainer
|
||||||
|
|
@ -444,14 +443,14 @@ func (c *Container) String() string {
|
||||||
result += fmt.Sprintf("KindType: %02x\n", kindTypes)
|
result += fmt.Sprintf("KindType: %02x\n", kindTypes)
|
||||||
result += fmt.Sprintf("TypesSize: %04x\n", len(c.types)*4)
|
result += fmt.Sprintf("TypesSize: %04x\n", len(c.types)*4)
|
||||||
result += fmt.Sprintf("KindCode: %02x\n", kindCode)
|
result += fmt.Sprintf("KindCode: %02x\n", kindCode)
|
||||||
result += fmt.Sprintf("CodeSize: %04x\n", len(c.code))
|
result += fmt.Sprintf("CodeSize: %04x\n", len(c.codeSections))
|
||||||
for i, code := range c.code {
|
for i, code := range c.codeSections {
|
||||||
result += fmt.Sprintf("Code %v length: %04x\n", i, len(code))
|
result += fmt.Sprintf("Code %v length: %04x\n", i, len(code))
|
||||||
}
|
}
|
||||||
if len(c.sections) != 0 {
|
if len(c.subContainers) != 0 {
|
||||||
result += fmt.Sprintf("KindContainer: %02x\n", kindContainer)
|
result += fmt.Sprintf("KindContainer: %02x\n", kindContainer)
|
||||||
result += fmt.Sprintf("ContainerSize: %04x\n", len(c.sections))
|
result += fmt.Sprintf("ContainerSize: %04x\n", len(c.subContainers))
|
||||||
for i, section := range c.sections {
|
for i, section := range c.subContainers {
|
||||||
result += fmt.Sprintf("Container %v length: %04x\n", i, len(section.MarshalBinary()))
|
result += fmt.Sprintf("Container %v length: %04x\n", i, len(section.MarshalBinary()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -464,10 +463,10 @@ func (c *Container) String() string {
|
||||||
for i, typ := range c.types {
|
for i, typ := range c.types {
|
||||||
result += fmt.Sprintf("Type %v: %v\n", i, hex.EncodeToString([]byte{typ.inputs, typ.outputs, byte(typ.maxStackHeight >> 8), byte(typ.maxStackHeight & 0x00ff)}))
|
result += fmt.Sprintf("Type %v: %v\n", i, hex.EncodeToString([]byte{typ.inputs, typ.outputs, byte(typ.maxStackHeight >> 8), byte(typ.maxStackHeight & 0x00ff)}))
|
||||||
}
|
}
|
||||||
for i, code := range c.code {
|
for i, code := range c.codeSections {
|
||||||
result += fmt.Sprintf("Code %v: %v\n", i, hex.EncodeToString(code))
|
result += fmt.Sprintf("Code %v: %v\n", i, hex.EncodeToString(code))
|
||||||
}
|
}
|
||||||
for i, section := range c.sections {
|
for i, section := range c.subContainers {
|
||||||
result += fmt.Sprintf("Section %v: %v\n", i, hex.EncodeToString(section.MarshalBinary()))
|
result += fmt.Sprintf("Section %v: %v\n", i, hex.EncodeToString(section.MarshalBinary()))
|
||||||
}
|
}
|
||||||
result += fmt.Sprintf("Data: %v\n", hex.EncodeToString(c.data))
|
result += fmt.Sprintf("Data: %v\n", hex.EncodeToString(c.data))
|
||||||
|
|
|
||||||
|
|
@ -55,3 +55,9 @@ func init() {
|
||||||
terminals[REVERT] = true
|
terminals[REVERT] = true
|
||||||
terminals[INVALID] = true
|
terminals[INVALID] = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Immediates returns the number bytes of immediates (argument not from
|
||||||
|
// stack but from code) a given opcode has.
|
||||||
|
func Immediates(op OpCode) int {
|
||||||
|
return int(immediates[op])
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,11 +16,6 @@
|
||||||
|
|
||||||
package vm
|
package vm
|
||||||
|
|
||||||
// opExtCodeCopyEOF implements the EXTCODECOPY opcode for EOF-enabled forks.
|
|
||||||
func opExtCodeCopyEOF(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
|
||||||
panic("not implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
// opRjump implements the RJUMP opcode.
|
// opRjump implements the RJUMP opcode.
|
||||||
func opRjump(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
func opRjump(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
||||||
panic("not implemented")
|
panic("not implemented")
|
||||||
|
|
|
||||||
|
|
@ -32,18 +32,18 @@ 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}},
|
||||||
code: [][]byte{common.Hex2Bytes("604200")},
|
codeSections: [][]byte{common.Hex2Bytes("604200")},
|
||||||
data: []byte{0x01, 0x02, 0x03},
|
data: []byte{0x01, 0x02, 0x03},
|
||||||
dataSize: 3,
|
dataSize: 3,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
want: Container{
|
want: Container{
|
||||||
types: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 1}},
|
types: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 1}},
|
||||||
code: [][]byte{common.Hex2Bytes("604200")},
|
codeSections: [][]byte{common.Hex2Bytes("604200")},
|
||||||
data: []byte{0x01, 0x02, 0x03},
|
data: []byte{0x01, 0x02, 0x03},
|
||||||
dataSize: 3,
|
dataSize: 3,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -53,7 +53,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},
|
||||||
},
|
},
|
||||||
code: [][]byte{
|
codeSections: [][]byte{
|
||||||
common.Hex2Bytes("604200"),
|
common.Hex2Bytes("604200"),
|
||||||
common.Hex2Bytes("6042604200"),
|
common.Hex2Bytes("6042604200"),
|
||||||
common.Hex2Bytes("00"),
|
common.Hex2Bytes("00"),
|
||||||
|
|
@ -82,11 +82,11 @@ func TestEOFSubcontainer(t *testing.T) {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
container := Container{
|
container := Container{
|
||||||
types: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 1}},
|
types: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 1}},
|
||||||
code: [][]byte{common.Hex2Bytes("604200")},
|
codeSections: [][]byte{common.Hex2Bytes("604200")},
|
||||||
sections: []*Container{subcontainer},
|
subContainers: []*Container{subcontainer},
|
||||||
data: []byte{0x01, 0x02, 0x03},
|
data: []byte{0x01, 0x02, 0x03},
|
||||||
dataSize: 3,
|
dataSize: 3,
|
||||||
}
|
}
|
||||||
var (
|
var (
|
||||||
b = container.MarshalBinary()
|
b = container.MarshalBinary()
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,6 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/common/math"
|
"github.com/ethereum/go-ethereum/common/math"
|
||||||
"github.com/ethereum/go-ethereum/params"
|
"github.com/ethereum/go-ethereum/params"
|
||||||
"github.com/holiman/uint256"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// memoryGasCost calculates the quadratic gas for memory expansion. It does so
|
// memoryGasCost calculates the quadratic gas for memory expansion. It does so
|
||||||
|
|
@ -481,52 +480,6 @@ func gasStaticCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memo
|
||||||
return gas, nil
|
return gas, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func gasExtCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
|
|
||||||
var (
|
|
||||||
gas uint64
|
|
||||||
transfersValue = !stack.Back(2).IsZero()
|
|
||||||
address = common.Address(stack.Back(1).Bytes20())
|
|
||||||
)
|
|
||||||
if transfersValue && evm.StateDB.Empty(address) {
|
|
||||||
gas += params.CallNewAccountGas
|
|
||||||
}
|
|
||||||
if transfersValue && !evm.chainRules.IsEIP4762 { // Non-verkle
|
|
||||||
gas += params.CallValueTransferGas
|
|
||||||
} else if transfersValue && evm.chainRules.IsEIP4762 { // Verkle
|
|
||||||
gas += evm.AccessEvents.ValueTransferGas(contract.Address(), address)
|
|
||||||
}
|
|
||||||
memoryGas, err := gasEOFMemCost(evm, contract, stack, mem, memorySize)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
var overflow bool
|
|
||||||
if gas, overflow = math.SafeAdd(gas, memoryGas); overflow {
|
|
||||||
return 0, ErrGasUintOverflow
|
|
||||||
}
|
|
||||||
return gas, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// gasEOFMemCost calculates the cost of an external call in EOF, but does not
|
|
||||||
// take value-transfer-related costs into account: only mem expansion costs.
|
|
||||||
func gasEOFMemCost(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
|
|
||||||
gas, err := memoryGasCost(mem, memorySize)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
evm.callGasTemp, err = callGas(true, contract.Gas, gas, new(uint256.Int).SetUint64(contract.Gas))
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
var overflow bool
|
|
||||||
if gas, overflow = math.SafeAdd(gas, evm.callGasTemp); overflow {
|
|
||||||
return 0, ErrGasUintOverflow
|
|
||||||
}
|
|
||||||
return gas, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var gasExtDelegateCall = gasEOFMemCost
|
|
||||||
var gasExtStaticCall = gasEOFMemCost
|
|
||||||
|
|
||||||
func gasSelfdestruct(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
|
func gasSelfdestruct(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
|
||||||
var gas uint64
|
var gas uint64
|
||||||
// EIP150 homestead gas reprice fork:
|
// EIP150 homestead gas reprice fork:
|
||||||
|
|
@ -550,8 +503,19 @@ func gasSelfdestruct(evm *EVM, contract *Contract, stack *Stack, mem *Memory, me
|
||||||
return gas, nil
|
return gas, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func gasExtCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
|
||||||
|
panic("not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
func gasExtDelegateCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
|
||||||
|
panic("not implemented")
|
||||||
|
}
|
||||||
|
func gasExtStaticCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
|
||||||
|
panic("not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
// gasEOFCreate returns the gas-cost for EOF-Create. Hashing charge needs to be
|
// gasEOFCreate returns the gas-cost for EOF-Create. Hashing charge needs to be
|
||||||
// deducted in the opcode itself, since it depends on the immediate
|
// deducted in the opcode itself, since it depends on the immediate
|
||||||
func gasEOFCreate(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
|
func gasEOFCreate(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
|
||||||
return memoryGasCost(mem, memorySize)
|
panic("not implemented")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,8 @@ import (
|
||||||
type OpCode byte
|
type OpCode byte
|
||||||
|
|
||||||
// IsPush specifies if an opcode is a PUSH opcode.
|
// IsPush specifies if an opcode is a PUSH opcode.
|
||||||
|
// @deprecated: this method is often used in order to know if there are immediates.
|
||||||
|
// Please use `vm.Immediates` instead.
|
||||||
func (op OpCode) IsPush() bool {
|
func (op OpCode) IsPush() bool {
|
||||||
return PUSH0 <= op && op <= PUSH32
|
return PUSH0 <= op && op <= PUSH32
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -82,8 +82,8 @@ func validateCode(code []byte, section int, container *Container, jt *JumpTable,
|
||||||
count = 0
|
count = 0
|
||||||
op OpCode
|
op OpCode
|
||||||
analysis bitvec
|
analysis bitvec
|
||||||
visitedCode = make(map[int]struct{})
|
visitedCode map[int]struct{}
|
||||||
visitedSubcontainers = make(map[int]int)
|
visitedSubcontainers map[int]int
|
||||||
hasReturnContract bool
|
hasReturnContract bool
|
||||||
hasStop bool
|
hasStop bool
|
||||||
)
|
)
|
||||||
|
|
@ -129,6 +129,9 @@ func validateCode(code []byte, section int, container *Container, jt *JumpTable,
|
||||||
if container.types[arg].outputs == 0x80 {
|
if container.types[arg].outputs == 0x80 {
|
||||||
return nil, fmt.Errorf("%w: section %v", ErrInvalidCallArgument, arg)
|
return nil, fmt.Errorf("%w: section %v", ErrInvalidCallArgument, arg)
|
||||||
}
|
}
|
||||||
|
if visitedCode == nil {
|
||||||
|
visitedCode = make(map[int]struct{})
|
||||||
|
}
|
||||||
visitedCode[arg] = struct{}{}
|
visitedCode[arg] = struct{}{}
|
||||||
case JUMPF:
|
case JUMPF:
|
||||||
arg, _ := parseUint16(code[i+1:])
|
arg, _ := parseUint16(code[i+1:])
|
||||||
|
|
@ -138,6 +141,9 @@ func validateCode(code []byte, section int, container *Container, jt *JumpTable,
|
||||||
if container.types[arg].outputs != 0x80 && container.types[arg].outputs > container.types[section].outputs {
|
if container.types[arg].outputs != 0x80 && container.types[arg].outputs > container.types[section].outputs {
|
||||||
return nil, fmt.Errorf("%w: arg %d, last %d, pos %d", ErrInvalidOutputs, arg, len(container.types), i)
|
return nil, fmt.Errorf("%w: arg %d, last %d, pos %d", ErrInvalidOutputs, arg, len(container.types), i)
|
||||||
}
|
}
|
||||||
|
if visitedCode == nil {
|
||||||
|
visitedCode = make(map[int]struct{})
|
||||||
|
}
|
||||||
visitedCode[arg] = struct{}{}
|
visitedCode[arg] = struct{}{}
|
||||||
case DATALOADN:
|
case DATALOADN:
|
||||||
arg, _ := parseUint16(code[i+1:])
|
arg, _ := parseUint16(code[i+1:])
|
||||||
|
|
@ -150,8 +156,11 @@ func validateCode(code []byte, section int, container *Container, jt *JumpTable,
|
||||||
return nil, ErrIncompatibleContainerKind
|
return nil, ErrIncompatibleContainerKind
|
||||||
}
|
}
|
||||||
arg := int(code[i+1])
|
arg := int(code[i+1])
|
||||||
if arg >= len(container.sections) {
|
if arg >= len(container.subContainers) {
|
||||||
return nil, fmt.Errorf("%w: arg %d, last %d, pos %d", ErrUnreachableCode, arg, len(container.sections), i)
|
return nil, fmt.Errorf("%w: arg %d, last %d, pos %d", ErrUnreachableCode, arg, len(container.subContainers), i)
|
||||||
|
}
|
||||||
|
if visitedSubcontainers == nil {
|
||||||
|
visitedSubcontainers = make(map[int]int)
|
||||||
}
|
}
|
||||||
// We need to store per subcontainer how it was referenced
|
// We need to store per subcontainer how it was referenced
|
||||||
if v, ok := visitedSubcontainers[arg]; ok && v != refByReturnContract {
|
if v, ok := visitedSubcontainers[arg]; ok && v != refByReturnContract {
|
||||||
|
|
@ -164,12 +173,15 @@ func validateCode(code []byte, section int, container *Container, jt *JumpTable,
|
||||||
visitedSubcontainers[arg] = refByReturnContract
|
visitedSubcontainers[arg] = refByReturnContract
|
||||||
case EOFCREATE:
|
case EOFCREATE:
|
||||||
arg := int(code[i+1])
|
arg := int(code[i+1])
|
||||||
if arg >= len(container.sections) {
|
if arg >= len(container.subContainers) {
|
||||||
return nil, fmt.Errorf("%w: arg %d, last %d, pos %d", ErrUnreachableCode, arg, len(container.sections), i)
|
return nil, fmt.Errorf("%w: arg %d, last %d, pos %d", ErrUnreachableCode, arg, len(container.subContainers), i)
|
||||||
}
|
}
|
||||||
if ct := container.sections[arg]; len(ct.data) != ct.dataSize {
|
if ct := container.subContainers[arg]; len(ct.data) != ct.dataSize {
|
||||||
return nil, fmt.Errorf("%w: container %d, have %d, claimed %d, pos %d", ErrEOFCreateWithTruncatedSection, arg, len(ct.data), ct.dataSize, i)
|
return nil, fmt.Errorf("%w: container %d, have %d, claimed %d, pos %d", ErrEOFCreateWithTruncatedSection, arg, len(ct.data), ct.dataSize, i)
|
||||||
}
|
}
|
||||||
|
if visitedSubcontainers == nil {
|
||||||
|
visitedSubcontainers = make(map[int]int)
|
||||||
|
}
|
||||||
if _, ok := visitedSubcontainers[arg]; ok {
|
if _, ok := visitedSubcontainers[arg]; ok {
|
||||||
return nil, fmt.Errorf("section already referenced, arg :%d", arg)
|
return nil, fmt.Errorf("section already referenced, arg :%d", arg)
|
||||||
}
|
}
|
||||||
|
|
@ -226,3 +238,21 @@ func checkDest(code []byte, analysis *bitvec, imm, from, length int) error {
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//// disasm is a helper utility to show a sequence of comma-separated operations,
|
||||||
|
//// with immediates shown inline,
|
||||||
|
//// e.g: PUSH1(0x00),EOFCREATE(0x00),
|
||||||
|
//func disasm(code []byte) string {
|
||||||
|
// var ops []string
|
||||||
|
// for i := 0; i < len(code); i++ {
|
||||||
|
// var op string
|
||||||
|
// if args := immediates[code[i]]; args > 0 {
|
||||||
|
// op = fmt.Sprintf("%v(%#x)", OpCode(code[i]).String(), code[i+1:i+1+int(args)])
|
||||||
|
// i += int(args)
|
||||||
|
// } else {
|
||||||
|
// op = OpCode(code[i]).String()
|
||||||
|
// }
|
||||||
|
// ops = append(ops, op)
|
||||||
|
// }
|
||||||
|
// return strings.Join(ops, ",")
|
||||||
|
//}
|
||||||
|
|
|
||||||
|
|
@ -6,22 +6,37 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/params"
|
"github.com/ethereum/go-ethereum/params"
|
||||||
)
|
)
|
||||||
|
|
||||||
type bounds struct {
|
|
||||||
min int
|
|
||||||
max int
|
|
||||||
}
|
|
||||||
|
|
||||||
func validateControlFlow(code []byte, section int, metadata []*functionMetadata, jt *JumpTable) (int, error) {
|
func validateControlFlow(code []byte, section int, metadata []*functionMetadata, jt *JumpTable) (int, error) {
|
||||||
var (
|
var (
|
||||||
stackBounds = make(map[int]*bounds)
|
|
||||||
maxStackHeight = int(metadata[section].inputs)
|
maxStackHeight = int(metadata[section].inputs)
|
||||||
debugging = !true
|
debugging = !true
|
||||||
|
visitCount = 0
|
||||||
|
next = make([]int, 0, 1)
|
||||||
)
|
)
|
||||||
|
var (
|
||||||
setBounds := func(pos, min, maxi int) *bounds {
|
stackBoundsMax = make([]uint16, len(code))
|
||||||
stackBounds[pos] = &bounds{min, maxi}
|
stackBoundsMin = make([]uint16, len(code))
|
||||||
|
)
|
||||||
|
setBounds := func(pos, min, maxi int) {
|
||||||
|
// The stackboundMax slice is a bit peculiar. We use `0` to denote
|
||||||
|
// not set. Therefore, we use `1` to represent the value `0`, and so on.
|
||||||
|
// So if the caller wants to store `1` as max bound, we internally store it as
|
||||||
|
// `2`.
|
||||||
|
if stackBoundsMax[pos] == 0 { // Not yet set
|
||||||
|
visitCount++
|
||||||
|
}
|
||||||
|
if maxi < 65535 {
|
||||||
|
stackBoundsMax[pos] = uint16(maxi + 1)
|
||||||
|
}
|
||||||
|
stackBoundsMin[pos] = uint16(min)
|
||||||
maxStackHeight = max(maxStackHeight, maxi)
|
maxStackHeight = max(maxStackHeight, maxi)
|
||||||
return stackBounds[pos]
|
}
|
||||||
|
getStackMaxMin := func(pos int) (ok bool, min, max int) {
|
||||||
|
maxi := stackBoundsMax[pos]
|
||||||
|
if maxi == 0 { // Not yet set
|
||||||
|
return false, 0, 0
|
||||||
|
}
|
||||||
|
return true, int(stackBoundsMin[pos]), int(maxi - 1)
|
||||||
}
|
}
|
||||||
// set the initial stack bounds
|
// set the initial stack bounds
|
||||||
setBounds(0, int(metadata[section].inputs), int(metadata[section].inputs))
|
setBounds(0, int(metadata[section].inputs), int(metadata[section].inputs))
|
||||||
|
|
@ -29,97 +44,89 @@ func validateControlFlow(code []byte, section int, metadata []*functionMetadata,
|
||||||
qualifiedExit := false
|
qualifiedExit := false
|
||||||
for pos := 0; pos < len(code); pos++ {
|
for pos := 0; pos < len(code); pos++ {
|
||||||
op := OpCode(code[pos])
|
op := OpCode(code[pos])
|
||||||
currentBounds := stackBounds[pos]
|
ok, currentStackMin, currentStackMax := getStackMaxMin(pos)
|
||||||
if currentBounds == nil {
|
if !ok {
|
||||||
if debugging {
|
if debugging {
|
||||||
fmt.Printf("Stack bounds not set: %v at %v \n", op, pos)
|
fmt.Printf("Stack bounds not set: %v at %v \n", op, pos)
|
||||||
}
|
}
|
||||||
return 0, ErrUnreachableCode
|
return 0, ErrUnreachableCode
|
||||||
}
|
}
|
||||||
|
|
||||||
if debugging {
|
if debugging {
|
||||||
fmt.Println(pos, op, maxStackHeight, currentBounds)
|
fmt.Println(pos, op, maxStackHeight, currentStackMin, currentStackMax)
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
|
||||||
currentStackMax = currentBounds.max
|
|
||||||
currentStackMin = currentBounds.min
|
|
||||||
)
|
|
||||||
|
|
||||||
switch op {
|
switch op {
|
||||||
case CALLF:
|
case CALLF:
|
||||||
arg, _ := parseUint16(code[pos+1:])
|
arg, _ := parseUint16(code[pos+1:])
|
||||||
newSection := metadata[arg]
|
newSection := metadata[arg]
|
||||||
if want, have := int(newSection.inputs), currentBounds.min; want > have {
|
if want, have := int(newSection.inputs), currentStackMin; want > have {
|
||||||
return 0, fmt.Errorf("%w: at pos %d", ErrStackUnderflow{stackLen: have, required: want}, pos)
|
return 0, fmt.Errorf("%w: at pos %d", ErrStackUnderflow{stackLen: have, required: want}, pos)
|
||||||
}
|
}
|
||||||
if have, limit := currentBounds.max+int(newSection.maxStackHeight)-int(newSection.inputs), int(params.StackLimit); have > limit {
|
if have, limit := currentStackMax+int(newSection.maxStackHeight)-int(newSection.inputs), int(params.StackLimit); have > limit {
|
||||||
return 0, fmt.Errorf("%w: at pos %d", ErrStackOverflow{stackLen: have, limit: limit}, pos)
|
return 0, fmt.Errorf("%w: at pos %d", ErrStackOverflow{stackLen: have, limit: limit}, pos)
|
||||||
}
|
}
|
||||||
change := int(newSection.outputs) - int(newSection.inputs)
|
change := int(newSection.outputs) - int(newSection.inputs)
|
||||||
currentStackMax += change
|
currentStackMax += change
|
||||||
currentStackMin += change
|
currentStackMin += change
|
||||||
case RETF:
|
case RETF:
|
||||||
if currentBounds.max != currentBounds.min {
|
if currentStackMax != currentStackMin {
|
||||||
return 0, fmt.Errorf("%w: max %d, min %d, at pos %d", ErrInvalidOutputs, currentBounds.max, currentBounds.min, pos)
|
return 0, fmt.Errorf("%w: max %d, min %d, at pos %d", ErrInvalidOutputs, currentStackMax, currentStackMin, pos)
|
||||||
}
|
}
|
||||||
have := int(metadata[section].outputs)
|
have := int(metadata[section].outputs)
|
||||||
if have >= maxOutputItems {
|
if have >= maxOutputItems {
|
||||||
return 0, fmt.Errorf("%w: at pos %d", ErrInvalidNonReturningFlag, pos)
|
return 0, fmt.Errorf("%w: at pos %d", ErrInvalidNonReturningFlag, pos)
|
||||||
}
|
}
|
||||||
if want := currentBounds.min; have != want {
|
if want := currentStackMin; have != want {
|
||||||
return 0, fmt.Errorf("%w: have %d, want %d, at pos %d", ErrInvalidOutputs, have, want, pos)
|
return 0, fmt.Errorf("%w: have %d, want %d, at pos %d", ErrInvalidOutputs, have, want, pos)
|
||||||
}
|
}
|
||||||
qualifiedExit = true
|
qualifiedExit = true
|
||||||
case JUMPF:
|
case JUMPF:
|
||||||
arg, _ := parseUint16(code[pos+1:])
|
arg, _ := parseUint16(code[pos+1:])
|
||||||
newSection := metadata[arg]
|
newSection := metadata[arg]
|
||||||
if have, limit := currentBounds.max+int(newSection.maxStackHeight)-int(newSection.inputs), int(params.StackLimit); have > limit {
|
if have, limit := currentStackMax+int(newSection.maxStackHeight)-int(newSection.inputs), int(params.StackLimit); have > limit {
|
||||||
return 0, fmt.Errorf("%w: at pos %d", ErrStackOverflow{stackLen: have, limit: limit}, pos)
|
return 0, fmt.Errorf("%w: at pos %d", ErrStackOverflow{stackLen: have, limit: limit}, pos)
|
||||||
}
|
}
|
||||||
if newSection.outputs == 0x80 {
|
if newSection.outputs == 0x80 {
|
||||||
if want, have := int(newSection.inputs), currentBounds.min; want > have {
|
if want, have := int(newSection.inputs), currentStackMin; want > have {
|
||||||
return 0, fmt.Errorf("%w: at pos %d", ErrStackUnderflow{stackLen: have, required: want}, pos)
|
return 0, fmt.Errorf("%w: at pos %d", ErrStackUnderflow{stackLen: have, required: want}, pos)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if currentBounds.max != currentBounds.min {
|
if currentStackMax != currentStackMin {
|
||||||
return 0, fmt.Errorf("%w: max %d, min %d, at pos %d", ErrInvalidOutputs, currentBounds.max, currentBounds.min, pos)
|
return 0, fmt.Errorf("%w: max %d, min %d, at pos %d", ErrInvalidOutputs, currentStackMax, currentStackMin, pos)
|
||||||
}
|
}
|
||||||
if have, want := currentBounds.max, int(metadata[section].outputs)+int(newSection.inputs)-int(newSection.outputs); have != want {
|
if have, want := currentStackMax, int(metadata[section].outputs)+int(newSection.inputs)-int(newSection.outputs); have != want {
|
||||||
return 0, fmt.Errorf("%w: at pos %d", ErrInvalidOutputs, pos)
|
return 0, fmt.Errorf("%w: at pos %d", ErrInvalidOutputs, pos)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
qualifiedExit = qualifiedExit || newSection.outputs < maxOutputItems
|
qualifiedExit = qualifiedExit || newSection.outputs < maxOutputItems
|
||||||
case DUPN:
|
case DUPN:
|
||||||
arg := int(code[pos+1]) + 1
|
arg := int(code[pos+1]) + 1
|
||||||
if want, have := arg, currentBounds.min; want > have {
|
if want, have := arg, currentStackMin; want > have {
|
||||||
return 0, fmt.Errorf("%w: at pos %d", ErrStackUnderflow{stackLen: have, required: want}, pos)
|
return 0, fmt.Errorf("%w: at pos %d", ErrStackUnderflow{stackLen: have, required: want}, pos)
|
||||||
}
|
}
|
||||||
case SWAPN:
|
case SWAPN:
|
||||||
arg := int(code[pos+1]) + 1
|
arg := int(code[pos+1]) + 1
|
||||||
if want, have := arg+1, currentBounds.min; want > have {
|
if want, have := arg+1, currentStackMin; want > have {
|
||||||
return 0, fmt.Errorf("%w: at pos %d", ErrStackUnderflow{stackLen: have, required: want}, pos)
|
return 0, fmt.Errorf("%w: at pos %d", ErrStackUnderflow{stackLen: have, required: want}, pos)
|
||||||
}
|
}
|
||||||
case EXCHANGE:
|
case EXCHANGE:
|
||||||
arg := int(code[pos+1])
|
arg := int(code[pos+1])
|
||||||
n := arg>>4 + 1
|
n := arg>>4 + 1
|
||||||
m := arg&0x0f + 1
|
m := arg&0x0f + 1
|
||||||
if want, have := n+m+1, currentBounds.min; want > have {
|
if want, have := n+m+1, currentStackMin; want > have {
|
||||||
return 0, fmt.Errorf("%w: at pos %d", ErrStackUnderflow{stackLen: have, required: want}, pos)
|
return 0, fmt.Errorf("%w: at pos %d", ErrStackUnderflow{stackLen: have, required: want}, pos)
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
if want, have := jt[op].minStack, currentBounds.min; want > have {
|
if want, have := jt[op].minStack, currentStackMin; want > have {
|
||||||
return 0, fmt.Errorf("%w: at pos %d", ErrStackUnderflow{stackLen: have, required: want}, pos)
|
return 0, fmt.Errorf("%w: at pos %d", ErrStackUnderflow{stackLen: have, required: want}, pos)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !terminals[op] && op != CALLF {
|
if !terminals[op] && op != CALLF {
|
||||||
change := int(params.StackLimit) - jt[op].maxStack
|
change := int(params.StackLimit) - jt[op].maxStack
|
||||||
currentStackMax += change
|
currentStackMax += change
|
||||||
currentStackMin += change
|
currentStackMin += change
|
||||||
}
|
}
|
||||||
|
next = next[:0]
|
||||||
var next []int
|
|
||||||
switch op {
|
switch op {
|
||||||
case RJUMP:
|
case RJUMP:
|
||||||
nextPos := pos + 2 + parseInt16(code[pos+1:])
|
nextPos := pos + 2 + parseInt16(code[pos+1:])
|
||||||
|
|
@ -127,19 +134,20 @@ func validateControlFlow(code []byte, section int, metadata []*functionMetadata,
|
||||||
// We set the stack bounds of the destination
|
// We set the stack bounds of the destination
|
||||||
// and skip the argument, only for RJUMP, all other opcodes are handled later
|
// and skip the argument, only for RJUMP, all other opcodes are handled later
|
||||||
if nextPos+1 < pos {
|
if nextPos+1 < pos {
|
||||||
nextBounds, ok := stackBounds[nextPos+1]
|
ok, nextMin, nextMax := getStackMaxMin(nextPos + 1)
|
||||||
if !ok {
|
if !ok {
|
||||||
return 0, ErrInvalidBackwardJump
|
return 0, ErrInvalidBackwardJump
|
||||||
}
|
}
|
||||||
if nextBounds.max != currentStackMax || nextBounds.min != currentStackMin {
|
if nextMax != currentStackMax || nextMin != currentStackMin {
|
||||||
return 0, ErrInvalidMaxStackHeight
|
return 0, ErrInvalidMaxStackHeight
|
||||||
}
|
}
|
||||||
}
|
|
||||||
nextBounds, ok := stackBounds[nextPos+1]
|
|
||||||
if !ok {
|
|
||||||
setBounds(nextPos+1, currentStackMin, currentStackMax)
|
|
||||||
} else {
|
} else {
|
||||||
setBounds(nextPos+1, min(nextBounds.min, currentStackMin), max(nextBounds.max, currentStackMax))
|
ok, nextMin, nextMax := getStackMaxMin(nextPos + 1)
|
||||||
|
if !ok {
|
||||||
|
setBounds(nextPos+1, currentStackMin, currentStackMax)
|
||||||
|
} else {
|
||||||
|
setBounds(nextPos+1, min(nextMin, currentStackMin), max(nextMax, currentStackMax))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
case RJUMPI:
|
case RJUMPI:
|
||||||
arg := parseInt16(code[pos+1:])
|
arg := parseInt16(code[pos+1:])
|
||||||
|
|
@ -172,23 +180,23 @@ func validateControlFlow(code []byte, section int, metadata []*functionMetadata,
|
||||||
}
|
}
|
||||||
if nextPC > pos {
|
if nextPC > pos {
|
||||||
// target reached via forward jump or seq flow
|
// target reached via forward jump or seq flow
|
||||||
nextBounds, ok := stackBounds[nextPC]
|
ok, nextMin, nextMax := getStackMaxMin(nextPC)
|
||||||
if !ok {
|
if !ok {
|
||||||
setBounds(nextPC, currentStackMin, currentStackMax)
|
setBounds(nextPC, currentStackMin, currentStackMax)
|
||||||
} else {
|
} else {
|
||||||
setBounds(nextPC, min(nextBounds.min, currentStackMin), max(nextBounds.max, currentStackMax))
|
setBounds(nextPC, min(nextMin, currentStackMin), max(nextMax, currentStackMax))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// target reached via backwards jump
|
// target reached via backwards jump
|
||||||
nextBounds, ok := stackBounds[nextPC]
|
ok, nextMin, nextMax := getStackMaxMin(nextPC)
|
||||||
if !ok {
|
if !ok {
|
||||||
return 0, ErrInvalidBackwardJump
|
return 0, ErrInvalidBackwardJump
|
||||||
}
|
}
|
||||||
if currentStackMax != nextBounds.max {
|
if currentStackMax != nextMax {
|
||||||
return 0, fmt.Errorf("%w want %d as current max got %d at pos %d,", ErrInvalidBackwardJump, currentStackMax, nextBounds.max, pos)
|
return 0, fmt.Errorf("%w want %d as current max got %d at pos %d,", ErrInvalidBackwardJump, currentStackMax, nextMax, pos)
|
||||||
}
|
}
|
||||||
if currentStackMin != nextBounds.min {
|
if currentStackMin != nextMin {
|
||||||
return 0, fmt.Errorf("%w want %d as current min got %d at pos %d,", ErrInvalidBackwardJump, currentStackMin, nextBounds.min, pos)
|
return 0, fmt.Errorf("%w want %d as current min got %d at pos %d,", ErrInvalidBackwardJump, currentStackMin, nextMin, pos)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -199,7 +207,6 @@ func validateControlFlow(code []byte, section int, metadata []*functionMetadata,
|
||||||
} else {
|
} else {
|
||||||
pos = next[0]
|
pos = next[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
if qualifiedExit != (metadata[section].outputs < maxOutputItems) {
|
if qualifiedExit != (metadata[section].outputs < maxOutputItems) {
|
||||||
return 0, fmt.Errorf("%w no RETF or qualified JUMPF", ErrInvalidNonReturningFlag)
|
return 0, fmt.Errorf("%w no RETF or qualified JUMPF", ErrInvalidNonReturningFlag)
|
||||||
|
|
@ -213,5 +220,5 @@ func validateControlFlow(code []byte, section int, metadata []*functionMetadata,
|
||||||
}
|
}
|
||||||
return 0, fmt.Errorf("%w in code section %d: have %d, want %d", ErrInvalidMaxStackHeight, section, maxStackHeight, metadata[section].maxStackHeight)
|
return 0, fmt.Errorf("%w in code section %d: have %d, want %d", ErrInvalidMaxStackHeight, section, maxStackHeight, metadata[section].maxStackHeight)
|
||||||
}
|
}
|
||||||
return len(stackBounds), nil
|
return visitCount, nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -247,9 +247,9 @@ func TestValidateCode(t *testing.T) {
|
||||||
},
|
},
|
||||||
} {
|
} {
|
||||||
container := &Container{
|
container := &Container{
|
||||||
types: test.metadata,
|
types: test.metadata,
|
||||||
data: make([]byte, 0),
|
data: make([]byte, 0),
|
||||||
sections: make([]*Container, 0),
|
subContainers: make([]*Container, 0),
|
||||||
}
|
}
|
||||||
_, err := validateCode(test.code, test.section, container, &pragueEOFInstructionSet, false)
|
_, err := validateCode(test.code, test.section, container, &pragueEOFInstructionSet, false)
|
||||||
if !errors.Is(err, test.err) {
|
if !errors.Is(err, test.err) {
|
||||||
|
|
@ -269,9 +269,9 @@ func BenchmarkRJUMPI(b *testing.B) {
|
||||||
}
|
}
|
||||||
code = append(code, byte(STOP))
|
code = append(code, byte(STOP))
|
||||||
container := &Container{
|
container := &Container{
|
||||||
types: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 1}},
|
types: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 1}},
|
||||||
data: make([]byte, 0),
|
data: make([]byte, 0),
|
||||||
sections: make([]*Container, 0),
|
subContainers: make([]*Container, 0),
|
||||||
}
|
}
|
||||||
b.ResetTimer()
|
b.ResetTimer()
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
|
|
@ -299,9 +299,9 @@ func BenchmarkRJUMPV(b *testing.B) {
|
||||||
code = append(code, byte(PUSH0))
|
code = append(code, byte(PUSH0))
|
||||||
code = append(code, byte(STOP))
|
code = append(code, byte(STOP))
|
||||||
container := &Container{
|
container := &Container{
|
||||||
types: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 1}},
|
types: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 1}},
|
||||||
data: make([]byte, 0),
|
data: make([]byte, 0),
|
||||||
sections: make([]*Container, 0),
|
subContainers: make([]*Container, 0),
|
||||||
}
|
}
|
||||||
b.ResetTimer()
|
b.ResetTimer()
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
|
|
@ -322,7 +322,7 @@ func BenchmarkEOFValidation(b *testing.B) {
|
||||||
}
|
}
|
||||||
code = append(code, byte(STOP))
|
code = append(code, byte(STOP))
|
||||||
// First container
|
// First container
|
||||||
container.code = append(container.code, code)
|
container.codeSections = append(container.codeSections, code)
|
||||||
container.types = append(container.types, &functionMetadata{inputs: 0, outputs: 0x80, maxStackHeight: 0})
|
container.types = append(container.types, &functionMetadata{inputs: 0, outputs: 0x80, maxStackHeight: 0})
|
||||||
|
|
||||||
inner := []byte{
|
inner := []byte{
|
||||||
|
|
@ -330,12 +330,12 @@ func BenchmarkEOFValidation(b *testing.B) {
|
||||||
}
|
}
|
||||||
|
|
||||||
for i := 0; i < 1023; i++ {
|
for i := 0; i < 1023; i++ {
|
||||||
container.code = append(container.code, inner)
|
container.codeSections = append(container.codeSections, inner)
|
||||||
container.types = append(container.types, &functionMetadata{inputs: 0, outputs: 0x80, maxStackHeight: 0})
|
container.types = append(container.types, &functionMetadata{inputs: 0, outputs: 0x80, maxStackHeight: 0})
|
||||||
}
|
}
|
||||||
|
|
||||||
for i := 0; i < 12; i++ {
|
for i := 0; i < 12; i++ {
|
||||||
container.code[i+1] = code
|
container.codeSections[i+1] = code
|
||||||
}
|
}
|
||||||
|
|
||||||
bin := container.MarshalBinary()
|
bin := container.MarshalBinary()
|
||||||
|
|
@ -365,7 +365,7 @@ func BenchmarkEOFValidation2(b *testing.B) {
|
||||||
}
|
}
|
||||||
code = append(code, byte(STOP))
|
code = append(code, byte(STOP))
|
||||||
// First container
|
// First container
|
||||||
container.code = append(container.code, code)
|
container.codeSections = append(container.codeSections, code)
|
||||||
container.types = append(container.types, &functionMetadata{inputs: 0, outputs: 0x80, maxStackHeight: 0})
|
container.types = append(container.types, &functionMetadata{inputs: 0, outputs: 0x80, maxStackHeight: 0})
|
||||||
|
|
||||||
inner := []byte{
|
inner := []byte{
|
||||||
|
|
@ -384,7 +384,7 @@ func BenchmarkEOFValidation2(b *testing.B) {
|
||||||
}
|
}
|
||||||
|
|
||||||
for i := 0; i < 1023; i++ {
|
for i := 0; i < 1023; i++ {
|
||||||
container.code = append(container.code, inner)
|
container.codeSections = append(container.codeSections, inner)
|
||||||
container.types = append(container.types, &functionMetadata{inputs: 0, outputs: 0x80, maxStackHeight: 0})
|
container.types = append(container.types, &functionMetadata{inputs: 0, outputs: 0x80, maxStackHeight: 0})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -425,15 +425,15 @@ func BenchmarkEOFValidation3(b *testing.B) {
|
||||||
}
|
}
|
||||||
code = append(code, byte(STOP))
|
code = append(code, byte(STOP))
|
||||||
// First container
|
// First container
|
||||||
container.code = append(container.code, code)
|
container.codeSections = append(container.codeSections, code)
|
||||||
container.types = append(container.types, &functionMetadata{inputs: 0, outputs: 0x80, maxStackHeight: 1})
|
container.types = append(container.types, &functionMetadata{inputs: 0, outputs: 0x80, maxStackHeight: 1})
|
||||||
|
|
||||||
for i := 0; i < 1023; i++ {
|
for i := 0; i < 1023; i++ {
|
||||||
container.code = append(container.code, []byte{byte(RJUMP), 0x00, 0x00, byte(JUMPF), 0x00, 0x00})
|
container.codeSections = append(container.codeSections, []byte{byte(RJUMP), 0x00, 0x00, byte(JUMPF), 0x00, 0x00})
|
||||||
container.types = append(container.types, &functionMetadata{inputs: 0, outputs: 0x80, maxStackHeight: 0})
|
container.types = append(container.types, &functionMetadata{inputs: 0, outputs: 0x80, maxStackHeight: 0})
|
||||||
}
|
}
|
||||||
for i := 0; i < 65; i++ {
|
for i := 0; i < 65; i++ {
|
||||||
container.code[i+1] = append(snippet, byte(STOP))
|
container.codeSections[i+1] = append(snippet, byte(STOP))
|
||||||
container.types[i+1] = &functionMetadata{inputs: 0, outputs: 0x80, maxStackHeight: 1}
|
container.types[i+1] = &functionMetadata{inputs: 0, outputs: 0x80, maxStackHeight: 1}
|
||||||
}
|
}
|
||||||
bin := container.MarshalBinary()
|
bin := container.MarshalBinary()
|
||||||
|
|
@ -468,9 +468,9 @@ func BenchmarkRJUMPI_2(b *testing.B) {
|
||||||
}
|
}
|
||||||
code = append(code, byte(STOP))
|
code = append(code, byte(STOP))
|
||||||
container := &Container{
|
container := &Container{
|
||||||
types: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 1}},
|
types: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 1}},
|
||||||
data: make([]byte, 0),
|
data: make([]byte, 0),
|
||||||
sections: make([]*Container, 0),
|
subContainers: make([]*Container, 0),
|
||||||
}
|
}
|
||||||
b.ResetTimer()
|
b.ResetTimer()
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue