github.com/nspcc-dev/neo-go@v0.105.2-0.20240517133400-6be757af3eba/cli/vm/cli.go (about) 1 package vm 2 3 import ( 4 "bytes" 5 "crypto/elliptic" 6 "encoding/base64" 7 "encoding/binary" 8 "encoding/hex" 9 "encoding/json" 10 "errors" 11 "fmt" 12 "io" 13 "math/big" 14 "os" 15 "strconv" 16 "strings" 17 "text/tabwriter" 18 19 "github.com/chzyer/readline" 20 "github.com/kballard/go-shellquote" 21 "github.com/nspcc-dev/neo-go/cli/cmdargs" 22 "github.com/nspcc-dev/neo-go/cli/flags" 23 "github.com/nspcc-dev/neo-go/cli/options" 24 "github.com/nspcc-dev/neo-go/cli/paramcontext" 25 "github.com/nspcc-dev/neo-go/pkg/compiler" 26 "github.com/nspcc-dev/neo-go/pkg/config" 27 "github.com/nspcc-dev/neo-go/pkg/core" 28 "github.com/nspcc-dev/neo-go/pkg/core/interop" 29 "github.com/nspcc-dev/neo-go/pkg/core/interop/runtime" 30 "github.com/nspcc-dev/neo-go/pkg/core/native" 31 "github.com/nspcc-dev/neo-go/pkg/core/state" 32 "github.com/nspcc-dev/neo-go/pkg/core/storage" 33 "github.com/nspcc-dev/neo-go/pkg/core/storage/dbconfig" 34 "github.com/nspcc-dev/neo-go/pkg/core/transaction" 35 "github.com/nspcc-dev/neo-go/pkg/crypto/keys" 36 "github.com/nspcc-dev/neo-go/pkg/encoding/address" 37 "github.com/nspcc-dev/neo-go/pkg/encoding/bigint" 38 "github.com/nspcc-dev/neo-go/pkg/smartcontract" 39 "github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag" 40 "github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest" 41 "github.com/nspcc-dev/neo-go/pkg/smartcontract/nef" 42 "github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger" 43 "github.com/nspcc-dev/neo-go/pkg/util" 44 "github.com/nspcc-dev/neo-go/pkg/util/slice" 45 "github.com/nspcc-dev/neo-go/pkg/vm" 46 "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" 47 "github.com/urfave/cli" 48 "go.uber.org/zap" 49 "go.uber.org/zap/zapcore" 50 ) 51 52 const ( 53 chainKey = "chain" 54 chainCfgKey = "chainCfg" 55 icKey = "ic" 56 contractStateKey = "contractState" 57 exitFuncKey = "exitFunc" 58 readlineInstanceKey = "readlineKey" 59 printLogoKey = "printLogoKey" 60 ) 61 62 // Various flag names. 63 const ( 64 verboseFlagFullName = "verbose" 65 historicFlagFullName = "historic" 66 gasFlagFullName = "gas" 67 backwardsFlagFullName = "backwards" 68 diffFlagFullName = "diff" 69 hashFlagFullName = "hash" 70 ) 71 72 var ( 73 historicFlag = cli.IntFlag{ 74 Name: historicFlagFullName, 75 Usage: "Height for historic script invocation (for MPT-enabled blockchain configuration with KeepOnlyLatestState setting disabled). " + 76 "Assuming that block N-th is specified as an argument, the historic invocation is based on the storage state of height N and fake currently-accepting block with index N+1.", 77 } 78 gasFlag = cli.Int64Flag{ 79 Name: gasFlagFullName, 80 Usage: "GAS limit for this execution (integer number, satoshi).", 81 } 82 hashFlag = cli.StringFlag{ 83 Name: hashFlagFullName, 84 Usage: "Smart-contract hash in LE form or address", 85 } 86 ) 87 88 var commands = []cli.Command{ 89 { 90 Name: "exit", 91 Usage: "Exit the VM prompt", 92 UsageText: "exit", 93 Description: "Exit the VM prompt.", 94 Action: handleExit, 95 }, 96 { 97 Name: "ip", 98 Usage: "Show current instruction", 99 UsageText: "ip", 100 Description: "Show current instruction.", 101 Action: handleIP, 102 }, 103 { 104 Name: "break", 105 Usage: "Place a breakpoint", 106 UsageText: `break <ip>`, 107 Description: `<ip> is mandatory parameter. 108 109 Example: 110 > break 12`, 111 Action: handleBreak, 112 }, 113 { 114 Name: "jump", 115 Usage: "Jump to the specified instruction (absolute IP value)", 116 UsageText: `jump <ip>`, 117 Description: `<ip> is mandatory parameter (absolute IP value). 118 119 Example: 120 > jump 12`, 121 Action: handleJump, 122 }, 123 { 124 Name: "estack", 125 Usage: "Show evaluation stack contents", 126 UsageText: "estack", 127 Description: "Show evaluation stack contents.", 128 Action: handleXStack, 129 }, 130 { 131 Name: "istack", 132 Usage: "Show invocation stack contents", 133 UsageText: "istack", 134 Description: "Show invocation stack contents.", 135 Action: handleXStack, 136 }, 137 { 138 Name: "sslot", 139 Usage: "Show static slot contents", 140 UsageText: "sslot", 141 Description: "Show static slot contents.", 142 Action: handleSlots, 143 }, 144 { 145 Name: "lslot", 146 Usage: "Show local slot contents", 147 UsageText: "lslot", 148 Description: "Show local slot contents", 149 Action: handleSlots, 150 }, 151 { 152 Name: "aslot", 153 Usage: "Show arguments slot contents", 154 UsageText: "aslot", 155 Description: "Show arguments slot contents.", 156 Action: handleSlots, 157 }, 158 { 159 Name: "loadnef", 160 Usage: "Load a NEF (possibly with a contract hash) into the VM optionally using provided scoped signers in the context", 161 UsageText: `loadnef [--historic <height>] [--gas <int>] [--hash <hash-or-address>] <file> [<manifest>] [-- <signer-with-scope>, ...]`, 162 Flags: []cli.Flag{historicFlag, gasFlag, hashFlag}, 163 Description: `<file> parameter is mandatory, <manifest> parameter (if omitted) will 164 be guessed from the <file> parameter by replacing '.nef' suffix with '.manifest.json' 165 suffix. 166 167 ` + cmdargs.SignersParsingDoc + ` 168 169 Example: 170 > loadnef /path/to/script.nef /path/to/manifest.json`, 171 Action: handleLoadNEF, 172 }, 173 { 174 Name: "loadbase64", 175 Usage: "Load a base64-encoded script string into the VM optionally attaching to it provided signers with scopes", 176 UsageText: `loadbase64 [--historic <height>] [--gas <int>] <string> [-- <signer-with-scope>, ...]`, 177 Flags: []cli.Flag{historicFlag, gasFlag}, 178 Description: `<string> is mandatory parameter. 179 180 ` + cmdargs.SignersParsingDoc + ` 181 182 Example: 183 > loadbase64 AwAQpdToAAAADBQV9ehtQR1OrVZVhtHtoUHRfoE+agwUzmFvf3Rhfg/EuAVYOvJgKiON9j8TwAwIdHJhbnNmZXIMFDt9NxHG8Mz5sdypA9G/odiW8SOMQWJ9W1I4`, 184 Action: handleLoadBase64, 185 }, 186 { 187 Name: "loadhex", 188 Usage: "Load a hex-encoded script string into the VM optionally attaching to it provided signers with scopes", 189 UsageText: `loadhex [--historic <height>] [--gas <int>] <string> [-- <signer-with-scope>, ...]`, 190 Flags: []cli.Flag{historicFlag, gasFlag}, 191 Description: `<string> is mandatory parameter. 192 193 ` + cmdargs.SignersParsingDoc + ` 194 195 Example: 196 > loadhex 0c0c48656c6c6f20776f726c6421`, 197 Action: handleLoadHex, 198 }, 199 { 200 Name: "loadgo", 201 Usage: "Compile and load a Go file with the manifest into the VM optionally attaching to it provided signers with scopes and setting provided hash", 202 UsageText: `loadgo [--historic <height>] [--gas <int>] [--hash <hash-or-address>] <file> [-- <signer-with-scope>, ...]`, 203 Flags: []cli.Flag{historicFlag, gasFlag, hashFlag}, 204 Description: `<file> is mandatory parameter. 205 206 ` + cmdargs.SignersParsingDoc + ` 207 208 Example: 209 > loadgo /path/to/file.go`, 210 Action: handleLoadGo, 211 }, 212 { 213 Name: "loadtx", 214 Usage: "Load transaction into the VM from chain or from parameter context file", 215 UsageText: `loadtx [--historic <height>] [--gas <int>] <file-or-hash>`, 216 Flags: []cli.Flag{historicFlag, gasFlag}, 217 Description: `Load transaction into the VM from chain or from parameter context file. 218 The transaction script will be loaded into VM; the resulting execution context 219 will use the provided transaction as script container including its signers, 220 hash and nonce. It'll also use transaction's system fee value as GAS limit if 221 --gas option is not used. 222 223 <file-or-hash> is mandatory parameter. 224 225 Example: 226 > loadtx /path/to/file`, 227 Action: handleLoadTx, 228 }, 229 { 230 Name: "loaddeployed", 231 Usage: "Load deployed contract into the VM from chain optionally attaching to it provided signers with scopes", 232 UsageText: `loaddeployed [--historic <height>] [--gas <int>] <hash-or-address-or-id> [-- <signer-with-scope>, ...]`, 233 Flags: []cli.Flag{historicFlag, gasFlag}, 234 Description: `Load deployed contract into the VM from chain optionally attaching to it provided signers with scopes. 235 If '--historic' flag specified, then the historic contract state (historic script and manifest) will be loaded. 236 237 <hash-or-address-or-id> is mandatory parameter. 238 239 ` + cmdargs.SignersParsingDoc + ` 240 241 Example: 242 > loaddeployed 0x0000000009070e030d0f0e020d0c06050e030c02`, 243 Action: handleLoadDeployed, 244 }, 245 { 246 Name: "reset", 247 Usage: "Unload compiled script from the VM and reset context to proper (possibly, historic) state", 248 UsageText: "reset", 249 Flags: []cli.Flag{historicFlag}, 250 Description: "Unload compiled script from the VM and reset context to proper (possibly, historic) state.", 251 Action: handleReset, 252 }, 253 { 254 Name: "parse", 255 Usage: "Parse provided argument and convert it into other possible formats", 256 UsageText: `parse <arg>`, 257 Description: `<arg> is an argument which is tried to be interpreted as an item of different types 258 and converted to other formats. Strings are escaped and output in quotes.`, 259 Action: handleParse, 260 }, 261 { 262 Name: "run", 263 Usage: "Usage Execute the current loaded script", 264 UsageText: `run [<method> [<parameter>...]]`, 265 Description: `<method> is a contract method, specified in manifest. It can be '_' which will push 266 parameters onto the stack and execute from the current offset. 267 <parameter> is a parameter (can be repeated multiple times) that can be specified 268 using the same rules as for 'contract testinvokefunction' command: 269 270 ` + cmdargs.ParamsParsingDoc + ` 271 272 Example: 273 > run put int:5 string:some_string_value`, 274 Action: handleRun, 275 }, 276 { 277 Name: "cont", 278 Usage: "Continue execution of the current loaded script", 279 UsageText: "cont", 280 Description: "Continue execution of the current loaded script.", 281 Action: handleCont, 282 }, 283 { 284 Name: "step", 285 Usage: "Step (n) instruction in the program", 286 UsageText: `step [<n>]`, 287 Description: `<n> is optional parameter to specify number of instructions to run. 288 289 Example: 290 > step 10`, 291 Action: handleStep, 292 }, 293 { 294 Name: "stepinto", 295 Usage: "Stepinto instruction to take in the debugger", 296 UsageText: "stepinto", 297 Description: `Stepinto instruction to take in the debugger. 298 299 Example: 300 > stepinto`, 301 Action: handleStepInto, 302 }, 303 { 304 Name: "stepout", 305 Usage: "Stepout instruction to take in the debugger", 306 UsageText: "stepout", 307 Description: `Stepout instruction to take in the debugger. 308 309 Example: 310 > stepout`, 311 Action: handleStepOut, 312 }, 313 { 314 Name: "stepover", 315 Usage: "Stepover instruction to take in the debugger", 316 UsageText: "stepover", 317 Description: `Stepover instruction to take in the debugger. 318 319 Example: 320 > stepover`, 321 Action: handleStepOver, 322 }, 323 { 324 Name: "ops", 325 Usage: "Dump opcodes of the current loaded program", 326 UsageText: "ops", 327 Description: "Dump opcodes of the current loaded program", 328 Action: handleOps, 329 }, 330 { 331 Name: "events", 332 Usage: "Dump events emitted by the current loaded program", 333 UsageText: "events", 334 Description: "Dump events emitted by the current loaded program", 335 Action: handleEvents, 336 }, 337 { 338 Name: "env", 339 Usage: "Dump state of the chain that is used for VM CLI invocations (use -v for verbose node configuration)", 340 UsageText: `env [-v]`, 341 Flags: []cli.Flag{ 342 cli.BoolFlag{ 343 Name: verboseFlagFullName + ",v", 344 Usage: "Print the whole blockchain node configuration.", 345 }, 346 }, 347 Description: `Dump state of the chain that is used for VM CLI invocations (use -v for verbose node configuration). 348 349 Example: 350 > env -v`, 351 Action: handleEnv, 352 }, 353 { 354 Name: "storage", 355 Usage: "Dump storage of the contract with the specified hash, address or ID as is at the current stage of script invocation", 356 UsageText: `storage <hash-or-address-or-id> [<prefix>] [--backwards] [--diff]`, 357 Flags: []cli.Flag{ 358 cli.BoolFlag{ 359 Name: backwardsFlagFullName + ",b", 360 Usage: "Backwards traversal direction", 361 }, 362 cli.BoolFlag{ 363 Name: diffFlagFullName + ",d", 364 Usage: "Dump only those storage items that were added or changed during the current script invocation. Note that this call won't show removed storage items, use 'changes' command for that.", 365 }, 366 }, 367 Description: `Dump storage of the contract with the specified hash, address or ID as is at the current stage of script invocation. 368 Can be used if no script is loaded. 369 Hex-encoded storage items prefix may be specified (empty by default to return the whole set of storage items). 370 If seek prefix is not empty, then it's trimmed from the resulting keys. 371 Items are sorted. Backwards seek direction may be specified (false by default, which means forwards storage seek direction). 372 It is possible to dump only those storage items that were added or changed during current script invocation (use --diff flag for it). 373 To dump the whole set of storage changes including removed items use 'changes' command. 374 375 Example: 376 > storage 0x0000000009070e030d0f0e020d0c06050e030c02 030e --backwards --diff`, 377 Action: handleStorage, 378 }, 379 { 380 Name: "changes", 381 Usage: "Dump storage changes as is at the current stage of loaded script invocation", 382 UsageText: `changes [<hash-or-address-or-id> [<prefix>]]`, 383 Description: `Dump storage changes as is at the current stage of loaded script invocation. 384 If no script is loaded or executed, then no changes are present. 385 The contract hash, address or ID may be specified as the first parameter to dump the specified contract storage changes. 386 Hex-encoded search prefix (without contract ID) may be specified to dump matching storage changes. 387 Resulting values are not sorted. 388 389 Example: 390 > changes 0x0000000009070e030d0f0e020d0c06050e030c02 030e`, 391 Action: handleChanges, 392 }, 393 } 394 395 var completer *readline.PrefixCompleter 396 397 func init() { 398 var pcItems []readline.PrefixCompleterInterface 399 for _, c := range commands { 400 if !c.Hidden { 401 var flagsItems []readline.PrefixCompleterInterface 402 for _, f := range c.Flags { 403 names := strings.SplitN(f.GetName(), ", ", 2) // only long name will be offered 404 flagsItems = append(flagsItems, readline.PcItem("--"+names[0])) 405 } 406 pcItems = append(pcItems, readline.PcItem(c.Name, flagsItems...)) 407 } 408 } 409 completer = readline.NewPrefixCompleter(pcItems...) 410 } 411 412 // Various errors. 413 var ( 414 ErrMissingParameter = errors.New("missing argument") 415 ErrInvalidParameter = errors.New("can't parse argument") 416 ) 417 418 // CLI object for interacting with the VM. 419 type CLI struct { 420 chain *core.Blockchain 421 shell *cli.App 422 } 423 424 // NewWithConfig returns new CLI instance using provided config and (optionally) 425 // provided node config for state-backed VM. 426 func NewWithConfig(printLogotype bool, onExit func(int), c *readline.Config, cfg config.Config) (*CLI, error) { 427 if c.AutoComplete == nil { 428 // Autocomplete commands/flags on TAB. 429 c.AutoComplete = completer 430 } 431 l, err := readline.NewEx(c) 432 if err != nil { 433 return nil, fmt.Errorf("failed to create readline instance: %w", err) 434 } 435 ctl := cli.NewApp() 436 ctl.Name = "VM CLI" 437 438 // Note: need to set empty `ctl.HelpName` and `ctl.UsageText`, otherwise 439 // `filepath.Base(os.Args[0])` will be used which is `neo-go`. 440 ctl.HelpName = "" 441 ctl.UsageText = "" 442 443 ctl.Writer = l.Stdout() 444 ctl.ErrWriter = l.Stderr() 445 ctl.Version = config.Version 446 ctl.Usage = "Official VM CLI for NeoGo" 447 448 // Override default error handler in order not to exit on error. 449 ctl.ExitErrHandler = func(context *cli.Context, err error) {} 450 451 ctl.Commands = commands 452 453 store, err := storage.NewStore(cfg.ApplicationConfiguration.DBConfiguration) 454 if err != nil { 455 writeErr(ctl.ErrWriter, fmt.Errorf("failed to open DB, clean in-memory storage will be used: %w", err)) 456 cfg.ApplicationConfiguration.DBConfiguration.Type = dbconfig.InMemoryDB 457 store = storage.NewMemoryStore() 458 } 459 460 log, _, logCloser, err := options.HandleLoggingParams(false, cfg.ApplicationConfiguration) 461 if err != nil { 462 return nil, cli.NewExitError(fmt.Errorf("failed to init logger: %w", err), 1) 463 } 464 filter := zap.WrapCore(func(z zapcore.Core) zapcore.Core { 465 return options.NewFilteringCore(z, func(entry zapcore.Entry) bool { 466 // Log only Runtime.Notify messages. 467 return entry.Level == zapcore.InfoLevel && entry.Message == runtime.SystemRuntimeLogMessage 468 }) 469 }) 470 fLog := log.WithOptions(filter) 471 472 exitF := func(i int) { 473 _ = store.Close() 474 if logCloser != nil { 475 _ = logCloser() 476 } 477 onExit(i) 478 } 479 480 chain, err := core.NewBlockchain(store, cfg.Blockchain(), fLog) 481 if err != nil { 482 return nil, cli.NewExitError(fmt.Errorf("could not initialize blockchain: %w", err), 1) 483 } 484 // Do not run chain, we need only state-related functionality from it. 485 ic, err := chain.GetTestVM(trigger.Application, nil, nil) 486 if err != nil { 487 return nil, cli.NewExitError(fmt.Errorf("failed to create test VM: %w", err), 1) 488 } 489 490 vmcli := CLI{ 491 chain: chain, 492 shell: ctl, 493 } 494 495 vmcli.shell.Metadata = map[string]any{ 496 chainKey: chain, 497 chainCfgKey: cfg, 498 icKey: ic, 499 contractStateKey: new(state.ContractBase), 500 exitFuncKey: exitF, 501 readlineInstanceKey: l, 502 printLogoKey: printLogotype, 503 } 504 changePrompt(vmcli.shell) 505 return &vmcli, nil 506 } 507 508 func getExitFuncFromContext(app *cli.App) func(int) { 509 return app.Metadata[exitFuncKey].(func(int)) 510 } 511 512 func getReadlineInstanceFromContext(app *cli.App) *readline.Instance { 513 return app.Metadata[readlineInstanceKey].(*readline.Instance) 514 } 515 516 func getVMFromContext(app *cli.App) *vm.VM { 517 return getInteropContextFromContext(app).VM 518 } 519 520 func getChainFromContext(app *cli.App) *core.Blockchain { 521 return app.Metadata[chainKey].(*core.Blockchain) 522 } 523 524 func getChainConfigFromContext(app *cli.App) config.Config { 525 return app.Metadata[chainCfgKey].(config.Config) 526 } 527 528 func getInteropContextFromContext(app *cli.App) *interop.Context { 529 return app.Metadata[icKey].(*interop.Context) 530 } 531 532 func getContractStateFromContext(app *cli.App) *state.ContractBase { 533 return app.Metadata[contractStateKey].(*state.ContractBase) 534 } 535 536 func getPrintLogoFromContext(app *cli.App) bool { 537 return app.Metadata[printLogoKey].(bool) 538 } 539 540 func setInteropContextInContext(app *cli.App, ic *interop.Context) { 541 app.Metadata[icKey] = ic 542 } 543 544 func setContractStateInContext(app *cli.App, cs *state.ContractBase) { 545 app.Metadata[contractStateKey] = cs 546 } 547 548 func checkVMIsReady(app *cli.App) bool { 549 v := getVMFromContext(app) 550 if v == nil || !v.Ready() { 551 writeErr(app.Writer, errors.New("VM is not ready: no program loaded")) 552 return false 553 } 554 return true 555 } 556 557 func handleExit(c *cli.Context) error { 558 finalizeInteropContext(c.App) 559 l := getReadlineInstanceFromContext(c.App) 560 _ = l.Close() 561 exit := getExitFuncFromContext(c.App) 562 fmt.Fprintln(c.App.Writer, "Bye!") 563 exit(0) 564 return nil 565 } 566 567 func handleIP(c *cli.Context) error { 568 if !checkVMIsReady(c.App) { 569 return nil 570 } 571 v := getVMFromContext(c.App) 572 ctx := v.Context() 573 if ctx.NextIP() < ctx.LenInstr() { 574 ip, opcode := v.Context().NextInstr() 575 fmt.Fprintf(c.App.Writer, "instruction pointer at %d (%s)\n", ip, opcode) 576 } else { 577 fmt.Fprintln(c.App.Writer, "execution has finished") 578 } 579 return nil 580 } 581 582 func handleBreak(c *cli.Context) error { 583 if !checkVMIsReady(c.App) { 584 return nil 585 } 586 n, err := getInstructionParameter(c) 587 if err != nil { 588 return err 589 } 590 591 v := getVMFromContext(c.App) 592 v.AddBreakPoint(n) 593 fmt.Fprintf(c.App.Writer, "breakpoint added at instruction %d\n", n) 594 return nil 595 } 596 597 func handleJump(c *cli.Context) error { 598 if !checkVMIsReady(c.App) { 599 return nil 600 } 601 n, err := getInstructionParameter(c) 602 if err != nil { 603 return err 604 } 605 606 v := getVMFromContext(c.App) 607 v.Context().Jump(n) 608 fmt.Fprintf(c.App.Writer, "jumped to instruction %d\n", n) 609 return nil 610 } 611 612 func getInstructionParameter(c *cli.Context) (int, error) { 613 args := c.Args() 614 if len(args) != 1 { 615 return 0, fmt.Errorf("%w: <ip>", ErrMissingParameter) 616 } 617 n, err := strconv.Atoi(args[0]) 618 if err != nil { 619 return 0, fmt.Errorf("%w: %w", ErrInvalidParameter, err) 620 } 621 return n, nil 622 } 623 624 func handleXStack(c *cli.Context) error { 625 v := getVMFromContext(c.App) 626 var stackDump string 627 switch c.Command.Name { 628 case "estack": 629 stackDump = v.DumpEStack() 630 case "istack": 631 stackDump = v.DumpIStack() 632 default: 633 return errors.New("unknown stack") 634 } 635 fmt.Fprintln(c.App.Writer, stackDump) 636 return nil 637 } 638 639 func handleSlots(c *cli.Context) error { 640 v := getVMFromContext(c.App) 641 vmCtx := v.Context() 642 if vmCtx == nil { 643 return errors.New("no program loaded") 644 } 645 var rawSlot string 646 switch c.Command.Name { 647 case "sslot": 648 rawSlot = vmCtx.DumpStaticSlot() 649 case "lslot": 650 rawSlot = vmCtx.DumpLocalSlot() 651 case "aslot": 652 rawSlot = vmCtx.DumpArgumentsSlot() 653 default: 654 return errors.New("unknown slot") 655 } 656 fmt.Fprintln(c.App.Writer, rawSlot) 657 return nil 658 } 659 660 // prepareVM retrieves --historic flag from context (if set) and resets app state 661 // (to the specified historic height if given). 662 func prepareVM(c *cli.Context, tx *transaction.Transaction) error { 663 var err error 664 if c.IsSet(historicFlagFullName) { 665 height := c.Int(historicFlagFullName) 666 err = resetState(c.App, tx, uint32(height)) 667 } else { 668 err = resetState(c.App, tx) 669 } 670 if err != nil { 671 return err 672 } 673 if c.IsSet(gasFlagFullName) { 674 gas := c.Int64(gasFlagFullName) 675 v := getVMFromContext(c.App) 676 v.GasLimit = gas 677 } 678 return nil 679 } 680 681 func getHashFlag(c *cli.Context) (util.Uint160, error) { 682 if !c.IsSet(hashFlagFullName) { 683 return util.Uint160{}, nil 684 } 685 h, err := flags.ParseAddress(c.String(hashFlagFullName)) 686 if err != nil { 687 return util.Uint160{}, fmt.Errorf("failed to parse contract hash: %w", err) 688 } 689 return h, nil 690 } 691 692 func handleLoadNEF(c *cli.Context) error { 693 args := c.Args() 694 if len(args) < 1 { 695 return fmt.Errorf("%w: <nef> is required", ErrMissingParameter) 696 } 697 nefFile := args[0] 698 var ( 699 manifestFile string 700 signersStartOffset int 701 ) 702 if len(args) == 2 { 703 manifestFile = args[1] 704 } else if len(args) == 3 { 705 if args[1] != cmdargs.CosignersSeparator { 706 return fmt.Errorf("%w: `%s` was expected as the second parameter, got %s", ErrInvalidParameter, cmdargs.CosignersSeparator, args[1]) 707 } 708 signersStartOffset = 2 709 } else if len(args) > 3 { 710 if args[1] == cmdargs.CosignersSeparator { 711 signersStartOffset = 2 712 } else { 713 manifestFile = args[1] 714 if args[2] != cmdargs.CosignersSeparator { 715 return fmt.Errorf("%w: `%s` was expected as the third parameter, got %s", ErrInvalidParameter, cmdargs.CosignersSeparator, args[2]) 716 } 717 signersStartOffset = 3 718 } 719 } 720 if len(manifestFile) == 0 { 721 manifestFile = strings.TrimSuffix(nefFile, ".nef") + ".manifest.json" 722 } 723 b, err := os.ReadFile(nefFile) 724 if err != nil { 725 return err 726 } 727 nef, err := nef.FileFromBytes(b) 728 if err != nil { 729 return fmt.Errorf("failed to decode NEF file: %w", err) 730 } 731 m, err := getManifestFromFile(manifestFile) 732 if err != nil { 733 return fmt.Errorf("failed to read manifest: %w", err) 734 } 735 var signers []transaction.Signer 736 if signersStartOffset != 0 && len(args) > signersStartOffset { 737 signers, err = cmdargs.ParseSigners(c.Args()[signersStartOffset:]) 738 if err != nil { 739 return fmt.Errorf("%w: failed to parse signers: %w", ErrInvalidParameter, err) 740 } 741 } 742 err = prepareVM(c, createFakeTransaction(nef.Script, signers)) 743 if err != nil { 744 return err 745 } 746 h, err := getHashFlag(c) 747 if err != nil { 748 return err 749 } 750 cs := &state.ContractBase{ 751 Hash: h, 752 NEF: nef, 753 Manifest: *m, 754 } 755 setContractStateInContext(c.App, cs) 756 757 v := getVMFromContext(c.App) 758 fmt.Fprintf(c.App.Writer, "READY: loaded %d instructions\n", v.Context().LenInstr()) 759 changePrompt(c.App) 760 return nil 761 } 762 763 func handleLoadBase64(c *cli.Context) error { 764 args := c.Args() 765 if len(args) < 1 { 766 return fmt.Errorf("%w: <string>", ErrMissingParameter) 767 } 768 b, err := base64.StdEncoding.DecodeString(args[0]) 769 if err != nil { 770 return fmt.Errorf("%w: %w", ErrInvalidParameter, err) 771 } 772 var signers []transaction.Signer 773 if len(args) > 1 { 774 if args[1] != cmdargs.CosignersSeparator { 775 return fmt.Errorf("%w: `%s` was expected as the second parameter, got %s", ErrInvalidParameter, cmdargs.CosignersSeparator, args[1]) 776 } 777 if len(args) < 3 { 778 return fmt.Errorf("%w: signers expected after `%s`, got none", ErrInvalidParameter, cmdargs.CosignersSeparator) 779 } 780 signers, err = cmdargs.ParseSigners(args[2:]) 781 if err != nil { 782 return fmt.Errorf("%w: %w", ErrInvalidParameter, err) 783 } 784 } 785 err = prepareVM(c, createFakeTransaction(b, signers)) 786 if err != nil { 787 return err 788 } 789 v := getVMFromContext(c.App) 790 fmt.Fprintf(c.App.Writer, "READY: loaded %d instructions\n", v.Context().LenInstr()) 791 changePrompt(c.App) 792 return nil 793 } 794 795 // createFakeTransaction creates fake transaction with prefilled script, VUB and signers. 796 func createFakeTransaction(script []byte, signers []transaction.Signer) *transaction.Transaction { 797 return &transaction.Transaction{ 798 Script: script, 799 Signers: signers, 800 } 801 } 802 803 func handleLoadHex(c *cli.Context) error { 804 args := c.Args() 805 if len(args) < 1 { 806 return fmt.Errorf("%w: <string>", ErrMissingParameter) 807 } 808 b, err := hex.DecodeString(args[0]) 809 if err != nil { 810 return fmt.Errorf("%w: %w", ErrInvalidParameter, err) 811 } 812 var signers []transaction.Signer 813 if len(args) > 1 { 814 if args[1] != cmdargs.CosignersSeparator { 815 return fmt.Errorf("%w: `%s` was expected as the second parameter, got %s", ErrInvalidParameter, cmdargs.CosignersSeparator, args[1]) 816 } 817 if len(args) < 3 { 818 return fmt.Errorf("%w: signers expected after `%s`, got none", ErrInvalidParameter, cmdargs.CosignersSeparator) 819 } 820 signers, err = cmdargs.ParseSigners(args[2:]) 821 if err != nil { 822 return fmt.Errorf("%w: %w", ErrInvalidParameter, err) 823 } 824 } 825 err = prepareVM(c, createFakeTransaction(b, signers)) 826 if err != nil { 827 return err 828 } 829 v := getVMFromContext(c.App) 830 fmt.Fprintf(c.App.Writer, "READY: loaded %d instructions\n", v.Context().LenInstr()) 831 changePrompt(c.App) 832 return nil 833 } 834 835 func handleLoadGo(c *cli.Context) error { 836 args := c.Args() 837 if len(args) < 1 { 838 return fmt.Errorf("%w: <file>", ErrMissingParameter) 839 } 840 841 name := strings.TrimSuffix(args[0], ".go") 842 ne, di, err := compiler.CompileWithOptions(args[0], nil, &compiler.Options{Name: name}) 843 if err != nil { 844 return fmt.Errorf("failed to compile: %w", err) 845 } 846 847 // Don't perform checks, just load. 848 m, err := di.ConvertToManifest(&compiler.Options{}) 849 if err != nil { 850 return fmt.Errorf("can't create manifest: %w", err) 851 } 852 var signers []transaction.Signer 853 if len(args) > 1 { 854 if args[1] != cmdargs.CosignersSeparator { 855 return fmt.Errorf("%w: `%s` was expected as the second parameter, got %s", ErrInvalidParameter, cmdargs.CosignersSeparator, args[1]) 856 } 857 if len(args) < 3 { 858 return fmt.Errorf("%w: signers expected after `%s`, got none", ErrInvalidParameter, cmdargs.CosignersSeparator) 859 } 860 signers, err = cmdargs.ParseSigners(args[2:]) 861 if err != nil { 862 return fmt.Errorf("%w: %w", ErrInvalidParameter, err) 863 } 864 } 865 866 err = prepareVM(c, createFakeTransaction(ne.Script, signers)) 867 if err != nil { 868 return err 869 } 870 h, err := getHashFlag(c) 871 if err != nil { 872 return err 873 } 874 cs := &state.ContractBase{ 875 Hash: h, 876 NEF: *ne, 877 Manifest: *m, 878 } 879 setContractStateInContext(c.App, cs) 880 881 v := getVMFromContext(c.App) 882 fmt.Fprintf(c.App.Writer, "READY: loaded %d instructions\n", v.Context().LenInstr()) 883 changePrompt(c.App) 884 return nil 885 } 886 887 func handleLoadTx(c *cli.Context) error { 888 args := c.Args() 889 if len(args) < 1 { 890 return fmt.Errorf("%w: <file-or-hash>", ErrMissingParameter) 891 } 892 893 var ( 894 tx *transaction.Transaction 895 err error 896 ) 897 h, err := util.Uint256DecodeStringLE(strings.TrimPrefix(args[0], "0x")) 898 if err != nil { 899 pc, err := paramcontext.Read(args[0]) 900 if err != nil { 901 return fmt.Errorf("invalid tx hash or path to parameter context: %w", err) 902 } 903 var ok bool 904 tx, ok = pc.Verifiable.(*transaction.Transaction) 905 if !ok { 906 return errors.New("failed to retrieve transaction from parameter context: verifiable item is not a transaction") 907 } 908 } else { 909 bc := getChainFromContext(c.App) 910 tx, _, err = bc.GetTransaction(h) 911 if err != nil { 912 return fmt.Errorf("failed to get transaction from chain: %w", err) 913 } 914 } 915 err = prepareVM(c, tx) 916 if err != nil { 917 return err 918 } 919 v := getVMFromContext(c.App) 920 if v.GasLimit == -1 { 921 v.GasLimit = tx.SystemFee 922 } 923 fmt.Fprintf(c.App.Writer, "READY: loaded %d instructions\n", v.Context().LenInstr()) 924 changePrompt(c.App) 925 return nil 926 } 927 928 func handleLoadDeployed(c *cli.Context) error { 929 err := prepareVM(c, nil) // prepare historic IC if needed (for further historic contract state retrieving). 930 if err != nil { 931 return err 932 } 933 if !c.Args().Present() { 934 return errors.New("contract hash, address or ID is mandatory argument") 935 } 936 args := c.Args() 937 hashOrID := args[0] 938 ic := getInteropContextFromContext(c.App) 939 h, err := flags.ParseAddress(hashOrID) 940 if err != nil { 941 i, err := strconv.ParseInt(hashOrID, 10, 32) 942 if err != nil { 943 return fmt.Errorf("failed to parse contract hash, address or ID: %w", err) 944 } 945 h, err = native.GetContractScriptHash(ic.DAO, int32(i)) 946 if err != nil { 947 return fmt.Errorf("failed to retrieve contract hash by ID: %w", err) 948 } 949 } 950 cs, err := ic.GetContract(h) // will return historic contract state. 951 if err != nil { 952 return fmt.Errorf("contract %s not found: %w", h.StringLE(), err) 953 } 954 955 var signers []transaction.Signer 956 if len(args) > 1 { 957 if args[1] != cmdargs.CosignersSeparator { 958 return fmt.Errorf("%w: %s was expected as the second parameter, got %s", ErrInvalidParameter, cmdargs.CosignersSeparator, args[1]) 959 } 960 if len(args) < 3 { 961 return fmt.Errorf("%w: signers expected after `%s`, got none", ErrInvalidParameter, cmdargs.CosignersSeparator) 962 } 963 signers, err = cmdargs.ParseSigners(args[2:]) 964 if err != nil { 965 return fmt.Errorf("%w: %w", ErrInvalidParameter, err) 966 } 967 } 968 err = prepareVM(c, createFakeTransaction(cs.NEF.Script, signers)) // prepare VM one more time for proper IC initialization. 969 if err != nil { 970 return err 971 } 972 ic = getInteropContextFromContext(c.App) // fetch newly-created IC. 973 gasLimit := ic.VM.GasLimit 974 ic.ReuseVM(ic.VM) // clear previously loaded program and context. 975 ic.VM.GasLimit = gasLimit 976 ic.VM.LoadScriptWithHash(cs.NEF.Script, cs.Hash, callflag.All) 977 fmt.Fprintf(c.App.Writer, "READY: loaded %d instructions\n", ic.VM.Context().LenInstr()) 978 setContractStateInContext(c.App, &cs.ContractBase) 979 changePrompt(c.App) 980 return nil 981 } 982 983 func handleReset(c *cli.Context) error { 984 err := prepareVM(c, nil) 985 if err != nil { 986 return err 987 } 988 changePrompt(c.App) 989 return nil 990 } 991 992 // finalizeInteropContext calls finalizer for the current interop context. 993 func finalizeInteropContext(app *cli.App) { 994 ic := getInteropContextFromContext(app) 995 ic.Finalize() 996 } 997 998 // resetInteropContext calls finalizer for current interop context and replaces 999 // it with the newly created one. If transaction is provided, then its script is 1000 // loaded into bound VM. 1001 func resetInteropContext(app *cli.App, tx *transaction.Transaction, height ...uint32) error { 1002 finalizeInteropContext(app) 1003 bc := getChainFromContext(app) 1004 var ( 1005 newIc *interop.Context 1006 err error 1007 ) 1008 if len(height) != 0 { 1009 if tx != nil { 1010 tx.ValidUntilBlock = height[0] + 1 1011 } 1012 newIc, err = bc.GetTestHistoricVM(trigger.Application, tx, height[0]+1) 1013 if err != nil { 1014 return fmt.Errorf("failed to create historic VM for height %d: %w", height[0], err) 1015 } 1016 } else { 1017 if tx != nil { 1018 tx.ValidUntilBlock = bc.BlockHeight() + 1 1019 } 1020 newIc, err = bc.GetTestVM(trigger.Application, tx, nil) 1021 if err != nil { 1022 return fmt.Errorf("failed to create VM: %w", err) 1023 } 1024 } 1025 if tx != nil { 1026 newIc.VM.LoadWithFlags(tx.Script, callflag.All) 1027 } 1028 1029 setInteropContextInContext(app, newIc) 1030 return nil 1031 } 1032 1033 // resetContractState removes loaded contract state from app context. 1034 func resetContractState(app *cli.App) { 1035 setContractStateInContext(app, nil) 1036 } 1037 1038 // resetState resets state of the app (clear interop context and manifest) so that it's ready 1039 // to load new program. 1040 func resetState(app *cli.App, tx *transaction.Transaction, height ...uint32) error { 1041 err := resetInteropContext(app, tx, height...) 1042 if err != nil { 1043 return err 1044 } 1045 resetContractState(app) 1046 return nil 1047 } 1048 1049 func getManifestFromFile(name string) (*manifest.Manifest, error) { 1050 bs, err := os.ReadFile(name) 1051 if err != nil { 1052 return nil, fmt.Errorf("%w: can't read manifest", ErrInvalidParameter) 1053 } 1054 1055 var m manifest.Manifest 1056 if err := json.Unmarshal(bs, &m); err != nil { 1057 return nil, fmt.Errorf("%w: can't unmarshal manifest", ErrInvalidParameter) 1058 } 1059 return &m, nil 1060 } 1061 1062 func handleRun(c *cli.Context) error { 1063 v := getVMFromContext(c.App) 1064 cs := getContractStateFromContext(c.App) 1065 args := c.Args() 1066 if len(args) != 0 { 1067 var ( 1068 params []stackitem.Item 1069 offset int 1070 err error 1071 runCurrent = args[0] != "_" 1072 hasRet bool 1073 ) 1074 1075 _, scParams, err := cmdargs.ParseParams(args[1:], true) 1076 if err != nil { 1077 return fmt.Errorf("%w: %w", ErrInvalidParameter, err) 1078 } 1079 params = make([]stackitem.Item, len(scParams)) 1080 for i := range scParams { 1081 params[i], err = scParams[i].ToStackItem() 1082 if err != nil { 1083 return fmt.Errorf("failed to convert parameter #%d to stackitem: %w", i, err) 1084 } 1085 } 1086 if runCurrent { 1087 if cs == nil { 1088 return fmt.Errorf("manifest is not loaded; either use 'run' command to run loaded script from the start or use 'loadgo', 'loadnef' or 'loaddeployed' commands to provide manifest") 1089 } 1090 md := cs.Manifest.ABI.GetMethod(args[0], len(params)) 1091 if md == nil { 1092 return fmt.Errorf("%w: method not found", ErrInvalidParameter) 1093 } 1094 hasRet = md.ReturnType != smartcontract.VoidType 1095 offset = md.Offset 1096 var initOff = -1 1097 if initMD := cs.Manifest.ABI.GetMethod(manifest.MethodInit, 0); initMD != nil { 1098 initOff = initMD.Offset 1099 } 1100 1101 // Clear context loaded by 'loadgo', 'loadnef' or 'loaddeployed' to properly handle LoadNEFMethod. 1102 // At the same time, preserve previously set gas limit and the set of breakpoints. 1103 ic := getInteropContextFromContext(c.App) 1104 gasLimit := v.GasLimit 1105 breaks := v.Context().BreakPoints() // We ensure that there's a context loaded. 1106 ic.ReuseVM(v) 1107 v.GasLimit = gasLimit 1108 v.LoadNEFMethod(&cs.NEF, util.Uint160{}, cs.Hash, callflag.All, hasRet, offset, initOff, nil) 1109 for _, bp := range breaks { 1110 v.AddBreakPoint(bp) 1111 } 1112 } 1113 for i := len(params) - 1; i >= 0; i-- { 1114 v.Estack().PushVal(params[i]) 1115 } 1116 } 1117 runVMWithHandling(c) 1118 changePrompt(c.App) 1119 return nil 1120 } 1121 1122 // runVMWithHandling runs VM with handling errors and additional state messages. 1123 func runVMWithHandling(c *cli.Context) { 1124 v := getVMFromContext(c.App) 1125 err := v.Run() 1126 if err != nil { 1127 writeErr(c.App.ErrWriter, err) 1128 } 1129 1130 var ( 1131 message string 1132 dumpNtf bool 1133 ) 1134 switch { 1135 case v.HasFailed(): 1136 message = "" // the error will be printed on return 1137 dumpNtf = true 1138 case v.HasHalted(): 1139 message = v.DumpEStack() 1140 dumpNtf = true 1141 case v.AtBreakpoint(): 1142 ctx := v.Context() 1143 if ctx.NextIP() < ctx.LenInstr() { 1144 i, op := ctx.NextInstr() 1145 message = fmt.Sprintf("at breakpoint %d (%s)", i, op) 1146 } else { 1147 message = "execution has finished" 1148 } 1149 } 1150 if dumpNtf { 1151 var e string 1152 e, err = dumpEvents(c.App) 1153 if err == nil && len(e) != 0 { 1154 if message != "" { 1155 message += "\n" 1156 } 1157 message += "Events:\n" + e 1158 } 1159 } 1160 if message != "" { 1161 fmt.Fprintln(c.App.Writer, message) 1162 } 1163 } 1164 1165 func handleCont(c *cli.Context) error { 1166 if !checkVMIsReady(c.App) { 1167 return nil 1168 } 1169 runVMWithHandling(c) 1170 changePrompt(c.App) 1171 return nil 1172 } 1173 1174 func handleStep(c *cli.Context) error { 1175 var ( 1176 n = 1 1177 err error 1178 ) 1179 1180 if !checkVMIsReady(c.App) { 1181 return nil 1182 } 1183 v := getVMFromContext(c.App) 1184 args := c.Args() 1185 if len(args) > 0 { 1186 n, err = strconv.Atoi(args[0]) 1187 if err != nil { 1188 return fmt.Errorf("%w: %w", ErrInvalidParameter, err) 1189 } 1190 } 1191 v.AddBreakPointRel(n) 1192 runVMWithHandling(c) 1193 changePrompt(c.App) 1194 return nil 1195 } 1196 1197 func handleStepInto(c *cli.Context) error { 1198 return handleStepType(c, "into") 1199 } 1200 1201 func handleStepOut(c *cli.Context) error { 1202 return handleStepType(c, "out") 1203 } 1204 1205 func handleStepOver(c *cli.Context) error { 1206 return handleStepType(c, "over") 1207 } 1208 1209 func handleStepType(c *cli.Context, stepType string) error { 1210 if !checkVMIsReady(c.App) { 1211 return nil 1212 } 1213 v := getVMFromContext(c.App) 1214 var err error 1215 switch stepType { 1216 case "into": 1217 err = v.StepInto() 1218 case "out": 1219 err = v.StepOut() 1220 case "over": 1221 err = v.StepOver() 1222 } 1223 if err != nil { 1224 return err 1225 } 1226 _ = handleIP(c) 1227 changePrompt(c.App) 1228 return nil 1229 } 1230 1231 func handleOps(c *cli.Context) error { 1232 if !checkVMIsReady(c.App) { 1233 return nil 1234 } 1235 v := getVMFromContext(c.App) 1236 out := bytes.NewBuffer(nil) 1237 v.PrintOps(out) 1238 fmt.Fprintln(c.App.Writer, out.String()) 1239 return nil 1240 } 1241 1242 func changePrompt(app *cli.App) { 1243 v := getVMFromContext(app) 1244 l := getReadlineInstanceFromContext(app) 1245 if v.Ready() && v.Context().NextIP() >= 0 && v.Context().NextIP() < v.Context().LenInstr() { 1246 l.SetPrompt(fmt.Sprintf("\033[32mNEO-GO-VM %d >\033[0m ", v.Context().NextIP())) 1247 } else { 1248 l.SetPrompt("\033[32mNEO-GO-VM >\033[0m ") 1249 } 1250 } 1251 1252 func handleEvents(c *cli.Context) error { 1253 e, err := dumpEvents(c.App) 1254 if err != nil { 1255 writeErr(c.App.ErrWriter, err) 1256 return nil 1257 } 1258 fmt.Fprintln(c.App.Writer, e) 1259 return nil 1260 } 1261 1262 func handleEnv(c *cli.Context) error { 1263 bc := getChainFromContext(c.App) 1264 cfg := getChainConfigFromContext(c.App) 1265 ic := getInteropContextFromContext(c.App) 1266 message := fmt.Sprintf("Chain height: %d\nVM height (may differ from chain height in case of historic call): %d\nNetwork magic: %d\nDB type: %s\n", 1267 bc.BlockHeight(), ic.BlockHeight(), bc.GetConfig().Magic, cfg.ApplicationConfiguration.DBConfiguration.Type) 1268 if c.Bool(verboseFlagFullName) { 1269 cfgBytes, err := json.MarshalIndent(cfg, "", "\t") 1270 if err != nil { 1271 return fmt.Errorf("failed to marshal node configuration: %w", err) 1272 } 1273 message += "Node config:\n" + string(cfgBytes) + "\n" 1274 } 1275 fmt.Fprint(c.App.Writer, message) 1276 return nil 1277 } 1278 1279 func handleStorage(c *cli.Context) error { 1280 id, prefix, err := getDumpArgs(c) 1281 if err != nil { 1282 return err 1283 } 1284 var ( 1285 backwards bool 1286 seekDepth int 1287 ic = getInteropContextFromContext(c.App) 1288 ) 1289 if c.Bool(backwardsFlagFullName) { 1290 backwards = true 1291 } 1292 if c.Bool(diffFlagFullName) { 1293 seekDepth = 1 // take only upper DAO layer which stores only added or updated items. 1294 } 1295 ic.DAO.Seek(id, storage.SeekRange{ 1296 Prefix: prefix, 1297 Backwards: backwards, 1298 SearchDepth: seekDepth, 1299 }, func(k, v []byte) bool { 1300 fmt.Fprintf(c.App.Writer, "%s: %v\n", hex.EncodeToString(k), hex.EncodeToString(v)) 1301 return true 1302 }) 1303 return nil 1304 } 1305 1306 func handleChanges(c *cli.Context) error { 1307 var ( 1308 expectedID int32 1309 prefix []byte 1310 err error 1311 hasAgs = c.Args().Present() 1312 ) 1313 if hasAgs { 1314 expectedID, prefix, err = getDumpArgs(c) 1315 if err != nil { 1316 return err 1317 } 1318 } 1319 ic := getInteropContextFromContext(c.App) 1320 b := ic.DAO.GetBatch() 1321 if b == nil { 1322 return nil 1323 } 1324 ops := storage.BatchToOperations(b) 1325 var notFirst bool 1326 for _, op := range ops { 1327 id := int32(binary.LittleEndian.Uint32(op.Key)) 1328 if hasAgs && (expectedID != id || (len(prefix) != 0 && !bytes.HasPrefix(op.Key[4:], prefix))) { 1329 continue 1330 } 1331 var message string 1332 if notFirst { 1333 message += "\n" 1334 } 1335 message += fmt.Sprintf("Contract ID: %d\nState: %s\nKey: %s\n", id, op.State, hex.EncodeToString(op.Key[4:])) 1336 if op.Value != nil { 1337 message += fmt.Sprintf("Value: %s\n", hex.EncodeToString(op.Value)) 1338 } 1339 fmt.Fprint(c.App.Writer, message) 1340 notFirst = true 1341 } 1342 return nil 1343 } 1344 1345 // getDumpArgs is a helper function that retrieves contract ID and search prefix (if given). 1346 func getDumpArgs(c *cli.Context) (int32, []byte, error) { 1347 id, err := getContractID(c) 1348 if err != nil { 1349 return 0, nil, err 1350 } 1351 var prefix []byte 1352 if c.NArg() > 1 { 1353 prefix, err = hex.DecodeString(c.Args().Get(1)) 1354 if err != nil { 1355 return 0, nil, fmt.Errorf("failed to decode prefix from hex: %w", err) 1356 } 1357 } 1358 return id, prefix, nil 1359 } 1360 1361 // getContractID returns contract ID parsed from the first argument which can be ID, 1362 // hash or address. 1363 func getContractID(c *cli.Context) (int32, error) { 1364 if !c.Args().Present() { 1365 return 0, errors.New("contract hash, address or ID is mandatory argument") 1366 } 1367 hashOrID := c.Args().Get(0) 1368 var ic = getInteropContextFromContext(c.App) 1369 h, err := flags.ParseAddress(hashOrID) 1370 if err != nil { 1371 i, err := strconv.ParseInt(hashOrID, 10, 32) 1372 if err != nil { 1373 return 0, fmt.Errorf("failed to parse contract hash, address or ID: %w", err) 1374 } 1375 return int32(i), nil 1376 } 1377 cs, err := ic.GetContract(h) 1378 if err != nil { 1379 return 0, fmt.Errorf("contract %s not found: %w", h.StringLE(), err) 1380 } 1381 return cs.ID, nil 1382 } 1383 1384 func dumpEvents(app *cli.App) (string, error) { 1385 ic := getInteropContextFromContext(app) 1386 if len(ic.Notifications) == 0 { 1387 return "", nil 1388 } 1389 b, err := json.MarshalIndent(ic.Notifications, "", "\t") 1390 if err != nil { 1391 return "", fmt.Errorf("failed to marshal notifications: %w", err) 1392 } 1393 return string(b), nil 1394 } 1395 1396 // Run waits for user input from Stdin and executes the passed command. 1397 func (c *CLI) Run() error { 1398 if getPrintLogoFromContext(c.shell) { 1399 printLogo(c.shell.Writer) 1400 } 1401 l := getReadlineInstanceFromContext(c.shell) 1402 for { 1403 line, err := l.Readline() 1404 if errors.Is(err, io.EOF) || errors.Is(err, readline.ErrInterrupt) { 1405 return nil // OK, stop execution. 1406 } 1407 if err != nil { 1408 return fmt.Errorf("failed to read input: %w", err) // Critical error, stop execution. 1409 } 1410 1411 args, err := shellquote.Split(line) 1412 if err != nil { 1413 writeErr(c.shell.ErrWriter, fmt.Errorf("failed to parse arguments: %w", err)) 1414 continue // Not a critical error, continue execution. 1415 } 1416 1417 err = c.shell.Run(append([]string{"vm"}, args...)) 1418 if err != nil { 1419 writeErr(c.shell.ErrWriter, err) // Various command/flags parsing errors and execution errors. 1420 } 1421 } 1422 } 1423 1424 func handleParse(c *cli.Context) error { 1425 res, err := Parse(c.Args()) 1426 if err != nil { 1427 return err 1428 } 1429 fmt.Fprintln(c.App.Writer, res) 1430 return nil 1431 } 1432 1433 // Parse converts it's argument to other formats. 1434 func Parse(args []string) (string, error) { 1435 if len(args) < 1 { 1436 return "", ErrMissingParameter 1437 } 1438 arg := args[0] 1439 var buf []byte 1440 if val, err := strconv.ParseInt(arg, 10, 64); err == nil { 1441 bs := bigint.ToBytes(big.NewInt(val)) 1442 buf = fmt.Appendf(buf, "Integer to Hex\t%s\n", hex.EncodeToString(bs)) 1443 buf = fmt.Appendf(buf, "Integer to Base64\t%s\n", base64.StdEncoding.EncodeToString(bs)) 1444 } 1445 noX := strings.TrimPrefix(arg, "0x") 1446 if rawStr, err := hex.DecodeString(noX); err == nil { 1447 if val, err := util.Uint160DecodeBytesBE(rawStr); err == nil { 1448 buf = fmt.Appendf(buf, "BE ScriptHash to Address\t%s\n", address.Uint160ToString(val)) 1449 buf = fmt.Appendf(buf, "LE ScriptHash to Address\t%s\n", address.Uint160ToString(val.Reverse())) 1450 } 1451 if pub, err := keys.NewPublicKeyFromBytes(rawStr, elliptic.P256()); err == nil { 1452 sh := pub.GetScriptHash() 1453 buf = fmt.Appendf(buf, "Public key to BE ScriptHash\t%s\n", sh) 1454 buf = fmt.Appendf(buf, "Public key to LE ScriptHash\t%s\n", sh.Reverse()) 1455 buf = fmt.Appendf(buf, "Public key to Address\t%s\n", address.Uint160ToString(sh)) 1456 } 1457 buf = fmt.Appendf(buf, "Hex to String\t%s\n", fmt.Sprintf("%q", string(rawStr))) 1458 buf = fmt.Appendf(buf, "Hex to Integer\t%s\n", bigint.FromBytes(rawStr)) 1459 buf = fmt.Appendf(buf, "Swap Endianness\t%s\n", hex.EncodeToString(slice.CopyReverse(rawStr))) 1460 } 1461 if addr, err := address.StringToUint160(arg); err == nil { 1462 buf = fmt.Appendf(buf, "Address to BE ScriptHash\t%s\n", addr) 1463 buf = fmt.Appendf(buf, "Address to LE ScriptHash\t%s\n", addr.Reverse()) 1464 buf = fmt.Appendf(buf, "Address to Base64 (BE)\t%s\n", base64.StdEncoding.EncodeToString(addr.BytesBE())) 1465 buf = fmt.Appendf(buf, "Address to Base64 (LE)\t%s\n", base64.StdEncoding.EncodeToString(addr.BytesLE())) 1466 } 1467 if rawStr, err := base64.StdEncoding.DecodeString(arg); err == nil { 1468 buf = fmt.Appendf(buf, "Base64 to String\t%s\n", fmt.Sprintf("%q", string(rawStr))) 1469 buf = fmt.Appendf(buf, "Base64 to BigInteger\t%s\n", bigint.FromBytes(rawStr)) 1470 if u, err := util.Uint160DecodeBytesBE(rawStr); err == nil { 1471 buf = fmt.Appendf(buf, "Base64 to BE ScriptHash\t%s\n", u.StringBE()) 1472 buf = fmt.Appendf(buf, "Base64 to LE ScriptHash\t%s\n", u.StringLE()) 1473 buf = fmt.Appendf(buf, "Base64 to Address (BE)\t%s\n", address.Uint160ToString(u)) 1474 buf = fmt.Appendf(buf, "Base64 to Address (LE)\t%s\n", address.Uint160ToString(u.Reverse())) 1475 } 1476 } 1477 1478 buf = fmt.Appendf(buf, "String to Hex\t%s\n", hex.EncodeToString([]byte(arg))) 1479 buf = fmt.Appendf(buf, "String to Base64\t%s\n", base64.StdEncoding.EncodeToString([]byte(arg))) 1480 1481 res := bytes.NewBuffer(nil) 1482 w := tabwriter.NewWriter(res, 0, 4, 4, '\t', 0) 1483 if _, err := w.Write(buf); err != nil { 1484 return "", err 1485 } 1486 if err := w.Flush(); err != nil { 1487 return "", err 1488 } 1489 return res.String(), nil 1490 } 1491 1492 const logo = ` 1493 _ ____________ __________ _ ____ ___ 1494 / | / / ____/ __ \ / ____/ __ \ | | / / |/ / 1495 / |/ / __/ / / / /_____/ / __/ / / /____| | / / /|_/ / 1496 / /| / /___/ /_/ /_____/ /_/ / /_/ /_____/ |/ / / / / 1497 /_/ |_/_____/\____/ \____/\____/ |___/_/ /_/ 1498 ` 1499 1500 func printLogo(w io.Writer) { 1501 fmt.Fprint(w, logo) 1502 fmt.Fprintln(w) 1503 fmt.Fprintln(w) 1504 fmt.Fprintln(w) 1505 } 1506 1507 func writeErr(w io.Writer, err error) { 1508 fmt.Fprintf(w, "Error: %s\n", err) 1509 }