github.com/ethereum/go-ethereum@v1.16.1/core/vm/program/program.go (about)

     1  // Copyright 2024 The go-ethereum Authors
     2  // This file is part of the go-ethereum library.
     3  //
     4  // The go-ethereum library is free software: you can redistribute it and/or modify
     5  // it under the terms of the GNU Lesser General Public License as published by
     6  // the Free Software Foundation, either version 3 of the License, or
     7  // (at your option) any later version.
     8  //
     9  // The go-ethereum library is distributed in the hope that it will be useful,
    10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    12  // GNU Lesser General Public License for more details.
    13  //
    14  // You should have received a copy of the GNU Lesser General Public License
    15  // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
    16  
    17  // package program is a utility to create EVM bytecode for testing, but _not_ for production. As such:
    18  //
    19  // - There are not package guarantees. We might iterate heavily on this package, and do backwards-incompatible changes without warning
    20  // - There are no quality-guarantees. These utilities may produce evm-code that is non-functional. YMMV.
    21  // - There are no stability-guarantees. The utility will `panic` if the inputs do not align / make sense.
    22  
    23  package program
    24  
    25  import (
    26  	"fmt"
    27  	"math/big"
    28  
    29  	"github.com/ethereum/go-ethereum/core/vm"
    30  	"github.com/holiman/uint256"
    31  )
    32  
    33  // Program is a simple bytecode container. It can be used to construct
    34  // simple EVM programs. Errors during construction of a Program typically
    35  // cause panics: so avoid using these programs in production settings or on
    36  // untrusted input.
    37  // This package is mainly meant to aid in testing. This is not a production
    38  // -level "compiler".
    39  type Program struct {
    40  	code []byte
    41  }
    42  
    43  // New creates a new Program
    44  func New() *Program {
    45  	return &Program{
    46  		code: make([]byte, 0),
    47  	}
    48  }
    49  
    50  // add adds the op to the code.
    51  func (p *Program) add(op byte) *Program {
    52  	p.code = append(p.code, op)
    53  	return p
    54  }
    55  
    56  // pushBig creates a PUSHX instruction and pushes the given val.
    57  // - If the val is nil, it pushes zero
    58  // - If the val is bigger than 32 bytes, it panics
    59  func (p *Program) doPush(val *uint256.Int) {
    60  	if val == nil {
    61  		val = new(uint256.Int)
    62  	}
    63  	valBytes := val.Bytes()
    64  	if len(valBytes) == 0 {
    65  		valBytes = append(valBytes, 0)
    66  	}
    67  	bLen := len(valBytes)
    68  	p.add(byte(vm.PUSH1) - 1 + byte(bLen))
    69  	p.Append(valBytes)
    70  }
    71  
    72  // Append appends the given data to the code.
    73  func (p *Program) Append(data []byte) *Program {
    74  	p.code = append(p.code, data...)
    75  	return p
    76  }
    77  
    78  // Bytes returns the Program bytecode. OBS: This is not a copy.
    79  func (p *Program) Bytes() []byte {
    80  	return p.code
    81  }
    82  
    83  // SetBytes sets the Program bytecode. The combination of Bytes and SetBytes means
    84  // that external callers can implement missing functionality:
    85  //
    86  //	...
    87  //	prog.Push(1)
    88  //	code := prog.Bytes()
    89  //	manipulate(code)
    90  //	prog.SetBytes(code)
    91  func (p *Program) SetBytes(code []byte) {
    92  	p.code = code
    93  }
    94  
    95  // Hex returns the Program bytecode as a hex string.
    96  func (p *Program) Hex() string {
    97  	return fmt.Sprintf("%02x", p.Bytes())
    98  }
    99  
   100  // Op appends the given opcode(s).
   101  func (p *Program) Op(ops ...vm.OpCode) *Program {
   102  	for _, op := range ops {
   103  		p.add(byte(op))
   104  	}
   105  	return p
   106  }
   107  
   108  // Push creates a PUSHX instruction with the data provided. If zero is being pushed,
   109  // PUSH0 will be avoided in favour of [PUSH1 0], to ensure backwards compatibility.
   110  func (p *Program) Push(val any) *Program {
   111  	switch v := val.(type) {
   112  	case int:
   113  		p.doPush(new(uint256.Int).SetUint64(uint64(v)))
   114  	case uint64:
   115  		p.doPush(new(uint256.Int).SetUint64(v))
   116  	case uint32:
   117  		p.doPush(new(uint256.Int).SetUint64(uint64(v)))
   118  	case uint16:
   119  		p.doPush(new(uint256.Int).SetUint64(uint64(v)))
   120  	case *big.Int:
   121  		p.doPush(uint256.MustFromBig(v))
   122  	case *uint256.Int:
   123  		p.doPush(v)
   124  	case uint256.Int:
   125  		p.doPush(&v)
   126  	case []byte:
   127  		p.doPush(new(uint256.Int).SetBytes(v))
   128  	case byte:
   129  		p.doPush(new(uint256.Int).SetUint64(uint64(v)))
   130  	case interface{ Bytes() []byte }:
   131  		// Here, we jump through some hoops in order to avoid depending on
   132  		// go-ethereum types.Address and common.Hash, and instead use the
   133  		// interface. This works on both values and pointers!
   134  		p.doPush(new(uint256.Int).SetBytes(v.Bytes()))
   135  	case nil:
   136  		p.doPush(nil)
   137  	default:
   138  		panic(fmt.Sprintf("unsupported type %T", v))
   139  	}
   140  	return p
   141  }
   142  
   143  // Push0 implements PUSH0 (0x5f).
   144  func (p *Program) Push0() *Program {
   145  	return p.Op(vm.PUSH0)
   146  }
   147  
   148  // ExtcodeCopy performs an extcodecopy invocation.
   149  func (p *Program) ExtcodeCopy(address, memOffset, codeOffset, length any) *Program {
   150  	p.Push(length)
   151  	p.Push(codeOffset)
   152  	p.Push(memOffset)
   153  	p.Push(address)
   154  	return p.Op(vm.EXTCODECOPY)
   155  }
   156  
   157  // Call is a convenience function to make a call. If 'gas' is nil, the opcode GAS will
   158  // be used to provide all gas.
   159  func (p *Program) Call(gas *uint256.Int, address, value, inOffset, inSize, outOffset, outSize any) *Program {
   160  	if outOffset == outSize && inSize == outSize && inOffset == outSize && value == outSize {
   161  		p.Push(outSize).Op(vm.DUP1, vm.DUP1, vm.DUP1, vm.DUP1)
   162  	} else {
   163  		p.Push(outSize).Push(outOffset).Push(inSize).Push(inOffset).Push(value)
   164  	}
   165  	p.Push(address)
   166  	if gas == nil {
   167  		p.Op(vm.GAS)
   168  	} else {
   169  		p.doPush(gas)
   170  	}
   171  	return p.Op(vm.CALL)
   172  }
   173  
   174  // DelegateCall is a convenience function to make a delegatecall. If 'gas' is nil, the opcode GAS will
   175  // be used to provide all gas.
   176  func (p *Program) DelegateCall(gas *uint256.Int, address, inOffset, inSize, outOffset, outSize any) *Program {
   177  	if outOffset == outSize && inSize == outSize && inOffset == outSize {
   178  		p.Push(outSize).Op(vm.DUP1, vm.DUP1, vm.DUP1)
   179  	} else {
   180  		p.Push(outSize).Push(outOffset).Push(inSize).Push(inOffset)
   181  	}
   182  	p.Push(address)
   183  	if gas == nil {
   184  		p.Op(vm.GAS)
   185  	} else {
   186  		p.doPush(gas)
   187  	}
   188  	return p.Op(vm.DELEGATECALL)
   189  }
   190  
   191  // StaticCall is a convenience function to make a staticcall. If 'gas' is nil, the opcode GAS will
   192  // be used to provide all gas.
   193  func (p *Program) StaticCall(gas *uint256.Int, address, inOffset, inSize, outOffset, outSize any) *Program {
   194  	if outOffset == outSize && inSize == outSize && inOffset == outSize {
   195  		p.Push(outSize).Op(vm.DUP1, vm.DUP1, vm.DUP1)
   196  	} else {
   197  		p.Push(outSize).Push(outOffset).Push(inSize).Push(inOffset)
   198  	}
   199  	p.Push(address)
   200  	if gas == nil {
   201  		p.Op(vm.GAS)
   202  	} else {
   203  		p.doPush(gas)
   204  	}
   205  	return p.Op(vm.STATICCALL)
   206  }
   207  
   208  // CallCode is a convenience function to make a callcode. If 'gas' is nil, the opcode GAS will
   209  // be used to provide all gas.
   210  func (p *Program) CallCode(gas *uint256.Int, address, value, inOffset, inSize, outOffset, outSize any) *Program {
   211  	if outOffset == outSize && inSize == outSize && inOffset == outSize {
   212  		p.Push(outSize).Op(vm.DUP1, vm.DUP1, vm.DUP1)
   213  	} else {
   214  		p.Push(outSize).Push(outOffset).Push(inSize).Push(inOffset)
   215  	}
   216  	p.Push(value)
   217  	p.Push(address)
   218  	if gas == nil {
   219  		p.Op(vm.GAS)
   220  	} else {
   221  		p.doPush(gas)
   222  	}
   223  	return p.Op(vm.CALLCODE)
   224  }
   225  
   226  // Label returns the PC (of the next instruction).
   227  func (p *Program) Label() uint64 {
   228  	return uint64(len(p.code))
   229  }
   230  
   231  // Jumpdest adds a JUMPDEST op, and returns the PC of that instruction.
   232  func (p *Program) Jumpdest() (*Program, uint64) {
   233  	here := p.Label()
   234  	p.Op(vm.JUMPDEST)
   235  	return p, here
   236  }
   237  
   238  // Jump pushes the destination and adds a JUMP.
   239  func (p *Program) Jump(loc any) *Program {
   240  	p.Push(loc)
   241  	p.Op(vm.JUMP)
   242  	return p
   243  }
   244  
   245  // JumpIf implements JUMPI.
   246  func (p *Program) JumpIf(loc any, condition any) *Program {
   247  	p.Push(condition)
   248  	p.Push(loc)
   249  	p.Op(vm.JUMPI)
   250  	return p
   251  }
   252  
   253  // Size returns the current size of the bytecode.
   254  func (p *Program) Size() int {
   255  	return len(p.code)
   256  }
   257  
   258  // InputAddressToStack stores the input (calldata) to memory as address (20 bytes).
   259  func (p *Program) InputAddressToStack(inputOffset uint32) *Program {
   260  	p.Push(inputOffset)
   261  	p.Op(vm.CALLDATALOAD) // Loads [n -> n + 32] of input data to stack top
   262  	mask, _ := big.NewInt(0).SetString("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 16)
   263  	p.Push(mask) // turn into address
   264  	return p.Op(vm.AND)
   265  }
   266  
   267  // Mstore stores the provided data (into the memory area starting at memStart).
   268  func (p *Program) Mstore(data []byte, memStart uint32) *Program {
   269  	var idx = 0
   270  	// We need to store it in chunks of 32 bytes
   271  	for ; idx+32 <= len(data); idx += 32 {
   272  		chunk := data[idx : idx+32]
   273  		// push the value
   274  		p.Push(chunk)
   275  		// push the memory index
   276  		p.Push(uint32(idx) + memStart)
   277  		p.Op(vm.MSTORE)
   278  	}
   279  	// Remainders become stored using MSTORE8
   280  	for ; idx < len(data); idx++ {
   281  		b := data[idx]
   282  		// push the byte
   283  		p.Push(b)
   284  		p.Push(uint32(idx) + memStart)
   285  		p.Op(vm.MSTORE8)
   286  	}
   287  	return p
   288  }
   289  
   290  // MstoreSmall stores the provided data, which must be smaller than 32 bytes,
   291  // into the memory area starting at memStart.
   292  // The data will be LHS zero-added to align on 32 bytes.
   293  // For example, providing data 0x1122, it will do a PUSH2:
   294  // PUSH2 0x1122, resulting in
   295  // stack: 0x0000000000000000000000000000000000000000000000000000000000001122
   296  // followed by MSTORE(0,0)
   297  // And thus, the resulting memory will be
   298  // [ 0000000000000000000000000000000000000000000000000000000000001122 ]
   299  func (p *Program) MstoreSmall(data []byte, memStart uint32) *Program {
   300  	if len(data) > 32 {
   301  		// For larger sizes, use Mstore instead.
   302  		panic("only <=32 byte data size supported")
   303  	}
   304  	if len(data) == 0 {
   305  		// Storing 0-length data smells of an error somewhere.
   306  		panic("data is zero length")
   307  	}
   308  	// push the value
   309  	p.Push(data)
   310  	// push the memory index
   311  	p.Push(memStart)
   312  	p.Op(vm.MSTORE)
   313  	return p
   314  }
   315  
   316  // MemToStorage copies the given memory area into SSTORE slots,
   317  // It expects data to be aligned to 32 byte, and does not zero out
   318  // remainders if some data is not
   319  // I.e, if given a 1-byte area, it will still copy the full 32 bytes to storage.
   320  func (p *Program) MemToStorage(memStart, memSize, startSlot int) *Program {
   321  	// We need to store it in chunks of 32 bytes
   322  	for idx := memStart; idx < (memStart + memSize); idx += 32 {
   323  		dataStart := idx
   324  		// Mload the chunk
   325  		p.Push(dataStart)
   326  		p.Op(vm.MLOAD)
   327  		// Value is now on stack,
   328  		p.Push(startSlot)
   329  		p.Op(vm.SSTORE)
   330  		startSlot++
   331  	}
   332  	return p
   333  }
   334  
   335  // ReturnViaCodeCopy utilises CODECOPY to place the given data in the bytecode of
   336  // p, loads into memory (offset 0) and returns the code.
   337  // This is a typical "constructor".
   338  // Note: since all indexing is calculated immediately, the preceding bytecode
   339  // must not be expanded or shortened.
   340  func (p *Program) ReturnViaCodeCopy(data []byte) *Program {
   341  	p.Push(len(data))
   342  	// For convenience, we'll use PUSH2 for the offset. Then we know we can always
   343  	// fit, since code is limited to 0xc000
   344  	p.Op(vm.PUSH2)
   345  	offsetPos := p.Size()  // Need to update this position later on
   346  	p.Append([]byte{0, 0}) // Offset of the code to be copied
   347  	p.Push(0)              // Offset in memory (destination)
   348  	p.Op(vm.CODECOPY)      // Copy from code[offset:offset+len] to memory[0:]
   349  	p.Return(0, len(data)) // Return memory[0:len]
   350  	offset := p.Size()
   351  	p.Append(data) // And add the data
   352  
   353  	// Now, go back and fix the offset
   354  	p.code[offsetPos] = byte(offset >> 8)
   355  	p.code[offsetPos+1] = byte(offset)
   356  	return p
   357  }
   358  
   359  // Sstore stores the given byte array to the given slot.
   360  // OBS! Does not verify that the value indeed fits into 32 bytes.
   361  // If it does not, it will panic later on via doPush.
   362  func (p *Program) Sstore(slot any, value any) *Program {
   363  	p.Push(value)
   364  	p.Push(slot)
   365  	return p.Op(vm.SSTORE)
   366  }
   367  
   368  // Tstore stores the given byte array to the given t-slot.
   369  // OBS! Does not verify that the value indeed fits into 32 bytes.
   370  // If it does not, it will panic later on via doPush.
   371  func (p *Program) Tstore(slot any, value any) *Program {
   372  	p.Push(value)
   373  	p.Push(slot)
   374  	return p.Op(vm.TSTORE)
   375  }
   376  
   377  // Return implements RETURN
   378  func (p *Program) Return(offset, len int) *Program {
   379  	p.Push(len)
   380  	p.Push(offset)
   381  	return p.Op(vm.RETURN)
   382  }
   383  
   384  // ReturnData loads the given data into memory, and does a return with it
   385  func (p *Program) ReturnData(data []byte) *Program {
   386  	p.Mstore(data, 0)
   387  	return p.Return(0, len(data))
   388  }
   389  
   390  // Create2 uses create2 to construct a contract with the given bytecode.
   391  // This operation leaves either '0' or address on the stack.
   392  func (p *Program) Create2(code []byte, salt any) *Program {
   393  	var (
   394  		value  = 0
   395  		offset = 0
   396  		size   = len(code)
   397  	)
   398  	// Load the code into mem
   399  	p.Mstore(code, 0)
   400  	// Create it
   401  	return p.Push(salt).
   402  		Push(size).
   403  		Push(offset).
   404  		Push(value).
   405  		Op(vm.CREATE2)
   406  	// On the stack now, is either
   407  	// - zero: in case of failure, OR
   408  	// - address: in case of success
   409  }
   410  
   411  // Create2ThenCall calls create2 with the given initcode and salt, and then calls
   412  // into the created contract (or calls into zero, if the creation failed).
   413  func (p *Program) Create2ThenCall(code []byte, salt any) *Program {
   414  	p.Create2(code, salt)
   415  	// If there happen to be a zero on the stack, it doesn't matter, we're
   416  	// not sending any value anyway
   417  	p.Push(0).Push(0) // mem out
   418  	p.Push(0).Push(0) // mem in
   419  	p.Push(0)         // value
   420  	p.Op(vm.DUP6)     // address
   421  	p.Op(vm.GAS)
   422  	p.Op(vm.CALL)
   423  	p.Op(vm.POP)        // pop the retval
   424  	return p.Op(vm.POP) // pop the address
   425  }
   426  
   427  // Selfdestruct pushes beneficiary and invokes selfdestruct.
   428  func (p *Program) Selfdestruct(beneficiary any) *Program {
   429  	p.Push(beneficiary)
   430  	return p.Op(vm.SELFDESTRUCT)
   431  }