|
|
|
@ -39,6 +39,7 @@ type Program struct { |
|
|
|
|
code []byte |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// New creates a new Program
|
|
|
|
|
func New() *Program { |
|
|
|
|
return &Program{ |
|
|
|
|
code: make([]byte, 0), |
|
|
|
@ -54,21 +55,17 @@ func (p *Program) add(op byte) *Program { |
|
|
|
|
// pushBig creates a PUSHX instruction and pushes the given val.
|
|
|
|
|
// - If the val is nil, it pushes zero
|
|
|
|
|
// - If the val is bigger than 32 bytes, it panics
|
|
|
|
|
func (p *Program) pushBig(val *big.Int) { |
|
|
|
|
func (p *Program) doPush(val *uint256.Int) { |
|
|
|
|
if val == nil { |
|
|
|
|
val = big.NewInt(0) |
|
|
|
|
val = new(uint256.Int) |
|
|
|
|
} |
|
|
|
|
valBytes := val.Bytes() |
|
|
|
|
if len(valBytes) == 0 { |
|
|
|
|
valBytes = append(valBytes, 0) |
|
|
|
|
} |
|
|
|
|
bLen := len(valBytes) |
|
|
|
|
if bLen > 32 { |
|
|
|
|
panic(fmt.Sprintf("Push value too large, %d bytes", bLen)) |
|
|
|
|
} |
|
|
|
|
p.add(byte(vm.PUSH1) - 1 + byte(bLen)) |
|
|
|
|
p.Append(valBytes) |
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Append appends the given data to the code.
|
|
|
|
@ -77,67 +74,77 @@ func (p *Program) Append(data []byte) *Program { |
|
|
|
|
return p |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Op appends the given opcode
|
|
|
|
|
func (p *Program) Op(op vm.OpCode) *Program { |
|
|
|
|
return p.add(byte(op)) |
|
|
|
|
// Bytes returns the Program bytecode. OBS: This is not a copy.
|
|
|
|
|
func (p *Program) Bytes() []byte { |
|
|
|
|
return p.code |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// SetBytes sets the Program bytecode. The combination of Bytes and SetBytes means
|
|
|
|
|
// that external callers can implement missing functionality:
|
|
|
|
|
//
|
|
|
|
|
// ...
|
|
|
|
|
// prog.Push(1)
|
|
|
|
|
// code := prog.Bytes()
|
|
|
|
|
// manipulate(code)
|
|
|
|
|
// prog.SetBytes(code)
|
|
|
|
|
func (p *Program) SetBytes(code []byte) { |
|
|
|
|
p.code = code |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Hex returns the Program bytecode as a hex string.
|
|
|
|
|
func (p *Program) Hex() string { |
|
|
|
|
return fmt.Sprintf("%02x", p.Bytes()) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Ops appends the given opcodes
|
|
|
|
|
func (p *Program) Ops(ops ...vm.OpCode) *Program { |
|
|
|
|
// Op appends the given opcode(s).
|
|
|
|
|
func (p *Program) Op(ops ...vm.OpCode) *Program { |
|
|
|
|
for _, op := range ops { |
|
|
|
|
p.add(byte(op)) |
|
|
|
|
} |
|
|
|
|
return p |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Push creates a PUSHX instruction with the data provided
|
|
|
|
|
// Push creates a PUSHX instruction with the data provided. If zero is being pushed,
|
|
|
|
|
// PUSH0 will be avoided in favour of [PUSH1 0], to ensure backwards compatibility.
|
|
|
|
|
func (p *Program) Push(val any) *Program { |
|
|
|
|
switch v := val.(type) { |
|
|
|
|
case int: |
|
|
|
|
p.pushBig(new(big.Int).SetUint64(uint64(v))) |
|
|
|
|
p.doPush(new(uint256.Int).SetUint64(uint64(v))) |
|
|
|
|
case uint64: |
|
|
|
|
p.pushBig(new(big.Int).SetUint64(v)) |
|
|
|
|
p.doPush(new(uint256.Int).SetUint64(v)) |
|
|
|
|
case uint32: |
|
|
|
|
p.pushBig(new(big.Int).SetUint64(uint64(v))) |
|
|
|
|
p.doPush(new(uint256.Int).SetUint64(uint64(v))) |
|
|
|
|
case uint16: |
|
|
|
|
p.doPush(new(uint256.Int).SetUint64(uint64(v))) |
|
|
|
|
case *big.Int: |
|
|
|
|
p.pushBig(v) |
|
|
|
|
p.doPush(uint256.MustFromBig(v)) |
|
|
|
|
case *uint256.Int: |
|
|
|
|
p.pushBig(v.ToBig()) |
|
|
|
|
p.doPush(v) |
|
|
|
|
case uint256.Int: |
|
|
|
|
p.pushBig(v.ToBig()) |
|
|
|
|
p.doPush(&v) |
|
|
|
|
case []byte: |
|
|
|
|
p.pushBig(new(big.Int).SetBytes(v)) |
|
|
|
|
p.doPush(new(uint256.Int).SetBytes(v)) |
|
|
|
|
case byte: |
|
|
|
|
p.pushBig(new(big.Int).SetUint64(uint64(v))) |
|
|
|
|
p.doPush(new(uint256.Int).SetUint64(uint64(v))) |
|
|
|
|
case interface{ Bytes() []byte }: |
|
|
|
|
// Here, we jump through some hovm in order to avoid depending on
|
|
|
|
|
// Here, we jump through some hoops in order to avoid depending on
|
|
|
|
|
// go-ethereum types.Address and common.Hash, and instead use the
|
|
|
|
|
// interface. This works on both values and pointers!
|
|
|
|
|
p.pushBig(new(big.Int).SetBytes(v.Bytes())) |
|
|
|
|
p.doPush(new(uint256.Int).SetBytes(v.Bytes())) |
|
|
|
|
case nil: |
|
|
|
|
p.pushBig(nil) |
|
|
|
|
p.doPush(nil) |
|
|
|
|
default: |
|
|
|
|
panic(fmt.Sprintf("unsupported type %v", v)) |
|
|
|
|
panic(fmt.Sprintf("unsupported type %T", v)) |
|
|
|
|
} |
|
|
|
|
return p |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Push0 implements PUSH0 (0x5f)
|
|
|
|
|
// Push0 implements PUSH0 (0x5f).
|
|
|
|
|
func (p *Program) Push0() *Program { |
|
|
|
|
return p.Op(vm.PUSH0) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Bytes returns the Program bytecode
|
|
|
|
|
func (p *Program) Bytes() []byte { |
|
|
|
|
return p.code |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Hex returns the Program bytecode as a hex string
|
|
|
|
|
func (p *Program) Hex() string { |
|
|
|
|
return fmt.Sprintf("%02x", p.Bytes()) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// ExtcodeCopy performsa an extcodecopy invocation
|
|
|
|
|
// ExtcodeCopy performs an extcodecopy invocation.
|
|
|
|
|
func (p *Program) ExtcodeCopy(address, memOffset, codeOffset, length any) *Program { |
|
|
|
|
p.Push(length) |
|
|
|
|
p.Push(codeOffset) |
|
|
|
@ -148,9 +155,9 @@ func (p *Program) ExtcodeCopy(address, memOffset, codeOffset, length any) *Progr |
|
|
|
|
|
|
|
|
|
// Call is a convenience function to make a call. If 'gas' is nil, the opcode GAS will
|
|
|
|
|
// be used to provide all gas.
|
|
|
|
|
func (p *Program) Call(gas *big.Int, address, value, inOffset, inSize, outOffset, outSize any) *Program { |
|
|
|
|
func (p *Program) Call(gas *uint256.Int, address, value, inOffset, inSize, outOffset, outSize any) *Program { |
|
|
|
|
if outOffset == outSize && inSize == outSize && inOffset == outSize && value == outSize { |
|
|
|
|
p.Push(outSize).Ops(vm.DUP1, vm.DUP1, vm.DUP1, vm.DUP1) |
|
|
|
|
p.Push(outSize).Op(vm.DUP1, vm.DUP1, vm.DUP1, vm.DUP1) |
|
|
|
|
} else { |
|
|
|
|
p.Push(outSize).Push(outOffset).Push(inSize).Push(inOffset).Push(value) |
|
|
|
|
} |
|
|
|
@ -158,16 +165,16 @@ func (p *Program) Call(gas *big.Int, address, value, inOffset, inSize, outOffset |
|
|
|
|
if gas == nil { |
|
|
|
|
p.Op(vm.GAS) |
|
|
|
|
} else { |
|
|
|
|
p.pushBig(gas) |
|
|
|
|
p.doPush(gas) |
|
|
|
|
} |
|
|
|
|
return p.Op(vm.CALL) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// DelegateCall is a convenience function to make a delegatecall. If 'gas' is nil, the opcode GAS will
|
|
|
|
|
// be used to provide all gas.
|
|
|
|
|
func (p *Program) DelegateCall(gas *big.Int, address, inOffset, inSize, outOffset, outSize any) *Program { |
|
|
|
|
func (p *Program) DelegateCall(gas *uint256.Int, address, inOffset, inSize, outOffset, outSize any) *Program { |
|
|
|
|
if outOffset == outSize && inSize == outSize && inOffset == outSize { |
|
|
|
|
p.Push(outSize).Ops(vm.DUP1, vm.DUP1, vm.DUP1) |
|
|
|
|
p.Push(outSize).Op(vm.DUP1, vm.DUP1, vm.DUP1) |
|
|
|
|
} else { |
|
|
|
|
p.Push(outSize).Push(outOffset).Push(inSize).Push(inOffset) |
|
|
|
|
} |
|
|
|
@ -175,16 +182,16 @@ func (p *Program) DelegateCall(gas *big.Int, address, inOffset, inSize, outOffse |
|
|
|
|
if gas == nil { |
|
|
|
|
p.Op(vm.GAS) |
|
|
|
|
} else { |
|
|
|
|
p.pushBig(gas) |
|
|
|
|
p.doPush(gas) |
|
|
|
|
} |
|
|
|
|
return p.Op(vm.DELEGATECALL) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// StaticCall is a convenience function to make a staticcall. If 'gas' is nil, the opcode GAS will
|
|
|
|
|
// be used to provide all gas.
|
|
|
|
|
func (p *Program) StaticCall(gas *big.Int, address, inOffset, inSize, outOffset, outSize any) *Program { |
|
|
|
|
func (p *Program) StaticCall(gas *uint256.Int, address, inOffset, inSize, outOffset, outSize any) *Program { |
|
|
|
|
if outOffset == outSize && inSize == outSize && inOffset == outSize { |
|
|
|
|
p.Push(outSize).Ops(vm.DUP1, vm.DUP1, vm.DUP1) |
|
|
|
|
p.Push(outSize).Op(vm.DUP1, vm.DUP1, vm.DUP1) |
|
|
|
|
} else { |
|
|
|
|
p.Push(outSize).Push(outOffset).Push(inSize).Push(inOffset) |
|
|
|
|
} |
|
|
|
@ -192,16 +199,16 @@ func (p *Program) StaticCall(gas *big.Int, address, inOffset, inSize, outOffset, |
|
|
|
|
if gas == nil { |
|
|
|
|
p.Op(vm.GAS) |
|
|
|
|
} else { |
|
|
|
|
p.pushBig(gas) |
|
|
|
|
p.doPush(gas) |
|
|
|
|
} |
|
|
|
|
return p.Op(vm.STATICCALL) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// StaticCall is a convenience function to make a callcode. If 'gas' is nil, the opcode GAS will
|
|
|
|
|
// be used to provide all gas.
|
|
|
|
|
func (p *Program) CallCode(gas *big.Int, address, value, inOffset, inSize, outOffset, outSize any) *Program { |
|
|
|
|
func (p *Program) CallCode(gas *uint256.Int, address, value, inOffset, inSize, outOffset, outSize any) *Program { |
|
|
|
|
if outOffset == outSize && inSize == outSize && inOffset == outSize { |
|
|
|
|
p.Push(outSize).Ops(vm.DUP1, vm.DUP1, vm.DUP1) |
|
|
|
|
p.Push(outSize).Op(vm.DUP1, vm.DUP1, vm.DUP1) |
|
|
|
|
} else { |
|
|
|
|
p.Push(outSize).Push(outOffset).Push(inSize).Push(inOffset) |
|
|
|
|
} |
|
|
|
@ -210,54 +217,53 @@ func (p *Program) CallCode(gas *big.Int, address, value, inOffset, inSize, outOf |
|
|
|
|
if gas == nil { |
|
|
|
|
p.Op(vm.GAS) |
|
|
|
|
} else { |
|
|
|
|
p.pushBig(gas) |
|
|
|
|
p.doPush(gas) |
|
|
|
|
} |
|
|
|
|
return p.Op(vm.CALLCODE) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Label returns the PC (of the next instruction)
|
|
|
|
|
// Label returns the PC (of the next instruction).
|
|
|
|
|
func (p *Program) Label() uint64 { |
|
|
|
|
return uint64(len(p.code)) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Jumpdest adds a JUMPDEST op, and returns the PC of that instruction
|
|
|
|
|
// Jumpdest adds a JUMPDEST op, and returns the PC of that instruction.
|
|
|
|
|
func (p *Program) Jumpdest() (*Program, uint64) { |
|
|
|
|
here := p.Label() |
|
|
|
|
p.Op(vm.JUMPDEST) |
|
|
|
|
return p, here |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Jump pushes the destination and adds a JUMP
|
|
|
|
|
// Jump pushes the destination and adds a JUMP.
|
|
|
|
|
func (p *Program) Jump(loc any) *Program { |
|
|
|
|
p.Push(loc) |
|
|
|
|
p.Op(vm.JUMP) |
|
|
|
|
return p |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Jump pushes the destination and adds a JUMP
|
|
|
|
|
func (p *Program) JumpIf(loc any, condition any) { |
|
|
|
|
// JumpIf implements JUMPI.
|
|
|
|
|
func (p *Program) JumpIf(loc any, condition any) *Program { |
|
|
|
|
p.Push(condition) |
|
|
|
|
p.Push(loc) |
|
|
|
|
p.Op(vm.JUMPI) |
|
|
|
|
return p |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Size returns the current size of the bytecode.
|
|
|
|
|
func (p *Program) Size() int { |
|
|
|
|
return len(p.code) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// InputToMemory stores the input (calldata) to memory as address (20 bytes).
|
|
|
|
|
// InputAddressToStack stores the input (calldata) to memory as address (20 bytes).
|
|
|
|
|
func (p *Program) InputAddressToStack(inputOffset uint32) *Program { |
|
|
|
|
p.Push(inputOffset) |
|
|
|
|
p.Op(vm.CALLDATALOAD) // Loads [n -> n + 32] of input data to stack top
|
|
|
|
|
mask, ok := big.NewInt(0).SetString("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 16) |
|
|
|
|
if !ok { |
|
|
|
|
panic("whoa") |
|
|
|
|
} |
|
|
|
|
mask, _ := big.NewInt(0).SetString("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 16) |
|
|
|
|
p.Push(mask) // turn into address
|
|
|
|
|
return p.Op(vm.AND) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// MStore stores the provided data (into the memory area starting at memStart)
|
|
|
|
|
// MStore stores the provided data (into the memory area starting at memStart).
|
|
|
|
|
func (p *Program) Mstore(data []byte, memStart uint32) *Program { |
|
|
|
|
var idx = 0 |
|
|
|
|
// We need to store it in chunks of 32 bytes
|
|
|
|
@ -292,11 +298,11 @@ func (p *Program) Mstore(data []byte, memStart uint32) *Program { |
|
|
|
|
func (p *Program) MstoreSmall(data []byte, memStart uint32) *Program { |
|
|
|
|
if len(data) > 32 { |
|
|
|
|
// For larger sizes, use Mstore instead.
|
|
|
|
|
panic(fmt.Sprintf("only <=32 byte data size supported")) |
|
|
|
|
panic("only <=32 byte data size supported") |
|
|
|
|
} |
|
|
|
|
if len(data) == 0 { |
|
|
|
|
// Storing 0-length data smells of an error somewhere.
|
|
|
|
|
panic(fmt.Sprintf("data is zero length")) |
|
|
|
|
panic("data is zero length") |
|
|
|
|
} |
|
|
|
|
// push the value
|
|
|
|
|
p.Push(data) |
|
|
|
@ -309,7 +315,7 @@ func (p *Program) MstoreSmall(data []byte, memStart uint32) *Program { |
|
|
|
|
// MemToStorage copies the given memory area into SSTORE slots,
|
|
|
|
|
// It expects data to be aligned to 32 byte, and does not zero out
|
|
|
|
|
// remainders if some data is not
|
|
|
|
|
// I.e, if given a 1-byte area, it will still copy the full 32 bytes to storage
|
|
|
|
|
// I.e, if given a 1-byte area, it will still copy the full 32 bytes to storage.
|
|
|
|
|
func (p *Program) MemToStorage(memStart, memSize, startSlot int) *Program { |
|
|
|
|
// We need to store it in chunks of 32 bytes
|
|
|
|
|
for idx := memStart; idx < (memStart + memSize); idx += 32 { |
|
|
|
@ -350,8 +356,8 @@ func (p *Program) ReturnViaCodeCopy(data []byte) *Program { |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Sstore stores the given byte array to the given slot.
|
|
|
|
|
// OBS! Does not verify that the value indeed fits into 32 bytes
|
|
|
|
|
// If it does not, it will panic later on via pushBig
|
|
|
|
|
// OBS! Does not verify that the value indeed fits into 32 bytes.
|
|
|
|
|
// If it does not, it will panic later on via doPush.
|
|
|
|
|
func (p *Program) Sstore(slot any, value any) *Program { |
|
|
|
|
p.Push(value) |
|
|
|
|
p.Push(slot) |
|
|
|
@ -359,14 +365,15 @@ func (p *Program) Sstore(slot any, value any) *Program { |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Tstore stores the given byte array to the given t-slot.
|
|
|
|
|
// OBS! Does not verify that the value indeed fits into 32 bytes
|
|
|
|
|
// If it does not, it will panic later on via pushBig
|
|
|
|
|
// OBS! Does not verify that the value indeed fits into 32 bytes.
|
|
|
|
|
// If it does not, it will panic later on via doPush.
|
|
|
|
|
func (p *Program) Tstore(slot any, value any) *Program { |
|
|
|
|
p.Push(value) |
|
|
|
|
p.Push(slot) |
|
|
|
|
return p.Op(vm.TSTORE) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Return implements RETURN
|
|
|
|
|
func (p *Program) Return(offset, len int) *Program { |
|
|
|
|
p.Push(len) |
|
|
|
|
p.Push(offset) |
|
|
|
@ -396,13 +403,13 @@ func (p *Program) Create2(code []byte, salt any) *Program { |
|
|
|
|
Push(value). |
|
|
|
|
Op(vm.CREATE2) |
|
|
|
|
// On the stack now, is either
|
|
|
|
|
// zero: in case of failure
|
|
|
|
|
// address: in case of success
|
|
|
|
|
// - zero: in case of failure, OR
|
|
|
|
|
// - address: in case of success
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Create2AndCall calls create2 with the given initcode and salt, and then calls
|
|
|
|
|
// Create2ThenCall calls create2 with the given initcode and salt, and then calls
|
|
|
|
|
// into the created contract (or calls into zero, if the creation failed).
|
|
|
|
|
func (p *Program) Create2AndCall(code []byte, salt any) *Program { |
|
|
|
|
func (p *Program) Create2ThenCall(code []byte, salt any) *Program { |
|
|
|
|
p.Create2(code, salt) |
|
|
|
|
// If there happen to be a zero on the stack, it doesn't matter, we're
|
|
|
|
|
// not sending any value anyway
|
|
|
|
|