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 }