core/vm/program: minor UX tweaks

This commit is contained in:
Martin Holst Swende 2024-11-04 17:00:57 +01:00
parent 6d7e9b9b82
commit 55edee5565
No known key found for this signature in database
GPG key ID: 683B438C05A5DDF0
3 changed files with 178 additions and 105 deletions

View file

@ -127,14 +127,14 @@ func (p *Program) Push0() *Program {
return p.Op(vm.PUSH0) return p.Op(vm.PUSH0)
} }
// Bytecode returns the Program bytecode // Bytes returns the Program bytecode
func (p *Program) Bytecode() []byte { func (p *Program) Bytes() []byte {
return p.code return p.code
} }
// Hex returns the Program bytecode as a hex string // Hex returns the Program bytecode as a hex string
func (p *Program) Hex() string { func (p *Program) Hex() string {
return fmt.Sprintf("%02x", p.Bytecode()) return fmt.Sprintf("%02x", p.Bytes())
} }
// ExtcodeCopy performsa an extcodecopy invocation // ExtcodeCopy performsa an extcodecopy invocation
@ -280,6 +280,29 @@ func (p *Program) Mstore(data []byte, memStart uint32) *Program {
return p return p
} }
// MstorePadded stores the provided data (into the memory area starting at memStart).
// If data does not align on 32 bytes, it will be LHS-padded.
// For example, providing data 0x1122, it will do a PUSH2:
// PUSH2 0x1122, resulting in
// stack: 0x0000000000000000000000000000000000000000000000000000000000001122
// followed by MSTORE(0,0)
// And thus, the resulting memory will be
// [ 0000000000000000000000000000000000000000000000000000000000001122 ]
func (p *Program) MstorePadded(data []byte, memStart uint32) *Program {
var idx = 0
// We need to store it in chunks of 32 bytes
for ; idx < len(data); idx += 32 {
end := min(len(data), idx+32)
chunk := data[idx:end]
// push the value
p.Push(chunk)
// push the memory index
p.Push(uint32(idx) + memStart)
p.Op(vm.MSTORE)
}
return p
}
// MemToStorage copies the given memory area into SSTORE slots, // MemToStorage copies the given memory area into SSTORE slots,
// It expects data to be aligned to 32 byte, and does not zero out // It expects data to be aligned to 32 byte, and does not zero out
// remainders if some data is not // remainders if some data is not

View file

@ -143,7 +143,7 @@ func TestCreateAndCall(t *testing.T) {
deployed.Return(0, 32) deployed.Return(0, 32)
// Pack them // Pack them
ctor.ReturnData(deployed.Bytecode()) ctor.ReturnData(deployed.Bytes())
// Verify constructor + runtime code // Verify constructor + runtime code
{ {
want := "6005600055606060005360006001536054600253606060035360006004536052600553606060065360206007536060600853600060095360f3600a53600b6000f3" want := "6005600055606060005360006001536054600253606060035360006004536052600553606060065360206007536060600853600060095360f3600a53600b6000f3"
@ -155,21 +155,134 @@ func TestCreateAndCall(t *testing.T) {
func TestCreate2Call(t *testing.T) { func TestCreate2Call(t *testing.T) {
// Some runtime code // Some runtime code
runtime := New().Ops(vm.ADDRESS, vm.SELFDESTRUCT).Bytecode() runtime := New().Ops(vm.ADDRESS, vm.SELFDESTRUCT).Bytes()
want := common.FromHex("0x30ff") want := common.FromHex("0x30ff")
if !bytes.Equal(want, runtime) { if !bytes.Equal(want, runtime) {
t.Fatalf("runtime code error\nwant: %x\nhave: %x\n", want, runtime) t.Fatalf("runtime code error\nwant: %x\nhave: %x\n", want, runtime)
} }
// A constructor returning the runtime code // A constructor returning the runtime code
initcode := New().ReturnData(runtime).Bytecode() initcode := New().ReturnData(runtime).Bytes()
want = common.FromHex("603060005360ff60015360026000f3") want = common.FromHex("603060005360ff60015360026000f3")
if !bytes.Equal(want, initcode) { if !bytes.Equal(want, initcode) {
t.Fatalf("initcode error\nwant: %x\nhave: %x\n", want, initcode) t.Fatalf("initcode error\nwant: %x\nhave: %x\n", want, initcode)
} }
// A factory invoking the constructor // A factory invoking the constructor
outer := New().Create2AndCall(initcode, nil).Bytecode() outer := New().Create2AndCall(initcode, nil).Bytes()
want = common.FromHex("60606000536030600153606060025360006003536053600453606060055360ff6006536060600753600160085360536009536060600a536002600b536060600c536000600d5360f3600e536000600f60006000f560006000600060006000855af15050") want = common.FromHex("60606000536030600153606060025360006003536053600453606060055360ff6006536060600753600160085360536009536060600a536002600b536060600c536000600d5360f3600e536000600f60006000f560006000600060006000855af15050")
if !bytes.Equal(want, outer) { if !bytes.Equal(want, outer) {
t.Fatalf("factory error\nwant: %x\nhave: %x\n", want, outer) t.Fatalf("factory error\nwant: %x\nhave: %x\n", want, outer)
} }
} }
func TestGenerator(t *testing.T) {
for i, tc := range []struct {
want []byte
haveFn func() []byte
}{
{ // CREATE
want: []byte{
// Store initcode in memory at 0x00 (5 bytes left-padded to 32 bytes)
byte(vm.PUSH5),
// Init code: PUSH1 0, PUSH1 0, RETURN (3 steps)
byte(vm.PUSH1), 0, byte(vm.PUSH1), 0, byte(vm.RETURN),
byte(vm.PUSH1), 0,
byte(vm.MSTORE),
// length, offset, value
byte(vm.PUSH1), 5, byte(vm.PUSH1), 27, byte(vm.PUSH1), 0,
byte(vm.CREATE),
byte(vm.POP),
},
haveFn: func() []byte {
initcode := New().Return(0, 0).Bytes()
return New().MstorePadded(initcode, 0).
Push(len(initcode)). // length
Push(32 - len(initcode)). // offset
Push(0). // value
Op(vm.CREATE).
Op(vm.POP).Bytes()
},
},
{ // CREATE2
want: []byte{
// Store initcode in memory at 0x00 (5 bytes left-padded to 32 bytes)
byte(vm.PUSH5),
// Init code: PUSH1 0, PUSH1 0, RETURN (3 steps)
byte(vm.PUSH1), 0, byte(vm.PUSH1), 0, byte(vm.RETURN),
byte(vm.PUSH1), 0,
byte(vm.MSTORE),
// salt, length, offset, value
byte(vm.PUSH1), 1, byte(vm.PUSH1), 5, byte(vm.PUSH1), 27, byte(vm.PUSH1), 0,
byte(vm.CREATE2),
byte(vm.POP),
},
haveFn: func() []byte {
initcode := New().Return(0, 0).Bytes()
return New().MstorePadded(initcode, 0).
Push(1). // salt
Push(len(initcode)). // length
Push(32 - len(initcode)). // offset
Push(0). // value
Op(vm.CREATE2).
Op(vm.POP).Bytes()
},
},
{ // CALL
want: []byte{
// outsize, outoffset, insize, inoffset
byte(vm.PUSH1), 0, byte(vm.DUP1), byte(vm.DUP1), byte(vm.DUP1),
byte(vm.DUP1), // value
byte(vm.PUSH1), 0xbb, //address
byte(vm.GAS), // gas
byte(vm.CALL),
byte(vm.POP),
},
haveFn: func() []byte {
return New().Call(nil, 0xbb, 0, 0, 0, 0, 0).Op(vm.POP).Bytes()
},
},
{ // CALLCODE
want: []byte{
// outsize, outoffset, insize, inoffset
byte(vm.PUSH1), 0, byte(vm.DUP1), byte(vm.DUP1), byte(vm.DUP1),
byte(vm.PUSH1), 0, // value
byte(vm.PUSH1), 0xcc, //address
byte(vm.GAS), // gas
byte(vm.CALLCODE),
byte(vm.POP),
},
haveFn: func() []byte {
return New().CallCode(nil, 0xcc, 0, 0, 0, 0, 0).Op(vm.POP).Bytes()
},
},
{ // STATICCALL
want: []byte{
// outsize, outoffset, insize, inoffset
byte(vm.PUSH1), 0, byte(vm.DUP1), byte(vm.DUP1), byte(vm.DUP1),
byte(vm.PUSH1), 0xdd, //address
byte(vm.GAS), // gas
byte(vm.STATICCALL),
byte(vm.POP),
},
haveFn: func() []byte {
return New().StaticCall(nil, 0xdd, 0, 0, 0, 0).Op(vm.POP).Bytes()
},
},
{ // DELEGATECALL
want: []byte{
// outsize, outoffset, insize, inoffset
byte(vm.PUSH1), 0, byte(vm.DUP1), byte(vm.DUP1), byte(vm.DUP1),
byte(vm.PUSH1), 0xee, //address
byte(vm.GAS), // gas
byte(vm.DELEGATECALL),
byte(vm.POP),
},
haveFn: func() []byte {
return New().DelegateCall(nil, 0xee, 0, 0, 0, 0).Op(vm.POP).Bytes()
},
},
} {
if have := tc.haveFn(); !bytes.Equal(have, tc.want) {
t.Fatalf("test %d error\nhave: %x\nwant: %x\n", i, have, tc.want)
}
}
}

View file

@ -442,22 +442,22 @@ func BenchmarkSimpleLoop(b *testing.B) {
// Call identity, and pop return value // Call identity, and pop return value
staticCallIdentity := p. staticCallIdentity := p.
StaticCall(nil, 0x4, 0, 0, 0, 0). StaticCall(nil, 0x4, 0, 0, 0, 0).
Op(vm.POP).Jump(lbl).Bytecode() // pop return value and jump to label Op(vm.POP).Jump(lbl).Bytes() // pop return value and jump to label
p, lbl = program.New().Jumpdest() p, lbl = program.New().Jumpdest()
callIdentity := p. callIdentity := p.
Call(nil, 0x4, 0, 0, 0, 0, 0). Call(nil, 0x4, 0, 0, 0, 0, 0).
Op(vm.POP).Jump(lbl).Bytecode() // pop return value and jump to label Op(vm.POP).Jump(lbl).Bytes() // pop return value and jump to label
p, lbl = program.New().Jumpdest() p, lbl = program.New().Jumpdest()
callInexistant := p. callInexistant := p.
Call(nil, 0xff, 0, 0, 0, 0, 0). Call(nil, 0xff, 0, 0, 0, 0, 0).
Op(vm.POP).Jump(lbl).Bytecode() // pop return value and jump to label Op(vm.POP).Jump(lbl).Bytes() // pop return value and jump to label
p, lbl = program.New().Jumpdest() p, lbl = program.New().Jumpdest()
callEOA := p. callEOA := p.
Call(nil, 0xE0, 0, 0, 0, 0, 0). // call addr of EOA Call(nil, 0xE0, 0, 0, 0, 0, 0). // call addr of EOA
Op(vm.POP).Jump(lbl).Bytecode() // pop return value and jump to label Op(vm.POP).Jump(lbl).Bytes() // pop return value and jump to label
p, lbl = program.New().Jumpdest() p, lbl = program.New().Jumpdest()
// Push as if we were making call, then pop it off again, and loop // Push as if we were making call, then pop it off again, and loop
@ -465,19 +465,19 @@ func BenchmarkSimpleLoop(b *testing.B) {
Ops(vm.DUP1, vm.DUP1, vm.DUP1). Ops(vm.DUP1, vm.DUP1, vm.DUP1).
Push(0x4). Push(0x4).
Ops(vm.GAS, vm.POP, vm.POP, vm.POP, vm.POP, vm.POP, vm.POP). Ops(vm.GAS, vm.POP, vm.POP, vm.POP, vm.POP, vm.POP, vm.POP).
Jump(lbl).Bytecode() Jump(lbl).Bytes()
p, lbl = program.New().Jumpdest() p, lbl = program.New().Jumpdest()
loopingCode2 := p. loopingCode2 := p.
Push(0x01020304).Push(uint64(0x0102030405)). Push(0x01020304).Push(uint64(0x0102030405)).
Ops(vm.POP, vm.POP). Ops(vm.POP, vm.POP).
Op(vm.PUSH6).Append(make([]byte, 6)).Op(vm.JUMP). // Jumpdest zero expressed in 6 bytes Op(vm.PUSH6).Append(make([]byte, 6)).Op(vm.JUMP). // Jumpdest zero expressed in 6 bytes
Bytecode() Bytes()
p, lbl = program.New().Jumpdest() p, lbl = program.New().Jumpdest()
callRevertingContractWithInput := p. callRevertingContractWithInput := p.
Call(nil, 0xee, 0, 0, 0x20, 0x0, 0x0). Call(nil, 0xee, 0, 0, 0x20, 0x0, 0x0).
Op(vm.POP).Jump(lbl).Bytecode() // pop return value and jump to label Op(vm.POP).Jump(lbl).Bytes() // pop return value and jump to label
//tracer := logger.NewJSONLogger(nil, os.Stdout) //tracer := logger.NewJSONLogger(nil, os.Stdout)
//Execute(loopingCode, nil, &Config{ //Execute(loopingCode, nil, &Config{
@ -716,104 +716,49 @@ func TestRuntimeJSTracer(t *testing.T) {
this.exits++; this.exits++;
this.gasUsed = res.getGasUsed(); this.gasUsed = res.getGasUsed();
}}`} }}`}
initcode := program.New().Return(0, 0).Bytes()
tests := []struct { tests := []struct {
code []byte code []byte
// One result per tracer // One result per tracer
results []string results []string
}{ }{
{ { // CREATE
// CREATE code: program.New().MstorePadded(initcode, 0).
code: []byte{ Push(len(initcode)). // length
// Store initcode in memory at 0x00 (5 bytes left-padded to 32 bytes) Push(32 - len(initcode)). // offset
byte(vm.PUSH5), Push(0). // value
// Init code: PUSH1 0, PUSH1 0, RETURN (3 steps) Op(vm.CREATE).
byte(vm.PUSH1), 0, byte(vm.PUSH1), 0, byte(vm.RETURN), Op(vm.POP).Bytes(),
byte(vm.PUSH1), 0,
byte(vm.MSTORE),
// length, offset, value
byte(vm.PUSH1), 5, byte(vm.PUSH1), 27, byte(vm.PUSH1), 0,
byte(vm.CREATE),
byte(vm.POP),
},
results: []string{`"1,1,952853,6,12"`, `"1,1,952853,6,0"`}, results: []string{`"1,1,952853,6,12"`, `"1,1,952853,6,0"`},
}, },
{ { // CREATE2
// CREATE2 code: program.New().MstorePadded(initcode, 0).
code: []byte{ Push(1). // salt
// Store initcode in memory at 0x00 (5 bytes left-padded to 32 bytes) Push(len(initcode)). // length
byte(vm.PUSH5), Push(32 - len(initcode)). // offset
// Init code: PUSH1 0, PUSH1 0, RETURN (3 steps) Push(0). // value
byte(vm.PUSH1), 0, byte(vm.PUSH1), 0, byte(vm.RETURN), Op(vm.CREATE2).
byte(vm.PUSH1), 0, Op(vm.POP).Bytes(),
byte(vm.MSTORE),
// salt, length, offset, value
byte(vm.PUSH1), 1, byte(vm.PUSH1), 5, byte(vm.PUSH1), 27, byte(vm.PUSH1), 0,
byte(vm.CREATE2),
byte(vm.POP),
},
results: []string{`"1,1,952844,6,13"`, `"1,1,952844,6,0"`}, results: []string{`"1,1,952844,6,13"`, `"1,1,952844,6,0"`},
}, },
{ { // CALL
// CALL code: program.New().Call(nil, 0xbb, 0, 0, 0, 0, 0).Op(vm.POP).Bytes(),
code: []byte{
// outsize, outoffset, insize, inoffset
byte(vm.PUSH1), 0, byte(vm.PUSH1), 0, byte(vm.PUSH1), 0, byte(vm.PUSH1), 0,
byte(vm.PUSH1), 0, // value
byte(vm.PUSH1), 0xbb, //address
byte(vm.GAS), // gas
byte(vm.CALL),
byte(vm.POP),
},
results: []string{`"1,1,981796,6,13"`, `"1,1,981796,6,0"`}, results: []string{`"1,1,981796,6,13"`, `"1,1,981796,6,0"`},
}, },
{ { // CALLCODE
// CALLCODE code: program.New().CallCode(nil, 0xcc, 0, 0, 0, 0, 0).Op(vm.POP).Bytes(),
code: []byte{
// outsize, outoffset, insize, inoffset
byte(vm.PUSH1), 0, byte(vm.PUSH1), 0, byte(vm.PUSH1), 0, byte(vm.PUSH1), 0,
byte(vm.PUSH1), 0, // value
byte(vm.PUSH1), 0xcc, //address
byte(vm.GAS), // gas
byte(vm.CALLCODE),
byte(vm.POP),
},
results: []string{`"1,1,981796,6,13"`, `"1,1,981796,6,0"`}, results: []string{`"1,1,981796,6,13"`, `"1,1,981796,6,0"`},
}, },
{ { // STATICCALL
// STATICCALL code: program.New().StaticCall(nil, 0xdd, 0, 0, 0, 0).Op(vm.POP).Bytes(),
code: []byte{
// outsize, outoffset, insize, inoffset
byte(vm.PUSH1), 0, byte(vm.PUSH1), 0, byte(vm.PUSH1), 0, byte(vm.PUSH1), 0,
byte(vm.PUSH1), 0xdd, //address
byte(vm.GAS), // gas
byte(vm.STATICCALL),
byte(vm.POP),
},
results: []string{`"1,1,981799,6,12"`, `"1,1,981799,6,0"`}, results: []string{`"1,1,981799,6,12"`, `"1,1,981799,6,0"`},
}, },
{ { // DELEGATECALL
// DELEGATECALL code: program.New().DelegateCall(nil, 0xee, 0, 0, 0, 0).Op(vm.POP).Bytes(),
code: []byte{
// outsize, outoffset, insize, inoffset
byte(vm.PUSH1), 0, byte(vm.PUSH1), 0, byte(vm.PUSH1), 0, byte(vm.PUSH1), 0,
byte(vm.PUSH1), 0xee, //address
byte(vm.GAS), // gas
byte(vm.DELEGATECALL),
byte(vm.POP),
},
results: []string{`"1,1,981799,6,12"`, `"1,1,981799,6,0"`}, results: []string{`"1,1,981799,6,12"`, `"1,1,981799,6,0"`},
}, },
{ { // CALL self-destructing contract
// CALL self-destructing contract code: program.New().Call(nil, 0xff, 0, 0, 0, 0, 0).Op(vm.POP).Bytes(),
code: []byte{
// outsize, outoffset, insize, inoffset
byte(vm.PUSH1), 0, byte(vm.PUSH1), 0, byte(vm.PUSH1), 0, byte(vm.PUSH1), 0,
byte(vm.PUSH1), 0, // value
byte(vm.PUSH1), 0xff, //address
byte(vm.GAS), // gas
byte(vm.CALL),
byte(vm.POP),
},
results: []string{`"2,2,0,5003,12"`, `"2,2,0,5003,0"`}, results: []string{`"2,2,0,5003,12"`, `"2,2,0,5003,0"`},
}, },
} }
@ -896,16 +841,8 @@ func TestJSTracerCreateTx(t *testing.T) {
func BenchmarkTracerStepVsCallFrame(b *testing.B) { func BenchmarkTracerStepVsCallFrame(b *testing.B) {
// Simply pushes and pops some values in a loop // Simply pushes and pops some values in a loop
code := []byte{ p, lbl := program.New().Jumpdest()
byte(vm.JUMPDEST), code := p.Push(0).Push(0).Ops(vm.POP, vm.POP).Jump(lbl).Bytes()
byte(vm.PUSH1), 0,
byte(vm.PUSH1), 0,
byte(vm.POP),
byte(vm.POP),
byte(vm.PUSH1), 0, // jumpdestination
byte(vm.JUMP),
}
stepTracer := ` stepTracer := `
{ {
step: function() {}, step: function() {},