github.com/nspcc-dev/neo-go@v0.105.2-0.20240517133400-6be757af3eba/pkg/vm/emit/emit.go (about)

     1  package emit
     2  
     3  import (
     4  	"encoding/binary"
     5  	"errors"
     6  	"fmt"
     7  	"math/big"
     8  	"math/bits"
     9  
    10  	"github.com/nspcc-dev/neo-go/pkg/core/interop/interopnames"
    11  	"github.com/nspcc-dev/neo-go/pkg/encoding/bigint"
    12  	"github.com/nspcc-dev/neo-go/pkg/io"
    13  	"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
    14  	"github.com/nspcc-dev/neo-go/pkg/util"
    15  	"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
    16  	"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
    17  )
    18  
    19  // Instruction emits a VM Instruction with data to the given buffer.
    20  func Instruction(w *io.BinWriter, op opcode.Opcode, b []byte) {
    21  	w.WriteB(byte(op))
    22  	w.WriteBytes(b)
    23  }
    24  
    25  // Opcodes emits a single VM Instruction without arguments to the given buffer.
    26  func Opcodes(w *io.BinWriter, ops ...opcode.Opcode) {
    27  	for _, op := range ops {
    28  		w.WriteB(byte(op))
    29  	}
    30  }
    31  
    32  // InitSlot emits INITSLOT instruction with the specified size of locals/args slots.
    33  func InitSlot(w *io.BinWriter, locals, args uint8) {
    34  	Instruction(w, opcode.INITSLOT, []byte{locals, args})
    35  }
    36  
    37  // Bool emits a bool type to the given buffer.
    38  func Bool(w *io.BinWriter, ok bool) {
    39  	var opVal = opcode.PUSHT
    40  	if !ok {
    41  		opVal = opcode.PUSHF
    42  	}
    43  	Opcodes(w, opVal)
    44  }
    45  
    46  func padRight(s int, buf []byte) []byte {
    47  	l := len(buf)
    48  	buf = buf[:s]
    49  	if buf[l-1]&0x80 != 0 {
    50  		for i := l; i < s; i++ {
    51  			buf[i] = 0xFF
    52  		}
    53  	}
    54  	return buf
    55  }
    56  
    57  // Int emits an int type to the given buffer.
    58  func Int(w *io.BinWriter, i int64) {
    59  	if smallInt(w, i) {
    60  		return
    61  	}
    62  	bigInt(w, big.NewInt(i), false)
    63  }
    64  
    65  // BigInt emits a big-integer to the given buffer.
    66  func BigInt(w *io.BinWriter, n *big.Int) {
    67  	bigInt(w, n, true)
    68  }
    69  
    70  func smallInt(w *io.BinWriter, i int64) bool {
    71  	switch {
    72  	case i == -1:
    73  		Opcodes(w, opcode.PUSHM1)
    74  	case i >= 0 && i < 16:
    75  		val := opcode.Opcode(int(opcode.PUSH0) + int(i))
    76  		Opcodes(w, val)
    77  	default:
    78  		return false
    79  	}
    80  	return true
    81  }
    82  
    83  func bigInt(w *io.BinWriter, n *big.Int, trySmall bool) {
    84  	if w.Err != nil {
    85  		return
    86  	}
    87  	if trySmall && n.IsInt64() && smallInt(w, n.Int64()) {
    88  		return
    89  	}
    90  
    91  	if err := stackitem.CheckIntegerSize(n); err != nil {
    92  		w.Err = err
    93  		return
    94  	}
    95  
    96  	buf := bigint.ToPreallocatedBytes(n, make([]byte, 0, 32))
    97  	if len(buf) == 0 {
    98  		Opcodes(w, opcode.PUSH0)
    99  		return
   100  	}
   101  	padSize := byte(8 - bits.LeadingZeros8(byte(len(buf)-1)))
   102  	Opcodes(w, opcode.PUSHINT8+opcode.Opcode(padSize))
   103  	w.WriteBytes(padRight(1<<padSize, buf))
   104  }
   105  
   106  // Array emits an array of elements to the given buffer. It accepts everything that
   107  // Any accepts.
   108  func Array(w *io.BinWriter, es ...any) {
   109  	if len(es) == 0 {
   110  		Opcodes(w, opcode.NEWARRAY0)
   111  		return
   112  	}
   113  	for i := len(es) - 1; i >= 0; i-- {
   114  		Any(w, es[i])
   115  	}
   116  	Int(w, int64(len(es)))
   117  	Opcodes(w, opcode.PACK)
   118  }
   119  
   120  // Any emits element if supported. It accepts elements of the following types:
   121  //   - int8, int16, int32, int64, int
   122  //   - uint8, uint16, uint32, uint64, uint
   123  //   - *big.Int
   124  //   - string, []byte
   125  //   - util.Uint160, *util.Uint160, util.Uint256, *util.Uint256
   126  //   - bool
   127  //   - stackitem.Convertible, stackitem.Item
   128  //   - nil
   129  //   - []any
   130  func Any(w *io.BinWriter, something any) {
   131  	switch e := something.(type) {
   132  	case []any:
   133  		Array(w, e...)
   134  	case int64:
   135  		Int(w, e)
   136  	case uint64:
   137  		BigInt(w, new(big.Int).SetUint64(e))
   138  	case int32:
   139  		Int(w, int64(e))
   140  	case uint32:
   141  		Int(w, int64(e))
   142  	case int16:
   143  		Int(w, int64(e))
   144  	case uint16:
   145  		Int(w, int64(e))
   146  	case int8:
   147  		Int(w, int64(e))
   148  	case uint8:
   149  		Int(w, int64(e))
   150  	case int:
   151  		Int(w, int64(e))
   152  	case uint:
   153  		BigInt(w, new(big.Int).SetUint64(uint64(e)))
   154  	case *big.Int:
   155  		BigInt(w, e)
   156  	case string:
   157  		String(w, e)
   158  	case util.Uint160:
   159  		Bytes(w, e.BytesBE())
   160  	case util.Uint256:
   161  		Bytes(w, e.BytesBE())
   162  	case *util.Uint160:
   163  		if e == nil {
   164  			Opcodes(w, opcode.PUSHNULL)
   165  		} else {
   166  			Bytes(w, e.BytesBE())
   167  		}
   168  	case *util.Uint256:
   169  		if e == nil {
   170  			Opcodes(w, opcode.PUSHNULL)
   171  		} else {
   172  			Bytes(w, e.BytesBE())
   173  		}
   174  	case []byte:
   175  		Bytes(w, e)
   176  	case bool:
   177  		Bool(w, e)
   178  	case stackitem.Convertible:
   179  		Convertible(w, e)
   180  	case stackitem.Item:
   181  		StackItem(w, e)
   182  	default:
   183  		if something != nil {
   184  			w.Err = fmt.Errorf("unsupported type: %T", e)
   185  			return
   186  		}
   187  		Opcodes(w, opcode.PUSHNULL)
   188  	}
   189  }
   190  
   191  // Convertible converts provided stackitem.Convertible to the stackitem.Item and
   192  // emits the item to the given buffer.
   193  func Convertible(w *io.BinWriter, c stackitem.Convertible) {
   194  	si, err := c.ToStackItem()
   195  	if err != nil {
   196  		w.Err = fmt.Errorf("failed to convert stackitem.Convertible to stackitem: %w", err)
   197  		return
   198  	}
   199  	StackItem(w, si)
   200  }
   201  
   202  // StackItem emits provided stackitem.Item to the given buffer.
   203  func StackItem(w *io.BinWriter, si stackitem.Item) {
   204  	switch t := si.Type(); t {
   205  	case stackitem.AnyT:
   206  		if si.Value() == nil {
   207  			Opcodes(w, opcode.PUSHNULL)
   208  		} else {
   209  			w.Err = fmt.Errorf("only nil value supported for %s", t)
   210  			return
   211  		}
   212  	case stackitem.BooleanT:
   213  		Bool(w, si.Value().(bool))
   214  	case stackitem.IntegerT:
   215  		BigInt(w, si.Value().(*big.Int))
   216  	case stackitem.ByteArrayT, stackitem.BufferT:
   217  		Bytes(w, si.Value().([]byte))
   218  	case stackitem.ArrayT:
   219  		arr := si.Value().([]stackitem.Item)
   220  		arrAny := make([]any, len(arr))
   221  		for i := range arr {
   222  			arrAny[i] = arr[i]
   223  		}
   224  		Array(w, arrAny...)
   225  	case stackitem.StructT:
   226  		arr := si.Value().([]stackitem.Item)
   227  		for i := len(arr) - 1; i >= 0; i-- {
   228  			StackItem(w, arr[i])
   229  		}
   230  
   231  		Int(w, int64(len(arr)))
   232  		Opcodes(w, opcode.PACKSTRUCT)
   233  	case stackitem.MapT:
   234  		arr := si.Value().([]stackitem.MapElement)
   235  		for i := len(arr) - 1; i >= 0; i-- {
   236  			StackItem(w, arr[i].Value)
   237  			StackItem(w, arr[i].Key)
   238  		}
   239  
   240  		Int(w, int64(len(arr)))
   241  		Opcodes(w, opcode.PACKMAP)
   242  	default:
   243  		w.Err = fmt.Errorf("%s is unsuppoted", t)
   244  		return
   245  	}
   246  }
   247  
   248  // String emits a string to the given buffer.
   249  func String(w *io.BinWriter, s string) {
   250  	Bytes(w, []byte(s))
   251  }
   252  
   253  // Bytes emits a byte array to the given buffer.
   254  func Bytes(w *io.BinWriter, b []byte) {
   255  	var n = len(b)
   256  
   257  	switch {
   258  	case n < 0x100:
   259  		Instruction(w, opcode.PUSHDATA1, []byte{byte(n)})
   260  	case n < 0x10000:
   261  		buf := make([]byte, 2)
   262  		binary.LittleEndian.PutUint16(buf, uint16(n))
   263  		Instruction(w, opcode.PUSHDATA2, buf)
   264  	default:
   265  		buf := make([]byte, 4)
   266  		binary.LittleEndian.PutUint32(buf, uint32(n))
   267  		Instruction(w, opcode.PUSHDATA4, buf)
   268  	}
   269  	w.WriteBytes(b)
   270  }
   271  
   272  // Syscall emits the syscall API to the given buffer.
   273  // Syscall API string cannot be 0.
   274  func Syscall(w *io.BinWriter, api string) {
   275  	if w.Err != nil {
   276  		return
   277  	} else if len(api) == 0 {
   278  		w.Err = errors.New("syscall api cannot be of length 0")
   279  		return
   280  	}
   281  	buf := make([]byte, 4)
   282  	binary.LittleEndian.PutUint32(buf, interopnames.ToID([]byte(api)))
   283  	Instruction(w, opcode.SYSCALL, buf)
   284  }
   285  
   286  // Call emits a call Instruction with the label to the given buffer.
   287  func Call(w *io.BinWriter, op opcode.Opcode, label uint16) {
   288  	Jmp(w, op, label)
   289  }
   290  
   291  // Jmp emits a jump Instruction along with the label to the given buffer.
   292  func Jmp(w *io.BinWriter, op opcode.Opcode, label uint16) {
   293  	if w.Err != nil {
   294  		return
   295  	} else if !isInstructionJmp(op) {
   296  		w.Err = fmt.Errorf("opcode %s is not a jump or call type", op.String())
   297  		return
   298  	}
   299  	buf := make([]byte, 4)
   300  	binary.LittleEndian.PutUint16(buf, label)
   301  	Instruction(w, op, buf)
   302  }
   303  
   304  // AppCallNoArgs emits a call to the provided contract.
   305  func AppCallNoArgs(w *io.BinWriter, scriptHash util.Uint160, operation string, f callflag.CallFlag) {
   306  	Int(w, int64(f))
   307  	String(w, operation)
   308  	Bytes(w, scriptHash.BytesBE())
   309  	Syscall(w, interopnames.SystemContractCall)
   310  }
   311  
   312  // AppCall emits SYSCALL with System.Contract.Call parameter for given contract, operation, call flag and arguments.
   313  func AppCall(w *io.BinWriter, scriptHash util.Uint160, operation string, f callflag.CallFlag, args ...any) {
   314  	Array(w, args...)
   315  	AppCallNoArgs(w, scriptHash, operation, f)
   316  }
   317  
   318  // CheckSig emits a single-key verification script using given []bytes as a key.
   319  // It does not check for key correctness, so you can get an invalid script if the
   320  // data passed is not really a public key.
   321  func CheckSig(w *io.BinWriter, key []byte) {
   322  	Bytes(w, key)
   323  	Syscall(w, interopnames.SystemCryptoCheckSig)
   324  }
   325  
   326  func isInstructionJmp(op opcode.Opcode) bool {
   327  	return opcode.JMP <= op && op <= opcode.CALLL || op == opcode.ENDTRYL
   328  }