diff --git a/cli/vm/cli.go b/cli/vm/cli.go index 27f01a7f6..36b021ab7 100644 --- a/cli/vm/cli.go +++ b/cli/vm/cli.go @@ -60,15 +60,22 @@ const ( const ( verboseFlagFullName = "verbose" historicFlagFullName = "historic" + gasFlagFullName = "gas" backwardsFlagFullName = "backwards" diffFlagFullName = "diff" ) -var historicFlag = cli.IntFlag{ - Name: historicFlagFullName, - Usage: "Height for historic script invocation (for MPT-enabled blockchain configuration with KeepOnlyLatestState setting disabled). " + - "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.", -} +var ( + historicFlag = cli.IntFlag{ + Name: historicFlagFullName, + Usage: "Height for historic script invocation (for MPT-enabled blockchain configuration with KeepOnlyLatestState setting disabled). " + + "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.", + } + gasFlag = cli.Int64Flag{ + Name: gasFlagFullName, + Usage: "GAS limit for this execution (integer number, satoshi).", + } +) var commands = []cli.Command{ { @@ -143,8 +150,8 @@ Example: { Name: "loadnef", Usage: "Load a NEF-consistent script into the VM optionally attaching to it provided signers with scopes", - UsageText: `loadnef [--historic ] [, ...]`, - Flags: []cli.Flag{historicFlag}, + UsageText: `loadnef [--historic ] [--gas ] [, ...]`, + Flags: []cli.Flag{historicFlag, gasFlag}, Description: ` and parameters are mandatory. ` + cmdargs.SignersParsingDoc + ` @@ -156,8 +163,8 @@ Example: { Name: "loadbase64", Usage: "Load a base64-encoded script string into the VM optionally attaching to it provided signers with scopes", - UsageText: `loadbase64 [--historic ] [, ...]`, - Flags: []cli.Flag{historicFlag}, + UsageText: `loadbase64 [--historic ] [--gas ] [, ...]`, + Flags: []cli.Flag{historicFlag, gasFlag}, Description: ` is mandatory parameter. ` + cmdargs.SignersParsingDoc + ` @@ -169,8 +176,8 @@ Example: { Name: "loadhex", Usage: "Load a hex-encoded script string into the VM optionally attaching to it provided signers with scopes", - UsageText: `loadhex [--historic ] [, ...]`, - Flags: []cli.Flag{historicFlag}, + UsageText: `loadhex [--historic ] [--gas ] [, ...]`, + Flags: []cli.Flag{historicFlag, gasFlag}, Description: ` is mandatory parameter. ` + cmdargs.SignersParsingDoc + ` @@ -182,8 +189,8 @@ Example: { Name: "loadgo", Usage: "Compile and load a Go file with the manifest into the VM optionally attaching to it provided signers with scopes", - UsageText: `loadgo [--historic ] [, ...]`, - Flags: []cli.Flag{historicFlag}, + UsageText: `loadgo [--historic ] [--gas ] [, ...]`, + Flags: []cli.Flag{historicFlag, gasFlag}, Description: ` is mandatory parameter. ` + cmdargs.SignersParsingDoc + ` @@ -195,10 +202,13 @@ Example: { Name: "loadtx", Usage: "Load transaction into the VM from chain or from parameter context file", - UsageText: `loadtx [--historic ] `, - Flags: []cli.Flag{historicFlag}, + UsageText: `loadtx [--historic ] [--gas ] `, + Flags: []cli.Flag{historicFlag, gasFlag}, Description: `Load transaction into the VM from chain or from parameter context file. -The transaction script will be loaded into VM; the resulting execution context will use the provided transaction as script container including its signers, hash and nonce. + The transaction script will be loaded into VM; the resulting execution context + will use the provided transaction as script container including its signers, + hash and nonce. It'll also use transaction's system fee value as GAS limit if + --gas option is not used. is mandatory parameter. @@ -209,8 +219,8 @@ Example: { Name: "loaddeployed", Usage: "Load deployed contract into the VM from chain optionally attaching to it provided signers with scopes", - UsageText: `loaddeployed [--historic ] [, ...]`, - Flags: []cli.Flag{historicFlag}, + UsageText: `loaddeployed [--historic ] [--gas ] [, ...]`, + Flags: []cli.Flag{historicFlag, gasFlag}, Description: `Load deployed contract into the VM from chain optionally attaching to it provided signers with scopes. If '--historic' flag specified, then the historic contract state (historic script and manifest) will be loaded. @@ -640,12 +650,22 @@ func handleSlots(c *cli.Context) error { // prepareVM retrieves --historic flag from context (if set) and resets app state // (to the specified historic height if given). func prepareVM(c *cli.Context, tx *transaction.Transaction) error { + var err error if c.IsSet(historicFlagFullName) { height := c.Int(historicFlagFullName) - return resetState(c.App, tx, uint32(height)) + err = resetState(c.App, tx, uint32(height)) + } else { + err = resetState(c.App, tx) } - - return resetState(c.App, tx) + if err != nil { + return err + } + if c.IsSet(gasFlagFullName) { + gas := c.Int64(gasFlagFullName) + v := getVMFromContext(c.App) + v.GasLimit = gas + } + return nil } func handleLoadNEF(c *cli.Context) error { @@ -812,6 +832,9 @@ func handleLoadTx(c *cli.Context) error { return err } v := getVMFromContext(c.App) + if v.GasLimit == -1 { + v.GasLimit = tx.SystemFee + } fmt.Fprintf(c.App.Writer, "READY: loaded %d instructions\n", v.Context().LenInstr()) changePrompt(c.App) return nil @@ -856,7 +879,9 @@ func handleLoadDeployed(c *cli.Context) error { return err } ic = getInteropContextFromContext(c.App) // fetch newly-created IC. - ic.ReuseVM(ic.VM) // clear previously loaded program and context. + gasLimit := ic.VM.GasLimit + ic.ReuseVM(ic.VM) // clear previously loaded program and context. + ic.VM.GasLimit = gasLimit ic.VM.LoadScriptWithHash(cs.NEF.Script, cs.Hash, callflag.All) fmt.Fprintf(c.App.Writer, "READY: loaded %d instructions\n", ic.VM.Context().LenInstr()) setManifestInContext(c.App, &cs.Manifest) diff --git a/cli/vm/cli_test.go b/cli/vm/cli_test.go index 2ee983055..0fdc20964 100644 --- a/cli/vm/cli_test.go +++ b/cli/vm/cli_test.go @@ -1118,6 +1118,8 @@ func TestLoadtx(t *testing.T) { e.runProg(t, "loadtx "+tx.Hash().StringLE(), // hash LE "run", + "loadtx --gas 10000 "+tx.Hash().StringLE(), // with GAS + "run", "loadtx 0x"+tx.Hash().StringLE(), // hash LE with 0x prefix "run", "loadtx '"+tmp+"'", // Tx from parameter context file. @@ -1128,6 +1130,8 @@ func TestLoadtx(t *testing.T) { e.checkNextLine(t, "READY: loaded \\d+ instructions") e.checkStack(t, 1) e.checkNextLine(t, "READY: loaded \\d+ instructions") + e.checkError(t, errors.New("at instruction 3 (PACK): gas limit is exceeded")) + e.checkNextLine(t, "READY: loaded \\d+ instructions") e.checkStack(t, 1) e.checkNextLine(t, "READY: loaded \\d+ instructions") e.checkStack(t, 1) @@ -1147,6 +1151,8 @@ func TestLoaddeployed(t *testing.T) { e.runProg(t, "loaddeployed "+h.StringLE(), // hash LE "run get 1", + "loaddeployed --gas 420000 "+h.StringLE(), // gas-limited + "run get 1", "loaddeployed 0x"+h.StringLE(), // hash LE with 0x prefix "run get 1", "loaddeployed 1", // contract ID @@ -1172,6 +1178,8 @@ func TestLoaddeployed(t *testing.T) { e.checkNextLine(t, "READY: loaded \\d+ instructions") e.checkStack(t, []byte{2}) e.checkNextLine(t, "READY: loaded \\d+ instructions") + e.checkError(t, errors.New("at instruction 63 (SYSCALL): failed to invoke syscall 837311890: insufficient amount of gas")) + e.checkNextLine(t, "READY: loaded \\d+ instructions") e.checkStack(t, []byte{2}) e.checkNextLine(t, "READY: loaded \\d+ instructions") e.checkStack(t, []byte{2}) diff --git a/pkg/vm/stackitem/serialization.go b/pkg/vm/stackitem/serialization.go index 0ddccfe61..03cfa8ee1 100644 --- a/pkg/vm/stackitem/serialization.go +++ b/pkg/vm/stackitem/serialization.go @@ -7,6 +7,7 @@ import ( "github.com/nspcc-dev/neo-go/pkg/encoding/bigint" "github.com/nspcc-dev/neo-go/pkg/io" + "github.com/nspcc-dev/neo-go/pkg/util" ) // MaxDeserialized is the maximum number one deserialized item can contain @@ -168,6 +169,13 @@ func (w *SerializationContext) serialize(item Item) error { } else { return fmt.Errorf("%w: Interop", ErrUnserializable) } + case *Pointer: + if w.allowInvalid { + w.data = append(w.data, byte(PointerT)) + w.appendVarUint(uint64(t.pos)) + } else { + return fmt.Errorf("%w: Pointer", ErrUnserializable) + } case *Array: w.data = append(w.data, byte(ArrayT)) if err := w.writeArray(item, t.value, start); err != nil { @@ -311,6 +319,12 @@ func (r *deserContext) decodeBinary() Item { return NewInterop(nil) } fallthrough + case PointerT: + if r.allowInvalid { + pos := int(r.ReadVarUint()) + return NewPointerWithHash(pos, nil, util.Uint160{}) + } + fallthrough default: if t == InvalidT && r.allowInvalid { return nil diff --git a/pkg/vm/stackitem/serialization_test.go b/pkg/vm/stackitem/serialization_test.go index 9b496dd52..8378536d1 100644 --- a/pkg/vm/stackitem/serialization_test.go +++ b/pkg/vm/stackitem/serialization_test.go @@ -76,6 +76,7 @@ func TestSerialize(t *testing.T) { }) t.Run("invalid", func(t *testing.T) { testSerialize(t, ErrUnserializable, NewInterop(42)) + testSerialize(t, ErrUnserializable, NewPointer(42, []byte{})) testSerialize(t, ErrUnserializable, nil) t.Run("protected interop", func(t *testing.T) { @@ -93,6 +94,22 @@ func TestSerialize(t *testing.T) { require.NoError(t, r.Err) require.IsType(t, (*Interop)(nil), item) }) + t.Run("protected pointer", func(t *testing.T) { + w := io.NewBufBinWriter() + EncodeBinaryProtected(NewPointer(42, []byte{}), w.BinWriter) + require.NoError(t, w.Err) + + data := w.Bytes() + r := io.NewBinReaderFromBuf(data) + DecodeBinary(r) + require.Error(t, r.Err) + + r = io.NewBinReaderFromBuf(data) + item := DecodeBinaryProtected(r) + require.NoError(t, r.Err) + require.IsType(t, (*Pointer)(nil), item) + require.Equal(t, 42, item.Value()) + }) t.Run("protected nil", func(t *testing.T) { w := io.NewBufBinWriter() EncodeBinaryProtected(nil, w.BinWriter)