diff --git a/pkg/compiler/util_test.go b/pkg/compiler/util_test.go index 996a466b8..67eeb8118 100644 --- a/pkg/compiler/util_test.go +++ b/pkg/compiler/util_test.go @@ -23,6 +23,7 @@ func TestSHA256(t *testing.T) { ` v := vmAndCompile(t, src) ic := &interop.Context{Trigger: trigger.Verification} + ic.VM = v crypto.Register(ic) v.SyscallHandler = ic.SyscallHandler require.NoError(t, v.Run()) diff --git a/pkg/core/interop/callback/callback.go b/pkg/core/interop/callback/callback.go index e32afd2bd..6ea266341 100644 --- a/pkg/core/interop/callback/callback.go +++ b/pkg/core/interop/callback/callback.go @@ -18,19 +18,19 @@ type Callback interface { } // Invoke invokes provided callback. -func Invoke(ic *interop.Context, v *vm.VM) error { - cb := v.Estack().Pop().Interop().Value().(Callback) - args := v.Estack().Pop().Array() +func Invoke(ic *interop.Context) error { + cb := ic.VM.Estack().Pop().Interop().Value().(Callback) + args := ic.VM.Estack().Pop().Array() if cb.ArgCount() != len(args) { return errors.New("invalid argument count") } - cb.LoadContext(v, args) + cb.LoadContext(ic.VM, args) switch t := cb.(type) { case *MethodCallback: id := emit.InteropNameToID([]byte("System.Contract.Call")) - return ic.SyscallHandler(v, id) + return ic.SyscallHandler(ic.VM, id) case *SyscallCallback: - return ic.SyscallHandler(v, t.desc.ID) + return ic.SyscallHandler(ic.VM, t.desc.ID) default: return nil } diff --git a/pkg/core/interop/callback/method.go b/pkg/core/interop/callback/method.go index 4eade208d..d595a5a37 100644 --- a/pkg/core/interop/callback/method.go +++ b/pkg/core/interop/callback/method.go @@ -33,8 +33,8 @@ func (s *MethodCallback) LoadContext(v *vm.VM, args []stackitem.Item) { } // CreateFromMethod creates callback for a contract method. -func CreateFromMethod(ic *interop.Context, v *vm.VM) error { - rawHash := v.Estack().Pop().Bytes() +func CreateFromMethod(ic *interop.Context) error { + rawHash := ic.VM.Estack().Pop().Bytes() h, err := util.Uint160DecodeBytesBE(rawHash) if err != nil { return err @@ -43,16 +43,16 @@ func CreateFromMethod(ic *interop.Context, v *vm.VM) error { if err != nil { return errors.New("contract not found") } - method := string(v.Estack().Pop().Bytes()) + method := string(ic.VM.Estack().Pop().Bytes()) if strings.HasPrefix(method, "_") { return errors.New("invalid method name") } - currCs, err := ic.DAO.GetContractState(v.GetCurrentScriptHash()) + currCs, err := ic.DAO.GetContractState(ic.VM.GetCurrentScriptHash()) if err == nil && !currCs.Manifest.CanCall(&cs.Manifest, method) { return errors.New("method call is not allowed") } md := cs.Manifest.ABI.GetMethod(method) - v.Estack().PushVal(stackitem.NewInterop(&MethodCallback{ + ic.VM.Estack().PushVal(stackitem.NewInterop(&MethodCallback{ contract: cs, method: md, })) diff --git a/pkg/core/interop/callback/pointer.go b/pkg/core/interop/callback/pointer.go index 87963ae6a..2a896b7e6 100644 --- a/pkg/core/interop/callback/pointer.go +++ b/pkg/core/interop/callback/pointer.go @@ -29,11 +29,11 @@ func (p *PointerCallback) LoadContext(v *vm.VM, args []stackitem.Item) { } // Create creates callback using pointer and parameters count. -func Create(_ *interop.Context, v *vm.VM) error { - ctx := v.Estack().Pop().Item().(*vm.Context) - offset := v.Estack().Pop().Item().(*stackitem.Pointer).Position() - count := v.Estack().Pop().BigInt().Int64() - v.Estack().PushVal(stackitem.NewInterop(&PointerCallback{ +func Create(ic *interop.Context) error { + ctx := ic.VM.Estack().Pop().Item().(*vm.Context) + offset := ic.VM.Estack().Pop().Item().(*stackitem.Pointer).Position() + count := ic.VM.Estack().Pop().BigInt().Int64() + ic.VM.Estack().PushVal(stackitem.NewInterop(&PointerCallback{ paramCount: int(count), offset: offset, context: ctx, diff --git a/pkg/core/interop/callback/syscall.go b/pkg/core/interop/callback/syscall.go index d52c091d3..a7d078104 100644 --- a/pkg/core/interop/callback/syscall.go +++ b/pkg/core/interop/callback/syscall.go @@ -28,8 +28,8 @@ func (p *SyscallCallback) LoadContext(v *vm.VM, args []stackitem.Item) { } // CreateFromSyscall creates callback from syscall. -func CreateFromSyscall(ic *interop.Context, v *vm.VM) error { - id := uint32(v.Estack().Pop().BigInt().Int64()) +func CreateFromSyscall(ic *interop.Context) error { + id := uint32(ic.VM.Estack().Pop().BigInt().Int64()) f := ic.GetFunction(id) if f == nil { return errors.New("syscall not found") @@ -37,6 +37,6 @@ func CreateFromSyscall(ic *interop.Context, v *vm.VM) error { if f.DisallowCallback { return errors.New("syscall is not allowed to be used in a callback") } - v.Estack().PushVal(stackitem.NewInterop(&SyscallCallback{f})) + ic.VM.Estack().PushVal(stackitem.NewInterop(&SyscallCallback{f})) return nil } diff --git a/pkg/core/interop/context.go b/pkg/core/interop/context.go index c35bf7936..ad1dbb958 100644 --- a/pkg/core/interop/context.go +++ b/pkg/core/interop/context.go @@ -35,7 +35,7 @@ type Context struct { Notifications []state.NotificationEvent Log *zap.Logger Invocations map[util.Uint160]int - ScriptGetter vm.ScriptHashGetter + VM *vm.VM Functions [][]Function } @@ -64,7 +64,7 @@ func NewContext(trigger trigger.Type, bc blockchainer.Blockchainer, d dao.DAO, n type Function struct { ID uint32 Name string - Func func(*Context, *vm.VM) error + Func func(*Context) error // DisallowCallback is true iff syscall can't be used in a callback. DisallowCallback bool // ParamCount is a number of function parameters. @@ -155,26 +155,26 @@ func (ic *Context) GetFunction(id uint32) *Function { } // SyscallHandler handles syscall with id. -func (ic *Context) SyscallHandler(v *vm.VM, id uint32) error { +func (ic *Context) SyscallHandler(_ *vm.VM, id uint32) error { f := ic.GetFunction(id) if f == nil { return errors.New("syscall not found") } - cf := v.Context().GetCallFlags() + cf := ic.VM.Context().GetCallFlags() if !cf.Has(f.RequiredFlags) { return fmt.Errorf("missing call flags: %05b vs %05b", cf, f.RequiredFlags) } - if !v.AddGas(f.Price) { + if !ic.VM.AddGas(f.Price) { return errors.New("insufficient amount of gas") } - return f.Func(ic, v) + return f.Func(ic) } -// SpawnVM spawns new VM with the specified gas limit. +// SpawnVM spawns new VM with the specified gas limit and set context.VM field. func (ic *Context) SpawnVM() *vm.VM { v := vm.NewWithTrigger(ic.Trigger) v.GasLimit = -1 v.SyscallHandler = ic.SyscallHandler - ic.ScriptGetter = v + ic.VM = v return v } diff --git a/pkg/core/interop/crypto/ecdsa.go b/pkg/core/interop/crypto/ecdsa.go index 333cc63ff..faf5526f3 100644 --- a/pkg/core/interop/crypto/ecdsa.go +++ b/pkg/core/interop/crypto/ecdsa.go @@ -18,56 +18,56 @@ import ( const ECDSAVerifyPrice = 1000000 // ECDSASecp256r1Verify checks ECDSA signature using Secp256r1 elliptic curve. -func ECDSASecp256r1Verify(ic *interop.Context, v *vm.VM) error { - return ecdsaVerify(ic, v, elliptic.P256()) +func ECDSASecp256r1Verify(ic *interop.Context) error { + return ecdsaVerify(ic, elliptic.P256()) } // ECDSASecp256k1Verify checks ECDSA signature using Secp256k1 elliptic curve -func ECDSASecp256k1Verify(ic *interop.Context, v *vm.VM) error { - return ecdsaVerify(ic, v, btcec.S256()) +func ECDSASecp256k1Verify(ic *interop.Context) error { + return ecdsaVerify(ic, btcec.S256()) } // ecdsaVerify is internal representation of ECDSASecp256k1Verify and // ECDSASecp256r1Verify. -func ecdsaVerify(ic *interop.Context, v *vm.VM, curve elliptic.Curve) error { - msg := getMessage(ic, v.Estack().Pop().Item()) +func ecdsaVerify(ic *interop.Context, curve elliptic.Curve) error { + msg := getMessage(ic, ic.VM.Estack().Pop().Item()) hashToCheck := hash.Sha256(msg).BytesBE() - keyb := v.Estack().Pop().Bytes() - signature := v.Estack().Pop().Bytes() + keyb := ic.VM.Estack().Pop().Bytes() + signature := ic.VM.Estack().Pop().Bytes() pkey, err := keys.NewPublicKeyFromBytes(keyb, curve) if err != nil { return err } res := pkey.Verify(signature, hashToCheck) - v.Estack().PushVal(res) + ic.VM.Estack().PushVal(res) return nil } // ECDSASecp256r1CheckMultisig checks multiple ECDSA signatures at once using // Secp256r1 elliptic curve. -func ECDSASecp256r1CheckMultisig(ic *interop.Context, v *vm.VM) error { - return ecdsaCheckMultisig(ic, v, elliptic.P256()) +func ECDSASecp256r1CheckMultisig(ic *interop.Context) error { + return ecdsaCheckMultisig(ic, elliptic.P256()) } // ECDSASecp256k1CheckMultisig checks multiple ECDSA signatures at once using // Secp256k1 elliptic curve. -func ECDSASecp256k1CheckMultisig(ic *interop.Context, v *vm.VM) error { - return ecdsaCheckMultisig(ic, v, btcec.S256()) +func ECDSASecp256k1CheckMultisig(ic *interop.Context) error { + return ecdsaCheckMultisig(ic, btcec.S256()) } // ecdsaCheckMultisig is internal representation of ECDSASecp256r1CheckMultisig and // ECDSASecp256k1CheckMultisig -func ecdsaCheckMultisig(ic *interop.Context, v *vm.VM, curve elliptic.Curve) error { - msg := getMessage(ic, v.Estack().Pop().Item()) +func ecdsaCheckMultisig(ic *interop.Context, curve elliptic.Curve) error { + msg := getMessage(ic, ic.VM.Estack().Pop().Item()) hashToCheck := hash.Sha256(msg).BytesBE() - pkeys, err := v.Estack().PopSigElements() + pkeys, err := ic.VM.Estack().PopSigElements() if err != nil { return fmt.Errorf("wrong parameters: %w", err) } - if !v.AddGas(ECDSAVerifyPrice * int64(len(pkeys))) { + if !ic.VM.AddGas(ECDSAVerifyPrice * int64(len(pkeys))) { return errors.New("gas limit exceeded") } - sigs, err := v.Estack().PopSigElements() + sigs, err := ic.VM.Estack().PopSigElements() if err != nil { return fmt.Errorf("wrong parameters: %w", err) } @@ -76,8 +76,8 @@ func ecdsaCheckMultisig(ic *interop.Context, v *vm.VM, curve elliptic.Curve) err if len(pkeys) < len(sigs) { return errors.New("more signatures than there are keys") } - sigok := vm.CheckMultisigPar(v, curve, hashToCheck, pkeys, sigs) - v.Estack().PushVal(sigok) + sigok := vm.CheckMultisigPar(ic.VM, curve, hashToCheck, pkeys, sigs) + ic.VM.Estack().PushVal(sigok) return nil } diff --git a/pkg/core/interop/crypto/hash.go b/pkg/core/interop/crypto/hash.go index 30936706f..106f545fd 100644 --- a/pkg/core/interop/crypto/hash.go +++ b/pkg/core/interop/crypto/hash.go @@ -3,21 +3,20 @@ package crypto import ( "github.com/nspcc-dev/neo-go/pkg/core/interop" "github.com/nspcc-dev/neo-go/pkg/crypto/hash" - "github.com/nspcc-dev/neo-go/pkg/vm" ) // Sha256 returns sha256 hash of the data. -func Sha256(ic *interop.Context, v *vm.VM) error { - msg := getMessage(ic, v.Estack().Pop().Item()) +func Sha256(ic *interop.Context) error { + msg := getMessage(ic, ic.VM.Estack().Pop().Item()) h := hash.Sha256(msg).BytesBE() - v.Estack().PushVal(h) + ic.VM.Estack().PushVal(h) return nil } // RipeMD160 returns RipeMD160 hash of the data. -func RipeMD160(ic *interop.Context, v *vm.VM) error { - msg := getMessage(ic, v.Estack().Pop().Item()) +func RipeMD160(ic *interop.Context) error { + msg := getMessage(ic, ic.VM.Estack().Pop().Item()) h := hash.RipeMD160(msg).BytesBE() - v.Estack().PushVal(h) + ic.VM.Estack().PushVal(h) return nil } diff --git a/pkg/core/interop/enumerator/interop.go b/pkg/core/interop/enumerator/interop.go index 6265d74d1..24cc85931 100644 --- a/pkg/core/interop/enumerator/interop.go +++ b/pkg/core/interop/enumerator/interop.go @@ -6,22 +6,22 @@ import ( ) // Concat concatenates 2 enumerators into a single one. -func Concat(_ *interop.Context, v *vm.VM) error { - return vm.EnumeratorConcat(v) +func Concat(ic *interop.Context) error { + return vm.EnumeratorConcat(ic.VM) } // Create creates an enumerator from an array-like or bytearray-like stack item. -func Create(_ *interop.Context, v *vm.VM) error { - return vm.EnumeratorCreate(v) +func Create(ic *interop.Context) error { + return vm.EnumeratorCreate(ic.VM) } // Next advances the enumerator, pushes true if is it was successful // and false otherwise. -func Next(_ *interop.Context, v *vm.VM) error { - return vm.EnumeratorNext(v) +func Next(ic *interop.Context) error { + return vm.EnumeratorNext(ic.VM) } // Value returns the current value of the enumerator. -func Value(_ *interop.Context, v *vm.VM) error { - return vm.EnumeratorValue(v) +func Value(ic *interop.Context) error { + return vm.EnumeratorValue(ic.VM) } diff --git a/pkg/core/interop/iterator/interop.go b/pkg/core/interop/iterator/interop.go index f04dfa942..6bc161479 100644 --- a/pkg/core/interop/iterator/interop.go +++ b/pkg/core/interop/iterator/interop.go @@ -6,26 +6,26 @@ import ( ) // Concat concatenates 2 iterators into a single one. -func Concat(_ *interop.Context, v *vm.VM) error { - return vm.IteratorConcat(v) +func Concat(ic *interop.Context) error { + return vm.IteratorConcat(ic.VM) } // Create creates an iterator from array-like or map stack item. -func Create(_ *interop.Context, v *vm.VM) error { - return vm.IteratorCreate(v) +func Create(ic *interop.Context) error { + return vm.IteratorCreate(ic.VM) } // Key returns current iterator key. -func Key(_ *interop.Context, v *vm.VM) error { - return vm.IteratorKey(v) +func Key(ic *interop.Context) error { + return vm.IteratorKey(ic.VM) } // Keys returns keys of the iterator. -func Keys(_ *interop.Context, v *vm.VM) error { - return vm.IteratorKeys(v) +func Keys(ic *interop.Context) error { + return vm.IteratorKeys(ic.VM) } // Values returns values of the iterator. -func Values(_ *interop.Context, v *vm.VM) error { - return vm.IteratorValues(v) +func Values(ic *interop.Context) error { + return vm.IteratorValues(ic.VM) } diff --git a/pkg/core/interop/json/json.go b/pkg/core/interop/json/json.go index c9f660fa3..5be269939 100644 --- a/pkg/core/interop/json/json.go +++ b/pkg/core/interop/json/json.go @@ -2,28 +2,27 @@ package json import ( "github.com/nspcc-dev/neo-go/pkg/core/interop" - "github.com/nspcc-dev/neo-go/pkg/vm" "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" ) // Serialize handles System.JSON.Serialize syscall. -func Serialize(_ *interop.Context, v *vm.VM) error { - item := v.Estack().Pop().Item() +func Serialize(ic *interop.Context) error { + item := ic.VM.Estack().Pop().Item() data, err := stackitem.ToJSON(item) if err != nil { return err } - v.Estack().PushVal(data) + ic.VM.Estack().PushVal(data) return nil } // Deserialize handles System.JSON.Deserialize syscall. -func Deserialize(_ *interop.Context, v *vm.VM) error { - data := v.Estack().Pop().Bytes() +func Deserialize(ic *interop.Context) error { + data := ic.VM.Estack().Pop().Bytes() item, err := stackitem.FromJSON(data) if err != nil { return err } - v.Estack().PushVal(item) + ic.VM.Estack().PushVal(item) return nil } diff --git a/pkg/core/interop/runtime/util.go b/pkg/core/interop/runtime/util.go index ba806761f..f9037887f 100644 --- a/pkg/core/interop/runtime/util.go +++ b/pkg/core/interop/runtime/util.go @@ -11,18 +11,18 @@ import ( ) // GasLeft returns remaining amount of GAS. -func GasLeft(_ *interop.Context, v *vm.VM) error { - if v.GasLimit == -1 { - v.Estack().PushVal(v.GasLimit) +func GasLeft(ic *interop.Context) error { + if ic.VM.GasLimit == -1 { + ic.VM.Estack().PushVal(ic.VM.GasLimit) } else { - v.Estack().PushVal(v.GasLimit - v.GasConsumed()) + ic.VM.Estack().PushVal(ic.VM.GasLimit - ic.VM.GasConsumed()) } return nil } // GetNotifications returns notifications emitted by current contract execution. -func GetNotifications(ic *interop.Context, v *vm.VM) error { - item := v.Estack().Pop().Item() +func GetNotifications(ic *interop.Context) error { + item := ic.VM.Estack().Pop().Item() notifications := ic.Notifications if _, ok := item.(stackitem.Null); !ok { b, err := item.TryBytes() @@ -52,16 +52,16 @@ func GetNotifications(ic *interop.Context, v *vm.VM) error { }) arr.Append(ev) } - v.Estack().PushVal(arr) + ic.VM.Estack().PushVal(arr) return nil } // GetInvocationCounter returns how many times current contract was invoked during current tx execution. -func GetInvocationCounter(ic *interop.Context, v *vm.VM) error { - count, ok := ic.Invocations[v.GetCurrentScriptHash()] +func GetInvocationCounter(ic *interop.Context) error { + count, ok := ic.Invocations[ic.VM.GetCurrentScriptHash()] if !ok { return errors.New("current contract wasn't invoked from others") } - v.Estack().PushVal(count) + ic.VM.Estack().PushVal(count) return nil } diff --git a/pkg/core/interop/runtime/witness.go b/pkg/core/interop/runtime/witness.go index 413980e0c..c06413a8c 100644 --- a/pkg/core/interop/runtime/witness.go +++ b/pkg/core/interop/runtime/witness.go @@ -17,13 +17,13 @@ import ( // for verifying in the interop context. func CheckHashedWitness(ic *interop.Context, hash util.Uint160) (bool, error) { if tx, ok := ic.Container.(*transaction.Transaction); ok { - return checkScope(ic.DAO, tx, ic.ScriptGetter, hash) + return checkScope(ic.DAO, tx, ic.VM, hash) } return false, errors.New("script container is not a transaction") } -func checkScope(d dao.DAO, tx *transaction.Transaction, v vm.ScriptHashGetter, hash util.Uint160) (bool, error) { +func checkScope(d dao.DAO, tx *transaction.Transaction, v *vm.VM, hash util.Uint160) (bool, error) { for _, c := range tx.Signers { if c.Account == hash { if c.Scopes == transaction.Global { @@ -75,11 +75,11 @@ func CheckKeyedWitness(ic *interop.Context, key *keys.PublicKey) (bool, error) { } // CheckWitness checks witnesses. -func CheckWitness(ic *interop.Context, v *vm.VM) error { +func CheckWitness(ic *interop.Context) error { var res bool var err error - hashOrKey := v.Estack().Pop().Bytes() + hashOrKey := ic.VM.Estack().Pop().Bytes() hash, err := util.Uint160DecodeBytesBE(hashOrKey) if err != nil { var key *keys.PublicKey @@ -94,6 +94,6 @@ func CheckWitness(ic *interop.Context, v *vm.VM) error { if err != nil { return fmt.Errorf("failed to check witness: %w", err) } - v.Estack().PushVal(res) + ic.VM.Estack().PushVal(res) return nil } diff --git a/pkg/core/interop_neo.go b/pkg/core/interop_neo.go index cb1588b9d..491d20dcd 100644 --- a/pkg/core/interop_neo.go +++ b/pkg/core/interop_neo.go @@ -29,13 +29,13 @@ const ( var errGasLimitExceeded = errors.New("gas limit exceeded") // storageFind finds stored key-value pair. -func storageFind(ic *interop.Context, v *vm.VM) error { - stcInterface := v.Estack().Pop().Value() +func storageFind(ic *interop.Context) error { + stcInterface := ic.VM.Estack().Pop().Value() stc, ok := stcInterface.(*StorageContext) if !ok { return fmt.Errorf("%T is not a StorageContext", stcInterface) } - prefix := v.Estack().Pop().Bytes() + prefix := ic.VM.Estack().Pop().Bytes() siMap, err := ic.DAO.GetStorageItemsWithPrefix(stc.ID, prefix) if err != nil { return err @@ -51,7 +51,7 @@ func storageFind(ic *interop.Context, v *vm.VM) error { }) item := vm.NewMapIterator(filteredMap) - v.Estack().PushVal(item) + ic.VM.Estack().PushVal(item) return nil } @@ -59,16 +59,16 @@ func storageFind(ic *interop.Context, v *vm.VM) error { // createContractStateFromVM pops all contract state elements from the VM // evaluation stack, does a lot of checks and returns Contract if it // succeeds. -func createContractStateFromVM(ic *interop.Context, v *vm.VM) (*state.Contract, error) { - script := v.Estack().Pop().Bytes() +func createContractStateFromVM(ic *interop.Context) (*state.Contract, error) { + script := ic.VM.Estack().Pop().Bytes() if len(script) > MaxContractScriptSize { return nil, errors.New("the script is too big") } - manifestBytes := v.Estack().Pop().Bytes() + manifestBytes := ic.VM.Estack().Pop().Bytes() if len(manifestBytes) > manifest.MaxManifestSize { return nil, errors.New("manifest is too big") } - if !v.AddGas(int64(StoragePrice * (len(script) + len(manifestBytes)))) { + if !ic.VM.AddGas(int64(StoragePrice * (len(script) + len(manifestBytes)))) { return nil, errGasLimitExceeded } var m manifest.Manifest @@ -83,8 +83,8 @@ func createContractStateFromVM(ic *interop.Context, v *vm.VM) (*state.Contract, } // contractCreate creates a contract. -func contractCreate(ic *interop.Context, v *vm.VM) error { - newcontract, err := createContractStateFromVM(ic, v) +func contractCreate(ic *interop.Context) error { + newcontract, err := createContractStateFromVM(ic) if err != nil { return err } @@ -107,26 +107,26 @@ func contractCreate(ic *interop.Context, v *vm.VM) error { if err != nil { return fmt.Errorf("cannot convert contract to stack item: %w", err) } - v.Estack().PushVal(cs) + ic.VM.Estack().PushVal(cs) return nil } // contractUpdate migrates a contract. This method assumes that Manifest and Script // of the contract can be updated independently. -func contractUpdate(ic *interop.Context, v *vm.VM) error { - contract, _ := ic.DAO.GetContractState(v.GetCurrentScriptHash()) +func contractUpdate(ic *interop.Context) error { + contract, _ := ic.DAO.GetContractState(ic.VM.GetCurrentScriptHash()) if contract == nil { return errors.New("contract doesn't exist") } - script := v.Estack().Pop().Bytes() + script := ic.VM.Estack().Pop().Bytes() if len(script) > MaxContractScriptSize { return errors.New("the script is too big") } - manifestBytes := v.Estack().Pop().Bytes() + manifestBytes := ic.VM.Estack().Pop().Bytes() if len(manifestBytes) > manifest.MaxManifestSize { return errors.New("manifest is too big") } - if !v.AddGas(int64(StoragePrice * (len(script) + len(manifestBytes)))) { + if !ic.VM.AddGas(int64(StoragePrice * (len(script) + len(manifestBytes)))) { return errGasLimitExceeded } // if script was provided, update the old contract script and Manifest.ABI hash @@ -186,30 +186,30 @@ func contractUpdate(ic *interop.Context, v *vm.VM) error { } // runtimeSerialize serializes top stack item into a ByteArray. -func runtimeSerialize(_ *interop.Context, v *vm.VM) error { - return vm.RuntimeSerialize(v) +func runtimeSerialize(ic *interop.Context) error { + return vm.RuntimeSerialize(ic.VM) } // runtimeDeserialize deserializes ByteArray from a stack into an item. -func runtimeDeserialize(_ *interop.Context, v *vm.VM) error { - return vm.RuntimeDeserialize(v) +func runtimeDeserialize(ic *interop.Context) error { + return vm.RuntimeDeserialize(ic.VM) } // runtimeEncode encodes top stack item into a base64 string. -func runtimeEncode(_ *interop.Context, v *vm.VM) error { - src := v.Estack().Pop().Bytes() +func runtimeEncode(ic *interop.Context) error { + src := ic.VM.Estack().Pop().Bytes() result := base64.StdEncoding.EncodeToString(src) - v.Estack().PushVal([]byte(result)) + ic.VM.Estack().PushVal([]byte(result)) return nil } // runtimeDecode decodes top stack item from base64 string to byte array. -func runtimeDecode(_ *interop.Context, v *vm.VM) error { - src := v.Estack().Pop().String() +func runtimeDecode(ic *interop.Context) error { + src := ic.VM.Estack().Pop().String() result, err := base64.StdEncoding.DecodeString(src) if err != nil { return err } - v.Estack().PushVal(result) + ic.VM.Estack().PushVal(result) return nil } diff --git a/pkg/core/interop_neo_test.go b/pkg/core/interop_neo_test.go index 5a441c3a0..17c6e7635 100644 --- a/pkg/core/interop_neo_test.go +++ b/pkg/core/interop_neo_test.go @@ -40,9 +40,9 @@ import ( */ func TestGetTrigger(t *testing.T) { - v, _, context, chain := createVMAndPushBlock(t) + _, _, context, chain := createVMAndPushBlock(t) defer chain.Close() - require.NoError(t, runtimeGetTrigger(context, v)) + require.NoError(t, runtimeGetTrigger(context)) } func TestStorageFind(t *testing.T) { @@ -75,37 +75,37 @@ func TestStorageFind(t *testing.T) { v.Estack().PushVal([]byte{0x01}) v.Estack().PushVal(stackitem.NewInterop(&StorageContext{ID: id})) - err := storageFind(context, v) + err := storageFind(context) require.NoError(t, err) var iter *stackitem.Interop require.NotPanics(t, func() { iter = v.Estack().Top().Interop() }) - require.NoError(t, enumerator.Next(context, v)) + require.NoError(t, enumerator.Next(context)) require.True(t, v.Estack().Pop().Bool()) v.Estack().PushVal(iter) - require.NoError(t, iterator.Key(context, v)) + require.NoError(t, iterator.Key(context)) require.Equal(t, []byte{0x01, 0x01}, v.Estack().Pop().Bytes()) v.Estack().PushVal(iter) - require.NoError(t, enumerator.Value(context, v)) + require.NoError(t, enumerator.Value(context)) require.Equal(t, []byte{0x03, 0x04, 0x05, 0x06}, v.Estack().Pop().Bytes()) v.Estack().PushVal(iter) - require.NoError(t, enumerator.Next(context, v)) + require.NoError(t, enumerator.Next(context)) require.True(t, v.Estack().Pop().Bool()) v.Estack().PushVal(iter) - require.NoError(t, iterator.Key(context, v)) + require.NoError(t, iterator.Key(context)) require.Equal(t, []byte{0x01, 0x02}, v.Estack().Pop().Bytes()) v.Estack().PushVal(iter) - require.NoError(t, enumerator.Value(context, v)) + require.NoError(t, enumerator.Value(context)) require.Equal(t, []byte{0x01, 0x02, 0x03, 0x04}, v.Estack().Pop().Bytes()) v.Estack().PushVal(iter) - require.NoError(t, enumerator.Next(context, v)) + require.NoError(t, enumerator.Next(context)) require.False(t, v.Estack().Pop().Bool()) }) @@ -113,10 +113,10 @@ func TestStorageFind(t *testing.T) { v.Estack().PushVal([]byte{0x03}) v.Estack().PushVal(stackitem.NewInterop(&StorageContext{ID: id})) - err := storageFind(context, v) + err := storageFind(context) require.NoError(t, err) - require.NoError(t, enumerator.Next(context, v)) + require.NoError(t, enumerator.Next(context)) require.False(t, v.Estack().Pop().Bool()) }) @@ -124,7 +124,7 @@ func TestStorageFind(t *testing.T) { v.Estack().PushVal([]byte{0x01}) v.Estack().PushVal(stackitem.NewInterop(nil)) - require.Error(t, storageFind(context, v)) + require.Error(t, storageFind(context)) }) t.Run("invalid id", func(t *testing.T) { @@ -133,8 +133,8 @@ func TestStorageFind(t *testing.T) { v.Estack().PushVal([]byte{0x01}) v.Estack().PushVal(stackitem.NewInterop(&StorageContext{ID: invalidID})) - require.NoError(t, storageFind(context, v)) - require.NoError(t, enumerator.Next(context, v)) + require.NoError(t, storageFind(context)) + require.NoError(t, enumerator.Next(context)) require.False(t, v.Estack().Pop().Bool()) }) } @@ -149,6 +149,7 @@ func TestECDSAVerify(t *testing.T) { ic := chain.newInteropContext(trigger.Application, dao.NewSimple(storage.NewMemoryStore(), netmode.UnitTestNet), nil, nil) runCase := func(t *testing.T, isErr bool, result interface{}, args ...interface{}) { v := vm.New() + ic.VM = v for i := range args { v.Estack().PushVal(args[i]) } @@ -160,7 +161,7 @@ func TestECDSAVerify(t *testing.T) { err = fmt.Errorf("panic: %v", r) } }() - err = crypto.ECDSASecp256r1Verify(ic, v) + err = crypto.ECDSASecp256r1Verify(ic) }() if isErr { @@ -227,7 +228,7 @@ func TestRuntimeEncode(t *testing.T) { defer bc.Close() v.Estack().PushVal(str) - require.NoError(t, runtimeEncode(ic, v)) + require.NoError(t, runtimeEncode(ic)) expected := []byte(base64.StdEncoding.EncodeToString(str)) actual := v.Estack().Pop().Bytes() @@ -242,7 +243,7 @@ func TestRuntimeDecode(t *testing.T) { t.Run("positive", func(t *testing.T) { v.Estack().PushVal(str) - require.NoError(t, runtimeDecode(ic, v)) + require.NoError(t, runtimeDecode(ic)) actual := v.Estack().Pop().Bytes() require.Equal(t, expected, actual) @@ -250,17 +251,17 @@ func TestRuntimeDecode(t *testing.T) { t.Run("error", func(t *testing.T) { v.Estack().PushVal(str + "%") - require.Error(t, runtimeDecode(ic, v)) + require.Error(t, runtimeDecode(ic)) }) } // Helper functions to create VM, InteropContext, TX, Account, Contract. func createVM(t *testing.T) (*vm.VM, *interop.Context, *Blockchain) { - v := vm.New() chain := newTestChain(t) context := chain.newInteropContext(trigger.Application, dao.NewSimple(storage.NewMemoryStore(), netmode.UnitTestNet), nil, nil) + v := context.SpawnVM() return v, context, chain } @@ -271,10 +272,10 @@ func createVMAndPushBlock(t *testing.T) (*vm.VM, *block.Block, *interop.Context, } func createVMAndBlock(t *testing.T) (*vm.VM, *block.Block, *interop.Context, *Blockchain) { - v := vm.New() block := newDumbBlock() chain := newTestChain(t) context := chain.newInteropContext(trigger.Application, dao.NewSimple(storage.NewMemoryStore(), netmode.UnitTestNet), block, nil) + v := context.SpawnVM() return v, block, context, chain } @@ -285,7 +286,6 @@ func createVMAndPushTX(t *testing.T) (*vm.VM, *transaction.Transaction, *interop } func createVMAndContractState(t *testing.T) (*vm.VM, *state.Contract, *interop.Context, *Blockchain) { - v := vm.New() script := []byte("testscript") m := manifest.NewManifest(hash.Hash160(script)) m.Features = smartcontract.HasStorage @@ -297,11 +297,11 @@ func createVMAndContractState(t *testing.T) (*vm.VM, *state.Contract, *interop.C chain := newTestChain(t) context := chain.newInteropContext(trigger.Application, dao.NewSimple(storage.NewMemoryStore(), netmode.UnitTestNet), nil, nil) + v := context.SpawnVM() return v, contractState, context, chain } func createVMAndAccState(t *testing.T) (*vm.VM, *state.Account, *interop.Context, *Blockchain) { - v := vm.New() rawHash := "4d3b96ae1bcc5a585e075e3b81920210dec16302" hash, err := util.Uint160DecodeStringBE(rawHash) accountState := state.NewAccount(hash) @@ -309,11 +309,11 @@ func createVMAndAccState(t *testing.T) (*vm.VM, *state.Account, *interop.Context require.NoError(t, err) chain := newTestChain(t) context := chain.newInteropContext(trigger.Application, dao.NewSimple(storage.NewMemoryStore(), netmode.UnitTestNet), nil, nil) + v := context.SpawnVM() return v, accountState, context, chain } func createVMAndTX(t *testing.T) (*vm.VM, *transaction.Transaction, *interop.Context, *Blockchain) { - v := vm.New() script := []byte{byte(opcode.PUSH1), byte(opcode.RET)} tx := transaction.New(netmode.UnitTestNet, script, 0) @@ -327,5 +327,6 @@ func createVMAndTX(t *testing.T) (*vm.VM, *transaction.Transaction, *interop.Con tx.Signers = []transaction.Signer{{Account: util.Uint160{1, 2, 3, 4}}} chain := newTestChain(t) context := chain.newInteropContext(trigger.Application, dao.NewSimple(storage.NewMemoryStore(), netmode.UnitTestNet), nil, tx) + v := context.SpawnVM() return v, tx, context, chain } diff --git a/pkg/core/interop_system.go b/pkg/core/interop_system.go index 242460ca0..abb396785 100644 --- a/pkg/core/interop_system.go +++ b/pkg/core/interop_system.go @@ -89,16 +89,16 @@ func blockToStackItem(b *block.Block) stackitem.Item { } // bcGetBlock returns current block. -func bcGetBlock(ic *interop.Context, v *vm.VM) error { - hash, err := getBlockHashFromElement(ic.Chain, v.Estack().Pop()) +func bcGetBlock(ic *interop.Context) error { + hash, err := getBlockHashFromElement(ic.Chain, ic.VM.Estack().Pop()) if err != nil { return err } block, err := ic.Chain.GetBlock(hash) if err != nil || !isTraceableBlock(ic, block.Index) { - v.Estack().PushVal(stackitem.Null{}) + ic.VM.Estack().PushVal(stackitem.Null{}) } else { - v.Estack().PushVal(blockToStackItem(block)) + ic.VM.Estack().PushVal(blockToStackItem(block)) } return nil } @@ -118,28 +118,28 @@ func contractToStackItem(cs *state.Contract) (stackitem.Item, error) { } // bcGetContract returns contract. -func bcGetContract(ic *interop.Context, v *vm.VM) error { - hashbytes := v.Estack().Pop().Bytes() +func bcGetContract(ic *interop.Context) error { + hashbytes := ic.VM.Estack().Pop().Bytes() hash, err := util.Uint160DecodeBytesBE(hashbytes) if err != nil { return err } cs, err := ic.DAO.GetContractState(hash) if err != nil { - v.Estack().PushVal(stackitem.Null{}) + ic.VM.Estack().PushVal(stackitem.Null{}) } else { item, err := contractToStackItem(cs) if err != nil { return err } - v.Estack().PushVal(item) + ic.VM.Estack().PushVal(item) } return nil } // bcGetHeight returns blockchain height. -func bcGetHeight(ic *interop.Context, v *vm.VM) error { - v.Estack().PushVal(ic.Chain.BlockHeight()) +func bcGetHeight(ic *interop.Context) error { + ic.VM.Estack().PushVal(ic.Chain.BlockHeight()) return nil } @@ -176,51 +176,51 @@ func transactionToStackItem(t *transaction.Transaction) stackitem.Item { } // bcGetTransaction returns transaction. -func bcGetTransaction(ic *interop.Context, v *vm.VM) error { - tx, h, err := getTransactionAndHeight(ic.DAO, v) +func bcGetTransaction(ic *interop.Context) error { + tx, h, err := getTransactionAndHeight(ic.DAO, ic.VM) if err != nil || !isTraceableBlock(ic, h) { - v.Estack().PushVal(stackitem.Null{}) + ic.VM.Estack().PushVal(stackitem.Null{}) return nil } - v.Estack().PushVal(transactionToStackItem(tx)) + ic.VM.Estack().PushVal(transactionToStackItem(tx)) return nil } // bcGetTransactionFromBlock returns transaction with the given index from the // block with height or hash specified. -func bcGetTransactionFromBlock(ic *interop.Context, v *vm.VM) error { - hash, err := getBlockHashFromElement(ic.Chain, v.Estack().Pop()) +func bcGetTransactionFromBlock(ic *interop.Context) error { + hash, err := getBlockHashFromElement(ic.Chain, ic.VM.Estack().Pop()) if err != nil { return err } - index := v.Estack().Pop().BigInt().Int64() + index := ic.VM.Estack().Pop().BigInt().Int64() block, err := ic.DAO.GetBlock(hash) if err != nil || !isTraceableBlock(ic, block.Index) { - v.Estack().PushVal(stackitem.Null{}) + ic.VM.Estack().PushVal(stackitem.Null{}) return nil } if index < 0 || index >= int64(len(block.Transactions)) { return errors.New("wrong transaction index") } tx := block.Transactions[index] - v.Estack().PushVal(tx.Hash().BytesBE()) + ic.VM.Estack().PushVal(tx.Hash().BytesBE()) return nil } // bcGetTransactionHeight returns transaction height. -func bcGetTransactionHeight(ic *interop.Context, v *vm.VM) error { - _, h, err := getTransactionAndHeight(ic.DAO, v) +func bcGetTransactionHeight(ic *interop.Context) error { + _, h, err := getTransactionAndHeight(ic.DAO, ic.VM) if err != nil || !isTraceableBlock(ic, h) { - v.Estack().PushVal(-1) + ic.VM.Estack().PushVal(-1) return nil } - v.Estack().PushVal(h) + ic.VM.Estack().PushVal(h) return nil } // engineGetScriptContainer returns transaction or block that contains the script // being run. -func engineGetScriptContainer(ic *interop.Context, v *vm.VM) error { +func engineGetScriptContainer(ic *interop.Context) error { var item stackitem.Item switch t := ic.Container.(type) { case *transaction.Transaction: @@ -230,45 +230,45 @@ func engineGetScriptContainer(ic *interop.Context, v *vm.VM) error { default: return errors.New("unknown script container") } - v.Estack().PushVal(item) + ic.VM.Estack().PushVal(item) return nil } // engineGetExecutingScriptHash returns executing script hash. -func engineGetExecutingScriptHash(ic *interop.Context, v *vm.VM) error { - return v.PushContextScriptHash(0) +func engineGetExecutingScriptHash(ic *interop.Context) error { + return ic.VM.PushContextScriptHash(0) } // engineGetCallingScriptHash returns calling script hash. -func engineGetCallingScriptHash(ic *interop.Context, v *vm.VM) error { - return v.PushContextScriptHash(1) +func engineGetCallingScriptHash(ic *interop.Context) error { + return ic.VM.PushContextScriptHash(1) } // engineGetEntryScriptHash returns entry script hash. -func engineGetEntryScriptHash(ic *interop.Context, v *vm.VM) error { - return v.PushContextScriptHash(v.Istack().Len() - 1) +func engineGetEntryScriptHash(ic *interop.Context) error { + return ic.VM.PushContextScriptHash(ic.VM.Istack().Len() - 1) } // runtimePlatform returns the name of the platform. -func runtimePlatform(ic *interop.Context, v *vm.VM) error { - v.Estack().PushVal([]byte("NEO")) +func runtimePlatform(ic *interop.Context) error { + ic.VM.Estack().PushVal([]byte("NEO")) return nil } // runtimeGetTrigger returns the script trigger. -func runtimeGetTrigger(ic *interop.Context, v *vm.VM) error { - v.Estack().PushVal(byte(ic.Trigger)) +func runtimeGetTrigger(ic *interop.Context) error { + ic.VM.Estack().PushVal(byte(ic.Trigger)) return nil } // runtimeNotify should pass stack item to the notify plugin to handle it, but // in neo-go the only meaningful thing to do here is to log. -func runtimeNotify(ic *interop.Context, v *vm.VM) error { - name := v.Estack().Pop().String() +func runtimeNotify(ic *interop.Context) error { + name := ic.VM.Estack().Pop().String() if len(name) > MaxEventNameLen { return fmt.Errorf("event name must be less than %d", MaxEventNameLen) } - elem := v.Estack().Pop() + elem := ic.VM.Estack().Pop() args := elem.Array() // But it has to be serializable, otherwise we either have some broken // (recursive) structure inside or an interop item that can't be used @@ -280,7 +280,7 @@ func runtimeNotify(ic *interop.Context, v *vm.VM) error { args = []stackitem.Item{stackitem.NewByteArray([]byte(fmt.Sprintf("bad notification: %v", err)))} } ne := state.NotificationEvent{ - ScriptHash: v.GetCurrentScriptHash(), + ScriptHash: ic.VM.GetCurrentScriptHash(), Name: name, Item: stackitem.DeepCopy(stackitem.NewArray(args)).(*stackitem.Array), } @@ -289,21 +289,21 @@ func runtimeNotify(ic *interop.Context, v *vm.VM) error { } // runtimeLog logs the message passed. -func runtimeLog(ic *interop.Context, v *vm.VM) error { - state := v.Estack().Pop().String() +func runtimeLog(ic *interop.Context) error { + state := ic.VM.Estack().Pop().String() if len(state) > MaxNotificationSize { return fmt.Errorf("message length shouldn't exceed %v", MaxNotificationSize) } msg := fmt.Sprintf("%q", state) ic.Log.Info("runtime log", - zap.Stringer("script", v.GetCurrentScriptHash()), + zap.Stringer("script", ic.VM.GetCurrentScriptHash()), zap.String("logs", msg)) return nil } // runtimeGetTime returns timestamp of the block being verified, or the latest // one in the blockchain if no block is given to Context. -func runtimeGetTime(ic *interop.Context, v *vm.VM) error { +func runtimeGetTime(ic *interop.Context) error { var header *block.Header if ic.Block == nil { var err error @@ -314,13 +314,13 @@ func runtimeGetTime(ic *interop.Context, v *vm.VM) error { } else { header = ic.Block.Header() } - v.Estack().PushVal(header.Timestamp) + ic.VM.Estack().PushVal(header.Timestamp) return nil } // storageDelete deletes stored key-value pair. -func storageDelete(ic *interop.Context, v *vm.VM) error { - stcInterface := v.Estack().Pop().Value() +func storageDelete(ic *interop.Context) error { + stcInterface := ic.VM.Estack().Pop().Value() stc, ok := stcInterface.(*StorageContext) if !ok { return fmt.Errorf("%T is not a StorageContext", stcInterface) @@ -328,7 +328,7 @@ func storageDelete(ic *interop.Context, v *vm.VM) error { if stc.ReadOnly { return errors.New("StorageContext is read only") } - key := v.Estack().Pop().Bytes() + key := ic.VM.Estack().Pop().Bytes() si := ic.DAO.GetStorageItem(stc.ID, key) if si != nil && si.IsConst { return errors.New("storage item is constant") @@ -337,36 +337,36 @@ func storageDelete(ic *interop.Context, v *vm.VM) error { } // storageGet returns stored key-value pair. -func storageGet(ic *interop.Context, v *vm.VM) error { - stcInterface := v.Estack().Pop().Value() +func storageGet(ic *interop.Context) error { + stcInterface := ic.VM.Estack().Pop().Value() stc, ok := stcInterface.(*StorageContext) if !ok { return fmt.Errorf("%T is not a StorageContext", stcInterface) } - key := v.Estack().Pop().Bytes() + key := ic.VM.Estack().Pop().Bytes() si := ic.DAO.GetStorageItem(stc.ID, key) if si != nil && si.Value != nil { - v.Estack().PushVal(si.Value) + ic.VM.Estack().PushVal(si.Value) } else { - v.Estack().PushVal(stackitem.Null{}) + ic.VM.Estack().PushVal(stackitem.Null{}) } return nil } // storageGetContext returns storage context (scripthash). -func storageGetContext(ic *interop.Context, v *vm.VM) error { - return storageGetContextInternal(ic, v, false) +func storageGetContext(ic *interop.Context) error { + return storageGetContextInternal(ic, false) } // storageGetReadOnlyContext returns read-only context (scripthash). -func storageGetReadOnlyContext(ic *interop.Context, v *vm.VM) error { - return storageGetContextInternal(ic, v, true) +func storageGetReadOnlyContext(ic *interop.Context) error { + return storageGetContextInternal(ic, true) } // storageGetContextInternal is internal version of storageGetContext and // storageGetReadOnlyContext which allows to specify ReadOnly context flag. -func storageGetContextInternal(ic *interop.Context, v *vm.VM, isReadOnly bool) error { - contract, err := ic.DAO.GetContractState(v.GetCurrentScriptHash()) +func storageGetContextInternal(ic *interop.Context, isReadOnly bool) error { + contract, err := ic.DAO.GetContractState(ic.VM.GetCurrentScriptHash()) if err != nil { return err } @@ -377,11 +377,11 @@ func storageGetContextInternal(ic *interop.Context, v *vm.VM, isReadOnly bool) e ID: contract.ID, ReadOnly: isReadOnly, } - v.Estack().PushVal(stackitem.NewInterop(sc)) + ic.VM.Estack().PushVal(stackitem.NewInterop(sc)) return nil } -func putWithContextAndFlags(ic *interop.Context, v *vm.VM, stc *StorageContext, key []byte, value []byte, isConst bool) error { +func putWithContextAndFlags(ic *interop.Context, stc *StorageContext, key []byte, value []byte, isConst bool) error { if len(key) > MaxStorageKeyLen { return errors.New("key is too big") } @@ -402,7 +402,7 @@ func putWithContextAndFlags(ic *interop.Context, v *vm.VM, stc *StorageContext, if len(value) > len(si.Value) { sizeInc = len(value) - len(si.Value) } - if !v.AddGas(int64(sizeInc) * StoragePrice) { + if !ic.VM.AddGas(int64(sizeInc) * StoragePrice) { return errGasLimitExceeded } si.Value = value @@ -411,34 +411,34 @@ func putWithContextAndFlags(ic *interop.Context, v *vm.VM, stc *StorageContext, } // storagePutInternal is a unified implementation of storagePut and storagePutEx. -func storagePutInternal(ic *interop.Context, v *vm.VM, getFlag bool) error { - stcInterface := v.Estack().Pop().Value() +func storagePutInternal(ic *interop.Context, getFlag bool) error { + stcInterface := ic.VM.Estack().Pop().Value() stc, ok := stcInterface.(*StorageContext) if !ok { return fmt.Errorf("%T is not a StorageContext", stcInterface) } - key := v.Estack().Pop().Bytes() - value := v.Estack().Pop().Bytes() + key := ic.VM.Estack().Pop().Bytes() + value := ic.VM.Estack().Pop().Bytes() var flag int if getFlag { - flag = int(v.Estack().Pop().BigInt().Int64()) + flag = int(ic.VM.Estack().Pop().BigInt().Int64()) } - return putWithContextAndFlags(ic, v, stc, key, value, int(Constant)&flag != 0) + return putWithContextAndFlags(ic, stc, key, value, int(Constant)&flag != 0) } // storagePut puts key-value pair into the storage. -func storagePut(ic *interop.Context, v *vm.VM) error { - return storagePutInternal(ic, v, false) +func storagePut(ic *interop.Context) error { + return storagePutInternal(ic, false) } // storagePutEx puts key-value pair with given flags into the storage. -func storagePutEx(ic *interop.Context, v *vm.VM) error { - return storagePutInternal(ic, v, true) +func storagePutEx(ic *interop.Context) error { + return storagePutInternal(ic, true) } // storageContextAsReadOnly sets given context to read-only mode. -func storageContextAsReadOnly(ic *interop.Context, v *vm.VM) error { - stcInterface := v.Estack().Pop().Value() +func storageContextAsReadOnly(ic *interop.Context) error { + stcInterface := ic.VM.Estack().Pop().Value() stc, ok := stcInterface.(*StorageContext) if !ok { return fmt.Errorf("%T is not a StorageContext", stcInterface) @@ -450,31 +450,31 @@ func storageContextAsReadOnly(ic *interop.Context, v *vm.VM) error { } stc = stx } - v.Estack().PushVal(stackitem.NewInterop(stc)) + ic.VM.Estack().PushVal(stackitem.NewInterop(stc)) return nil } // contractCall calls a contract. -func contractCall(ic *interop.Context, v *vm.VM) error { - h := v.Estack().Pop().Bytes() - method := v.Estack().Pop().String() - args := v.Estack().Pop().Array() - return contractCallExInternal(ic, v, h, method, args, smartcontract.All) +func contractCall(ic *interop.Context) error { + h := ic.VM.Estack().Pop().Bytes() + method := ic.VM.Estack().Pop().String() + args := ic.VM.Estack().Pop().Array() + return contractCallExInternal(ic, h, method, args, smartcontract.All) } // contractCallEx calls a contract with flags. -func contractCallEx(ic *interop.Context, v *vm.VM) error { - h := v.Estack().Pop().Bytes() - method := v.Estack().Pop().String() - args := v.Estack().Pop().Array() - flags := smartcontract.CallFlag(int32(v.Estack().Pop().BigInt().Int64())) +func contractCallEx(ic *interop.Context) error { + h := ic.VM.Estack().Pop().Bytes() + method := ic.VM.Estack().Pop().String() + args := ic.VM.Estack().Pop().Array() + flags := smartcontract.CallFlag(int32(ic.VM.Estack().Pop().BigInt().Int64())) if flags&^smartcontract.All != 0 { return errors.New("call flags out of range") } - return contractCallExInternal(ic, v, h, method, args, flags) + return contractCallExInternal(ic, h, method, args, flags) } -func contractCallExInternal(ic *interop.Context, v *vm.VM, h []byte, name string, args []stackitem.Item, f smartcontract.CallFlag) error { +func contractCallExInternal(ic *interop.Context, h []byte, name string, args []stackitem.Item, f smartcontract.CallFlag) error { u, err := util.Uint160DecodeBytesBE(h) if err != nil { return errors.New("invalid contract hash") @@ -490,7 +490,7 @@ func contractCallExInternal(ic *interop.Context, v *vm.VM, h []byte, name string if md == nil { return fmt.Errorf("method '%s' not found", name) } - curr, err := ic.DAO.GetContractState(v.GetCurrentScriptHash()) + curr, err := ic.DAO.GetContractState(ic.VM.GetCurrentScriptHash()) if err == nil { if !curr.Manifest.CanCall(&cs.Manifest, name) { return errors.New("disallowed method call") @@ -502,7 +502,7 @@ func contractCallExInternal(ic *interop.Context, v *vm.VM, h []byte, name string } ic.Invocations[u]++ - v.LoadScriptWithHash(cs.Script, u, v.Context().GetCallFlags()&f) + ic.VM.LoadScriptWithHash(cs.Script, u, ic.VM.Context().GetCallFlags()&f) var isNative bool for i := range ic.Natives { if ic.Natives[i].Metadata().Hash.Equals(u) { @@ -511,27 +511,27 @@ func contractCallExInternal(ic *interop.Context, v *vm.VM, h []byte, name string } } if isNative { - v.Estack().PushVal(args) - v.Estack().PushVal(name) + ic.VM.Estack().PushVal(args) + ic.VM.Estack().PushVal(name) } else { for i := len(args) - 1; i >= 0; i-- { - v.Estack().PushVal(args[i]) + ic.VM.Estack().PushVal(args[i]) } // use Jump not Call here because context was loaded in LoadScript above. - v.Jump(v.Context(), md.Offset) + ic.VM.Jump(ic.VM.Context(), md.Offset) } md = cs.Manifest.ABI.GetMethod(manifest.MethodInit) if md != nil { - v.Call(v.Context(), md.Offset) + ic.VM.Call(ic.VM.Context(), md.Offset) } return nil } // contractDestroy destroys a contract. -func contractDestroy(ic *interop.Context, v *vm.VM) error { - hash := v.GetCurrentScriptHash() +func contractDestroy(ic *interop.Context) error { + hash := ic.VM.GetCurrentScriptHash() cs, err := ic.DAO.GetContractState(hash) if err != nil { return nil @@ -553,8 +553,8 @@ func contractDestroy(ic *interop.Context, v *vm.VM) error { } // contractIsStandard checks if contract is standard (sig or multisig) contract. -func contractIsStandard(ic *interop.Context, v *vm.VM) error { - h := v.Estack().Pop().Bytes() +func contractIsStandard(ic *interop.Context) error { + h := ic.VM.Estack().Pop().Bytes() u, err := util.Uint160DecodeBytesBE(h) if err != nil { return err @@ -573,23 +573,23 @@ func contractIsStandard(ic *interop.Context, v *vm.VM) error { } } } - v.Estack().PushVal(result) + ic.VM.Estack().PushVal(result) return nil } // contractCreateStandardAccount calculates contract scripthash for a given public key. -func contractCreateStandardAccount(ic *interop.Context, v *vm.VM) error { - h := v.Estack().Pop().Bytes() +func contractCreateStandardAccount(ic *interop.Context) error { + h := ic.VM.Estack().Pop().Bytes() p, err := keys.NewPublicKeyFromBytes(h, elliptic.P256()) if err != nil { return err } - v.Estack().PushVal(p.GetScriptHash().BytesBE()) + ic.VM.Estack().PushVal(p.GetScriptHash().BytesBE()) return nil } // contractGetCallFlags returns current context calling flags. -func contractGetCallFlags(_ *interop.Context, v *vm.VM) error { - v.Estack().PushVal(v.Context().GetCallFlags()) +func contractGetCallFlags(ic *interop.Context) error { + ic.VM.Estack().PushVal(ic.VM.Context().GetCallFlags()) return nil } diff --git a/pkg/core/interop_system_test.go b/pkg/core/interop_system_test.go index 58a8a5c82..0a1776b45 100644 --- a/pkg/core/interop_system_test.go +++ b/pkg/core/interop_system_test.go @@ -29,7 +29,7 @@ func TestBCGetTransaction(t *testing.T) { t.Run("success", func(t *testing.T) { require.NoError(t, context.DAO.StoreAsTransaction(tx, 0)) v.Estack().PushVal(tx.Hash().BytesBE()) - err := bcGetTransaction(context, v) + err := bcGetTransaction(context) require.NoError(t, err) value := v.Estack().Pop().Value() @@ -49,7 +49,7 @@ func TestBCGetTransaction(t *testing.T) { t.Run("isn't traceable", func(t *testing.T) { require.NoError(t, context.DAO.StoreAsTransaction(tx, 1)) v.Estack().PushVal(tx.Hash().BytesBE()) - err := bcGetTransaction(context, v) + err := bcGetTransaction(context) require.NoError(t, err) _, ok := v.Estack().Pop().Item().(stackitem.Null) @@ -59,7 +59,7 @@ func TestBCGetTransaction(t *testing.T) { t.Run("bad hash", func(t *testing.T) { require.NoError(t, context.DAO.StoreAsTransaction(tx, 1)) v.Estack().PushVal(tx.Hash().BytesLE()) - err := bcGetTransaction(context, v) + err := bcGetTransaction(context) require.NoError(t, err) _, ok := v.Estack().Pop().Item().(stackitem.Null) @@ -76,7 +76,7 @@ func TestBCGetTransactionFromBlock(t *testing.T) { t.Run("success", func(t *testing.T) { v.Estack().PushVal(0) v.Estack().PushVal(block.Hash().BytesBE()) - err := bcGetTransactionFromBlock(context, v) + err := bcGetTransactionFromBlock(context) require.NoError(t, err) value := v.Estack().Pop().Value() @@ -88,7 +88,7 @@ func TestBCGetTransactionFromBlock(t *testing.T) { t.Run("invalid block hash", func(t *testing.T) { v.Estack().PushVal(0) v.Estack().PushVal(block.Hash().BytesBE()[:10]) - err := bcGetTransactionFromBlock(context, v) + err := bcGetTransactionFromBlock(context) require.Error(t, err) }) @@ -97,7 +97,7 @@ func TestBCGetTransactionFromBlock(t *testing.T) { require.NoError(t, context.DAO.StoreAsBlock(block)) v.Estack().PushVal(0) v.Estack().PushVal(block.Hash().BytesBE()) - err := bcGetTransactionFromBlock(context, v) + err := bcGetTransactionFromBlock(context) require.NoError(t, err) _, ok := v.Estack().Pop().Item().(stackitem.Null) @@ -109,7 +109,7 @@ func TestBCGetTransactionFromBlock(t *testing.T) { require.NoError(t, context.DAO.StoreAsBlock(block)) v.Estack().PushVal(0) v.Estack().PushVal(block.Hash().BytesLE()) - err := bcGetTransactionFromBlock(context, v) + err := bcGetTransactionFromBlock(context) require.NoError(t, err) _, ok := v.Estack().Pop().Item().(stackitem.Null) @@ -120,7 +120,7 @@ func TestBCGetTransactionFromBlock(t *testing.T) { require.NoError(t, context.DAO.StoreAsBlock(block)) v.Estack().PushVal(1) v.Estack().PushVal(block.Hash().BytesBE()) - err := bcGetTransactionFromBlock(context, v) + err := bcGetTransactionFromBlock(context) require.Error(t, err) }) } @@ -133,7 +133,7 @@ func TestBCGetBlock(t *testing.T) { t.Run("success", func(t *testing.T) { v.Estack().PushVal(block.Hash().BytesBE()) - err := bcGetBlock(context, v) + err := bcGetBlock(context) require.NoError(t, err) value := v.Estack().Pop().Value() @@ -152,7 +152,7 @@ func TestBCGetBlock(t *testing.T) { t.Run("bad hash", func(t *testing.T) { v.Estack().PushVal(block.Hash().BytesLE()) - err := bcGetTransaction(context, v) + err := bcGetTransaction(context) require.NoError(t, err) _, ok := v.Estack().Pop().Item().(stackitem.Null) @@ -180,14 +180,14 @@ func TestContractIsStandard(t *testing.T) { t.Run("true", func(t *testing.T) { v.Estack().PushVal(pub.GetScriptHash().BytesBE()) - require.NoError(t, contractIsStandard(ic, v)) + require.NoError(t, contractIsStandard(ic)) require.True(t, v.Estack().Pop().Bool()) }) t.Run("false", func(t *testing.T) { tx.Scripts[0].VerificationScript = []byte{9, 8, 7} v.Estack().PushVal(pub.GetScriptHash().BytesBE()) - require.NoError(t, contractIsStandard(ic, v)) + require.NoError(t, contractIsStandard(ic)) require.False(t, v.Estack().Pop().Bool()) }) }) @@ -201,7 +201,7 @@ func TestContractIsStandard(t *testing.T) { require.NoError(t, err) v.Estack().PushVal(pub.GetScriptHash().BytesBE()) - require.NoError(t, contractIsStandard(ic, v)) + require.NoError(t, contractIsStandard(ic)) require.True(t, v.Estack().Pop().Bool()) }) t.Run("contract stored, false", func(t *testing.T) { @@ -209,7 +209,7 @@ func TestContractIsStandard(t *testing.T) { require.NoError(t, ic.DAO.PutContractState(&state.Contract{ID: 24, Script: script})) v.Estack().PushVal(crypto.Hash160(script).BytesBE()) - require.NoError(t, contractIsStandard(ic, v)) + require.NoError(t, contractIsStandard(ic)) require.False(t, v.Estack().Pop().Bool()) }) } @@ -222,7 +222,7 @@ func TestContractCreateAccount(t *testing.T) { require.NoError(t, err) pub := priv.PublicKey() v.Estack().PushVal(pub.Bytes()) - require.NoError(t, contractCreateStandardAccount(ic, v)) + require.NoError(t, contractCreateStandardAccount(ic)) value := v.Estack().Pop().Bytes() u, err := util.Uint160DecodeBytesBE(value) @@ -231,7 +231,7 @@ func TestContractCreateAccount(t *testing.T) { }) t.Run("InvalidKey", func(t *testing.T) { v.Estack().PushVal([]byte{1, 2, 3}) - require.Error(t, contractCreateStandardAccount(ic, v)) + require.Error(t, contractCreateStandardAccount(ic)) }) } @@ -241,7 +241,7 @@ func TestRuntimeGasLeft(t *testing.T) { v.GasLimit = 100 v.AddGas(58) - require.NoError(t, runtime.GasLeft(ic, v)) + require.NoError(t, runtime.GasLeft(ic)) require.EqualValues(t, 42, v.Estack().Pop().BigInt().Int64()) } @@ -257,7 +257,7 @@ func TestRuntimeGetNotifications(t *testing.T) { t.Run("NoFilter", func(t *testing.T) { v.Estack().PushVal(stackitem.Null{}) - require.NoError(t, runtime.GetNotifications(ic, v)) + require.NoError(t, runtime.GetNotifications(ic)) arr := v.Estack().Pop().Array() require.Equal(t, len(ic.Notifications), len(arr)) @@ -274,7 +274,7 @@ func TestRuntimeGetNotifications(t *testing.T) { t.Run("WithFilter", func(t *testing.T) { h := util.Uint160{2}.BytesBE() v.Estack().PushVal(h) - require.NoError(t, runtime.GetNotifications(ic, v)) + require.NoError(t, runtime.GetNotifications(ic)) arr := v.Estack().Pop().Array() require.Equal(t, 1, len(arr)) @@ -295,11 +295,11 @@ func TestRuntimeGetInvocationCounter(t *testing.T) { t.Run("Zero", func(t *testing.T) { v.LoadScript([]byte{1}) - require.Error(t, runtime.GetInvocationCounter(ic, v)) + require.Error(t, runtime.GetInvocationCounter(ic)) }) t.Run("NonZero", func(t *testing.T) { v.LoadScript([]byte{2}) - require.NoError(t, runtime.GetInvocationCounter(ic, v)) + require.NoError(t, runtime.GetInvocationCounter(ic)) require.EqualValues(t, 42, v.Estack().Pop().BigInt().Int64()) }) } @@ -311,7 +311,7 @@ func TestBlockchainGetContractState(t *testing.T) { t.Run("positive", func(t *testing.T) { v.Estack().PushVal(cs.ScriptHash().BytesBE()) - require.NoError(t, bcGetContract(ic, v)) + require.NoError(t, bcGetContract(ic)) actual := v.Estack().Pop().Item() compareContractStates(t, cs, actual) @@ -319,7 +319,7 @@ func TestBlockchainGetContractState(t *testing.T) { t.Run("uncknown contract state", func(t *testing.T) { v.Estack().PushVal(util.Uint160{1, 2, 3}.BytesBE()) - require.NoError(t, bcGetContract(ic, v)) + require.NoError(t, bcGetContract(ic)) actual := v.Estack().Pop().Item() require.Equal(t, stackitem.Null{}, actual) @@ -394,14 +394,14 @@ func getTestContractState() (*state.Contract, *state.Contract) { } } -func loadScript(script []byte, args ...interface{}) *vm.VM { +func loadScript(ic *interop.Context, script []byte, args ...interface{}) { v := vm.New() v.LoadScriptWithFlags(script, smartcontract.AllowCall) for i := range args { v.Estack().PushVal(args[i]) } v.GasLimit = -1 - return v + ic.VM = v } func TestContractCall(t *testing.T) { @@ -417,36 +417,36 @@ func TestContractCall(t *testing.T) { addArgs := stackitem.NewArray([]stackitem.Item{stackitem.Make(1), stackitem.Make(2)}) t.Run("Good", func(t *testing.T) { - v := loadScript(currScript, 42) - v.Estack().PushVal(addArgs) - v.Estack().PushVal("add") - v.Estack().PushVal(h.BytesBE()) - require.NoError(t, contractCall(ic, v)) - require.NoError(t, v.Run()) - require.Equal(t, 2, v.Estack().Len()) - require.Equal(t, big.NewInt(3), v.Estack().Pop().Value()) - require.Equal(t, big.NewInt(42), v.Estack().Pop().Value()) + loadScript(ic, currScript, 42) + ic.VM.Estack().PushVal(addArgs) + ic.VM.Estack().PushVal("add") + ic.VM.Estack().PushVal(h.BytesBE()) + require.NoError(t, contractCall(ic)) + require.NoError(t, ic.VM.Run()) + require.Equal(t, 2, ic.VM.Estack().Len()) + require.Equal(t, big.NewInt(3), ic.VM.Estack().Pop().Value()) + require.Equal(t, big.NewInt(42), ic.VM.Estack().Pop().Value()) }) t.Run("CallExInvalidFlag", func(t *testing.T) { - v := loadScript(currScript, 42) - v.Estack().PushVal(byte(0xFF)) - v.Estack().PushVal(addArgs) - v.Estack().PushVal("add") - v.Estack().PushVal(h.BytesBE()) - require.Error(t, contractCallEx(ic, v)) + loadScript(ic, currScript, 42) + ic.VM.Estack().PushVal(byte(0xFF)) + ic.VM.Estack().PushVal(addArgs) + ic.VM.Estack().PushVal("add") + ic.VM.Estack().PushVal(h.BytesBE()) + require.Error(t, contractCallEx(ic)) }) runInvalid := func(args ...interface{}) func(t *testing.T) { return func(t *testing.T) { - v := loadScript(currScript, 42) + loadScript(ic, currScript, 42) for i := range args { - v.Estack().PushVal(args[i]) + ic.VM.Estack().PushVal(args[i]) } // interops can both return error and panic, // we don't care which kind of error has occured require.Panics(t, func() { - err := contractCall(ic, v) + err := contractCall(ic) if err != nil { panic(err) } @@ -466,26 +466,26 @@ func TestContractCall(t *testing.T) { }) t.Run("IsolatedStack", func(t *testing.T) { - v := loadScript(currScript, 42) - v.Estack().PushVal(stackitem.NewArray(nil)) - v.Estack().PushVal("drop") - v.Estack().PushVal(h.BytesBE()) - require.NoError(t, contractCall(ic, v)) - require.Error(t, v.Run()) + loadScript(ic, currScript, 42) + ic.VM.Estack().PushVal(stackitem.NewArray(nil)) + ic.VM.Estack().PushVal("drop") + ic.VM.Estack().PushVal(h.BytesBE()) + require.NoError(t, contractCall(ic)) + require.Error(t, ic.VM.Run()) }) t.Run("CallInitialize", func(t *testing.T) { t.Run("Directly", runInvalid(stackitem.NewArray([]stackitem.Item{}), "_initialize", h.BytesBE())) - v := loadScript(currScript, 42) - v.Estack().PushVal(stackitem.NewArray([]stackitem.Item{stackitem.Make(5)})) - v.Estack().PushVal("add3") - v.Estack().PushVal(h.BytesBE()) - require.NoError(t, contractCall(ic, v)) - require.NoError(t, v.Run()) - require.Equal(t, 2, v.Estack().Len()) - require.Equal(t, big.NewInt(8), v.Estack().Pop().Value()) - require.Equal(t, big.NewInt(42), v.Estack().Pop().Value()) + loadScript(ic, currScript, 42) + ic.VM.Estack().PushVal(stackitem.NewArray([]stackitem.Item{stackitem.Make(5)})) + ic.VM.Estack().PushVal("add3") + ic.VM.Estack().PushVal(h.BytesBE()) + require.NoError(t, contractCall(ic)) + require.NoError(t, ic.VM.Run()) + require.Equal(t, 2, ic.VM.Estack().Len()) + require.Equal(t, big.NewInt(8), ic.VM.Estack().Pop().Value()) + require.Equal(t, big.NewInt(42), ic.VM.Estack().Pop().Value()) }) } @@ -504,7 +504,7 @@ func TestContractCreate(t *testing.T) { t.Run("positive", func(t *testing.T) { putArgsOnStack() - require.NoError(t, contractCreate(ic, v)) + require.NoError(t, contractCreate(ic)) actual := v.Estack().Pop().Item() compareContractStates(t, cs, actual) }) @@ -513,7 +513,7 @@ func TestContractCreate(t *testing.T) { cs.Script = append(cs.Script, 0x01) putArgsOnStack() - require.Error(t, contractCreate(ic, v)) + require.Error(t, contractCreate(ic)) }) t.Run("contract already exists", func(t *testing.T) { @@ -521,7 +521,7 @@ func TestContractCreate(t *testing.T) { require.NoError(t, ic.DAO.PutContractState(cs)) putArgsOnStack() - require.Error(t, contractCreate(ic, v)) + require.Error(t, contractCreate(ic)) }) } @@ -553,27 +553,27 @@ func TestContractUpdate(t *testing.T) { require.NoError(t, ic.DAO.PutContractState(cs)) v.LoadScriptWithHash([]byte{byte(opcode.RET)}, cs.ScriptHash(), smartcontract.All) putArgsOnStack(nil, nil) - require.NoError(t, contractUpdate(ic, v)) + require.NoError(t, contractUpdate(ic)) }) t.Run("no contract", func(t *testing.T) { require.NoError(t, ic.DAO.PutContractState(cs)) v.LoadScriptWithHash([]byte{byte(opcode.RET)}, util.Uint160{8, 9, 7}, smartcontract.All) - require.Error(t, contractUpdate(ic, v)) + require.Error(t, contractUpdate(ic)) }) t.Run("too large script", func(t *testing.T) { require.NoError(t, ic.DAO.PutContractState(cs)) v.LoadScriptWithHash([]byte{byte(opcode.RET)}, cs.ScriptHash(), smartcontract.All) putArgsOnStack(make([]byte, MaxContractScriptSize+1), nil) - require.Error(t, contractUpdate(ic, v)) + require.Error(t, contractUpdate(ic)) }) t.Run("too large manifest", func(t *testing.T) { require.NoError(t, ic.DAO.PutContractState(cs)) v.LoadScriptWithHash([]byte{byte(opcode.RET)}, cs.ScriptHash(), smartcontract.All) putArgsOnStack(nil, make([]byte, manifest.MaxManifestSize+1)) - require.Error(t, contractUpdate(ic, v)) + require.Error(t, contractUpdate(ic)) }) t.Run("gas limit exceeded", func(t *testing.T) { @@ -581,7 +581,7 @@ func TestContractUpdate(t *testing.T) { v.GasLimit = 0 v.LoadScriptWithHash([]byte{byte(opcode.RET)}, cs.ScriptHash(), smartcontract.All) putArgsOnStack([]byte{1}, []byte{2}) - require.Error(t, contractUpdate(ic, v)) + require.Error(t, contractUpdate(ic)) }) t.Run("update script, the same script", func(t *testing.T) { @@ -590,7 +590,7 @@ func TestContractUpdate(t *testing.T) { v.LoadScriptWithHash([]byte{byte(opcode.RET)}, cs.ScriptHash(), smartcontract.All) putArgsOnStack(cs.Script, nil) - require.Error(t, contractUpdate(ic, v)) + require.Error(t, contractUpdate(ic)) }) t.Run("update script, already exists", func(t *testing.T) { @@ -608,7 +608,7 @@ func TestContractUpdate(t *testing.T) { v.LoadScriptWithHash([]byte{byte(opcode.RET)}, cs.ScriptHash(), smartcontract.All) putArgsOnStack(duplicateScript, nil) - require.Error(t, contractUpdate(ic, v)) + require.Error(t, contractUpdate(ic)) }) t.Run("update script, positive", func(t *testing.T) { @@ -617,7 +617,7 @@ func TestContractUpdate(t *testing.T) { newScript := []byte{9, 8, 7, 6, 5} putArgsOnStack(newScript, nil) - require.NoError(t, contractUpdate(ic, v)) + require.NoError(t, contractUpdate(ic)) // updated contract should have new scripthash actual, err := ic.DAO.GetContractState(hash.Hash160(newScript)) @@ -641,7 +641,7 @@ func TestContractUpdate(t *testing.T) { v.LoadScriptWithHash([]byte{byte(opcode.RET)}, cs.ScriptHash(), smartcontract.All) putArgsOnStack(nil, []byte{1, 2, 3}) - require.Error(t, contractUpdate(ic, v)) + require.Error(t, contractUpdate(ic)) }) t.Run("update manifest, bad contract hash", func(t *testing.T) { @@ -656,7 +656,7 @@ func TestContractUpdate(t *testing.T) { require.NoError(t, err) putArgsOnStack(nil, manifestBytes) - require.Error(t, contractUpdate(ic, v)) + require.Error(t, contractUpdate(ic)) }) t.Run("update manifest, old contract shouldn't have storage", func(t *testing.T) { @@ -676,7 +676,7 @@ func TestContractUpdate(t *testing.T) { require.NoError(t, err) putArgsOnStack(nil, manifestBytes) - require.Error(t, contractUpdate(ic, v)) + require.Error(t, contractUpdate(ic)) }) t.Run("update manifest, positive", func(t *testing.T) { @@ -693,7 +693,7 @@ func TestContractUpdate(t *testing.T) { require.NoError(t, err) putArgsOnStack(nil, manifestBytes) - require.NoError(t, contractUpdate(ic, v)) + require.NoError(t, contractUpdate(ic)) // updated contract should have new scripthash actual, err := ic.DAO.GetContractState(cs.ScriptHash()) @@ -721,7 +721,7 @@ func TestContractUpdate(t *testing.T) { putArgsOnStack(newScript, newManifestBytes) - require.NoError(t, contractUpdate(ic, v)) + require.NoError(t, contractUpdate(ic)) // updated contract should have new script and manifest actual, err := ic.DAO.GetContractState(hash.Hash160(newScript)) @@ -746,7 +746,7 @@ func TestContractGetCallFlags(t *testing.T) { defer bc.Close() v.LoadScriptWithHash([]byte{byte(opcode.RET)}, util.Uint160{1, 2, 3}, smartcontract.All) - require.NoError(t, contractGetCallFlags(ic, v)) + require.NoError(t, contractGetCallFlags(ic)) require.Equal(t, int64(smartcontract.All), v.Estack().Pop().Value().(*big.Int).Int64()) } @@ -759,27 +759,27 @@ func TestPointerCallback(t *testing.T) { byte(opcode.DIV), byte(opcode.RET), } t.Run("Good", func(t *testing.T) { - v := loadScript(script, 2, stackitem.NewPointer(3, script)) - v.Estack().PushVal(v.Context()) - require.NoError(t, callback.Create(ic, v)) + loadScript(ic, script, 2, stackitem.NewPointer(3, script)) + ic.VM.Estack().PushVal(ic.VM.Context()) + require.NoError(t, callback.Create(ic)) args := stackitem.NewArray([]stackitem.Item{stackitem.Make(3), stackitem.Make(12)}) - v.Estack().InsertAt(vm.NewElement(args), 1) - require.NoError(t, callback.Invoke(ic, v)) + ic.VM.Estack().InsertAt(vm.NewElement(args), 1) + require.NoError(t, callback.Invoke(ic)) - require.NoError(t, v.Run()) - require.Equal(t, 1, v.Estack().Len()) - require.Equal(t, big.NewInt(5), v.Estack().Pop().Item().Value()) + require.NoError(t, ic.VM.Run()) + require.Equal(t, 1, ic.VM.Estack().Len()) + require.Equal(t, big.NewInt(5), ic.VM.Estack().Pop().Item().Value()) }) t.Run("Invalid", func(t *testing.T) { t.Run("NotEnoughParameters", func(t *testing.T) { - v := loadScript(script, 2, stackitem.NewPointer(3, script)) - v.Estack().PushVal(v.Context()) - require.NoError(t, callback.Create(ic, v)) + loadScript(ic, script, 2, stackitem.NewPointer(3, script)) + ic.VM.Estack().PushVal(ic.VM.Context()) + require.NoError(t, callback.Create(ic)) args := stackitem.NewArray([]stackitem.Item{stackitem.Make(3)}) - v.Estack().InsertAt(vm.NewElement(args), 1) - require.Error(t, callback.Invoke(ic, v)) + ic.VM.Estack().InsertAt(vm.NewElement(args), 1) + require.Error(t, callback.Invoke(ic)) }) }) @@ -799,11 +799,11 @@ func TestMethodCallback(t *testing.T) { t.Run("Invalid", func(t *testing.T) { runInvalid := func(args ...interface{}) func(t *testing.T) { return func(t *testing.T) { - v := loadScript(currCs.Script, 42) + loadScript(ic, currCs.Script, 42) for i := range args { - v.Estack().PushVal(args[i]) + ic.VM.Estack().PushVal(args[i]) } - require.Error(t, callback.CreateFromMethod(ic, v)) + require.Error(t, callback.CreateFromMethod(ic)) } } t.Run("Hash", runInvalid("add", rawHash[1:])) @@ -812,37 +812,38 @@ func TestMethodCallback(t *testing.T) { t.Run("DisallowedMethod", runInvalid("ret7", rawHash)) t.Run("Initialize", runInvalid("_initialize", rawHash)) t.Run("NotEnoughArguments", func(t *testing.T) { - v := loadScript(currCs.Script, 42, "add", rawHash) - require.NoError(t, callback.CreateFromMethod(ic, v)) + loadScript(ic, currCs.Script, 42, "add", rawHash) + require.NoError(t, callback.CreateFromMethod(ic)) - v.Estack().InsertAt(vm.NewElement(stackitem.NewArray([]stackitem.Item{stackitem.Make(1)})), 1) - require.Error(t, callback.Invoke(ic, v)) + ic.VM.Estack().InsertAt(vm.NewElement(stackitem.NewArray([]stackitem.Item{stackitem.Make(1)})), 1) + require.Error(t, callback.Invoke(ic)) }) t.Run("CallIsNotAllowed", func(t *testing.T) { v := vm.New() + ic.VM = v v.Load(currCs.Script) v.Estack().PushVal("add") v.Estack().PushVal(rawHash) - require.NoError(t, callback.CreateFromMethod(ic, v)) + require.NoError(t, callback.CreateFromMethod(ic)) args := stackitem.NewArray([]stackitem.Item{stackitem.Make(1), stackitem.Make(5)}) v.Estack().InsertAt(vm.NewElement(args), 1) - require.Error(t, callback.Invoke(ic, v)) + require.Error(t, callback.Invoke(ic)) }) }) t.Run("Good", func(t *testing.T) { - v := loadScript(currCs.Script, 42, "add", rawHash) - require.NoError(t, callback.CreateFromMethod(ic, v)) + loadScript(ic, currCs.Script, 42, "add", rawHash) + require.NoError(t, callback.CreateFromMethod(ic)) args := stackitem.NewArray([]stackitem.Item{stackitem.Make(1), stackitem.Make(5)}) - v.Estack().InsertAt(vm.NewElement(args), 1) + ic.VM.Estack().InsertAt(vm.NewElement(args), 1) - require.NoError(t, callback.Invoke(ic, v)) - require.NoError(t, v.Run()) - require.Equal(t, 2, v.Estack().Len()) - require.Equal(t, big.NewInt(6), v.Estack().Pop().Item().Value()) - require.Equal(t, big.NewInt(42), v.Estack().Pop().Item().Value()) + require.NoError(t, callback.Invoke(ic)) + require.NoError(t, ic.VM.Run()) + require.Equal(t, 2, ic.VM.Estack().Len()) + require.Equal(t, big.NewInt(6), ic.VM.Estack().Pop().Item().Value()) + require.Equal(t, big.NewInt(42), ic.VM.Estack().Pop().Item().Value()) }) } func TestSyscallCallback(t *testing.T) { @@ -852,44 +853,44 @@ func TestSyscallCallback(t *testing.T) { ic.Functions = append(ic.Functions, []interop.Function{ { ID: 0x42, - Func: func(_ *interop.Context, v *vm.VM) error { - a := v.Estack().Pop().BigInt() - b := v.Estack().Pop().BigInt() - v.Estack().PushVal(new(big.Int).Add(a, b)) + Func: func(ic *interop.Context) error { + a := ic.VM.Estack().Pop().BigInt() + b := ic.VM.Estack().Pop().BigInt() + ic.VM.Estack().PushVal(new(big.Int).Add(a, b)) return nil }, ParamCount: 2, }, { ID: 0x53, - Func: func(_ *interop.Context, _ *vm.VM) error { return nil }, + Func: func(_ *interop.Context) error { return nil }, DisallowCallback: true, }, }) t.Run("Good", func(t *testing.T) { args := stackitem.NewArray([]stackitem.Item{stackitem.Make(12), stackitem.Make(30)}) - v := loadScript([]byte{byte(opcode.RET)}, args, 0x42) - require.NoError(t, callback.CreateFromSyscall(ic, v)) - require.NoError(t, callback.Invoke(ic, v)) - require.Equal(t, 1, v.Estack().Len()) - require.Equal(t, big.NewInt(42), v.Estack().Pop().Item().Value()) + loadScript(ic, []byte{byte(opcode.RET)}, args, 0x42) + require.NoError(t, callback.CreateFromSyscall(ic)) + require.NoError(t, callback.Invoke(ic)) + require.Equal(t, 1, ic.VM.Estack().Len()) + require.Equal(t, big.NewInt(42), ic.VM.Estack().Pop().Item().Value()) }) t.Run("Invalid", func(t *testing.T) { t.Run("InvalidParameterCount", func(t *testing.T) { args := stackitem.NewArray([]stackitem.Item{stackitem.Make(12)}) - v := loadScript([]byte{byte(opcode.RET)}, args, 0x42) - require.NoError(t, callback.CreateFromSyscall(ic, v)) - require.Error(t, callback.Invoke(ic, v)) + loadScript(ic, []byte{byte(opcode.RET)}, args, 0x42) + require.NoError(t, callback.CreateFromSyscall(ic)) + require.Error(t, callback.Invoke(ic)) }) t.Run("MissingSyscall", func(t *testing.T) { - v := loadScript([]byte{byte(opcode.RET)}, stackitem.NewArray(nil), 0x43) - require.Error(t, callback.CreateFromSyscall(ic, v)) + loadScript(ic, []byte{byte(opcode.RET)}, stackitem.NewArray(nil), 0x43) + require.Error(t, callback.CreateFromSyscall(ic)) }) t.Run("Disallowed", func(t *testing.T) { - v := loadScript([]byte{byte(opcode.RET)}, stackitem.NewArray(nil), 0x53) - require.Error(t, callback.CreateFromSyscall(ic, v)) + loadScript(ic, []byte{byte(opcode.RET)}, stackitem.NewArray(nil), 0x53) + require.Error(t, callback.CreateFromSyscall(ic)) }) }) } diff --git a/pkg/core/interops_test.go b/pkg/core/interops_test.go index 4c811363b..748744e0b 100644 --- a/pkg/core/interops_test.go +++ b/pkg/core/interops_test.go @@ -14,13 +14,14 @@ import ( "github.com/stretchr/testify/require" ) -func testNonInterop(t *testing.T, value interface{}, f func(*interop.Context, *vm.VM) error) { +func testNonInterop(t *testing.T, value interface{}, f func(*interop.Context) error) { v := vm.New() v.Estack().PushVal(value) chain := newTestChain(t) defer chain.Close() context := chain.newInteropContext(trigger.Application, dao.NewSimple(storage.NewMemoryStore(), netmode.UnitTestNet), nil, nil) - require.Error(t, f(context, v)) + context.VM = v + require.Error(t, f(context)) } func TestUnexpectedNonInterops(t *testing.T) { @@ -32,7 +33,7 @@ func TestUnexpectedNonInterops(t *testing.T) { } // All of these functions expect an interop item on the stack. - funcs := []func(*interop.Context, *vm.VM) error{ + funcs := []func(*interop.Context) error{ storageContextAsReadOnly, storageDelete, storageFind, diff --git a/pkg/core/native/interop.go b/pkg/core/native/interop.go index cdbed93cc..0c752f8e2 100644 --- a/pkg/core/native/interop.go +++ b/pkg/core/native/interop.go @@ -6,11 +6,10 @@ import ( "github.com/nspcc-dev/neo-go/pkg/core/interop" "github.com/nspcc-dev/neo-go/pkg/core/state" - "github.com/nspcc-dev/neo-go/pkg/vm" ) // Deploy deploys native contract. -func Deploy(ic *interop.Context, _ *vm.VM) error { +func Deploy(ic *interop.Context) error { if ic.Block == nil || ic.Block.Index != 0 { return errors.New("native contracts can be deployed only at 0 block") } @@ -34,8 +33,8 @@ func Deploy(ic *interop.Context, _ *vm.VM) error { } // Call calls specified native contract method. -func Call(ic *interop.Context, v *vm.VM) error { - name := v.Estack().Pop().String() +func Call(ic *interop.Context) error { + name := ic.VM.Estack().Pop().String() var c interop.Contract for _, ctr := range ic.Natives { if ctr.Metadata().Name == name { @@ -46,23 +45,23 @@ func Call(ic *interop.Context, v *vm.VM) error { if c == nil { return fmt.Errorf("native contract %s not found", name) } - h := v.GetCurrentScriptHash() + h := ic.VM.GetCurrentScriptHash() if !h.Equals(c.Metadata().Hash) { return errors.New("it is not allowed to use Neo.Native.Call directly to call native contracts. System.Contract.Call should be used") } - operation := v.Estack().Pop().String() - args := v.Estack().Pop().Array() + operation := ic.VM.Estack().Pop().String() + args := ic.VM.Estack().Pop().Array() m, ok := c.Metadata().Methods[operation] if !ok { return fmt.Errorf("method %s not found", operation) } - if !v.Context().GetCallFlags().Has(m.RequiredFlags) { + if !ic.VM.Context().GetCallFlags().Has(m.RequiredFlags) { return errors.New("missing call flags") } - if !v.AddGas(m.Price) { + if !ic.VM.AddGas(m.Price) { return errors.New("gas limit exceeded") } result := m.Func(ic, args) - v.Estack().PushVal(result) + ic.VM.Estack().PushVal(result) return nil } diff --git a/pkg/core/native/native_nep5.go b/pkg/core/native/native_nep5.go index 938395fbe..e09b3a0a5 100644 --- a/pkg/core/native/native_nep5.go +++ b/pkg/core/native/native_nep5.go @@ -177,7 +177,7 @@ func (c *nep5TokenNative) TransferInternal(ic *interop.Context, from, to util.Ui return errors.New("negative amount") } - caller := ic.ScriptGetter.GetCallingScriptHash() + caller := ic.VM.GetCallingScriptHash() if caller.Equals(util.Uint160{}) || !from.Equals(caller) { ok, err := runtime.CheckHashedWitness(ic, from) if err != nil { diff --git a/pkg/core/native_contract_test.go b/pkg/core/native_contract_test.go index c4d1479f3..222af4c4d 100644 --- a/pkg/core/native_contract_test.go +++ b/pkg/core/native_contract_test.go @@ -11,6 +11,7 @@ import ( "github.com/nspcc-dev/neo-go/pkg/core/state" "github.com/nspcc-dev/neo-go/pkg/core/storage" "github.com/nspcc-dev/neo-go/pkg/core/transaction" + "github.com/nspcc-dev/neo-go/pkg/crypto/hash" "github.com/nspcc-dev/neo-go/pkg/io" "github.com/nspcc-dev/neo-go/pkg/smartcontract" "github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest" @@ -18,6 +19,7 @@ import ( "github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/vm" "github.com/nspcc-dev/neo-go/pkg/vm/emit" + "github.com/nspcc-dev/neo-go/pkg/vm/opcode" "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" "github.com/stretchr/testify/require" ) @@ -76,6 +78,35 @@ func newTestNative() *testNative { } tn.meta.AddMethod(md, desc, true) + desc = &manifest.Method{ + Name: "callOtherContractWithoutArgs", + Parameters: []manifest.Parameter{ + manifest.NewParameter("contractHash", smartcontract.Hash160Type), + manifest.NewParameter("method", smartcontract.StringType), + }, + ReturnType: smartcontract.AnyType, + } + md = &interop.MethodAndPrice{ + Func: tn.callOtherContractWithoutArgs, + Price: testSumPrice, + RequiredFlags: smartcontract.NoneFlag} + tn.meta.AddMethod(md, desc, true) + + desc = &manifest.Method{ + Name: "callOtherContractWithArg", + Parameters: []manifest.Parameter{ + manifest.NewParameter("contractHash", smartcontract.Hash160Type), + manifest.NewParameter("method", smartcontract.StringType), + manifest.NewParameter("arg", smartcontract.ArrayType), + }, + ReturnType: smartcontract.AnyType, + } + md = &interop.MethodAndPrice{ + Func: tn.callOtherContractWithArg, + Price: testSumPrice, + RequiredFlags: smartcontract.NoneFlag} + tn.meta.AddMethod(md, desc, true) + desc = &manifest.Method{Name: "onPersist", ReturnType: smartcontract.BoolType} md = &interop.MethodAndPrice{Func: tn.OnPersist, RequiredFlags: smartcontract.AllowModifyStates} tn.meta.AddMethod(md, desc, false) @@ -95,6 +126,38 @@ func (tn *testNative) sum(_ *interop.Context, args []stackitem.Item) stackitem.I return stackitem.NewBigInteger(s1.Add(s1, s2)) } +func (tn *testNative) callOtherContractWithoutArgs(ic *interop.Context, args []stackitem.Item) stackitem.Item { + vm := ic.VM + vm.Estack().PushVal(stackitem.NewArray([]stackitem.Item{})) // no args + vm.Estack().PushVal(args[1]) // method + vm.Estack().PushVal(args[0]) // contract hash + err := contractCall(ic) + if err != nil { + return stackitem.NewBigInteger(big.NewInt(-1)) + } + _ = vm.Run() + if vm.HasFailed() { + return stackitem.NewBigInteger(big.NewInt(-2)) + } + return vm.Estack().Pop().Item() +} + +func (tn *testNative) callOtherContractWithArg(ic *interop.Context, args []stackitem.Item) stackitem.Item { + vm := ic.VM + vm.Estack().PushVal(stackitem.NewArray([]stackitem.Item{args[2]})) // arg + vm.Estack().PushVal(args[1]) // method + vm.Estack().PushVal(args[0]) // contract hash + err := contractCall(ic) + if err != nil { + return stackitem.NewBigInteger(big.NewInt(-1)) + } + _ = vm.Run() + if vm.HasFailed() { + return stackitem.NewBigInteger(big.NewInt(-2)) + } + return vm.Estack().Pop().Item() +} + func TestNativeContract_Invoke(t *testing.T) { chain := newTestChain(t) defer chain.Close() @@ -159,10 +222,9 @@ func TestNativeContract_InvokeInternal(t *testing.T) { }) require.NoError(t, err) - v := vm.New() - v.GasLimit = -1 ic := chain.newInteropContext(trigger.Application, dao.NewSimple(storage.NewMemoryStore(), netmode.UnitTestNet), nil, nil) + v := ic.SpawnVM() t.Run("fail, bad current script hash", func(t *testing.T) { v.LoadScriptWithHash([]byte{1}, util.Uint160{1, 2, 3}, smartcontract.All) @@ -171,7 +233,7 @@ func TestNativeContract_InvokeInternal(t *testing.T) { v.Estack().PushVal(tn.Metadata().Name) // it's prohibited to call natives directly - require.Error(t, native.Call(ic, v)) + require.Error(t, native.Call(ic)) }) t.Run("success", func(t *testing.T) { @@ -180,9 +242,117 @@ func TestNativeContract_InvokeInternal(t *testing.T) { v.Estack().PushVal("sum") v.Estack().PushVal(tn.Metadata().Name) - require.NoError(t, native.Call(ic, v)) + require.NoError(t, native.Call(ic)) value := v.Estack().Pop().BigInt() require.Equal(t, int64(42), value.Int64()) }) } + +func TestNativeContract_InvokeOtherContract(t *testing.T) { + chain := newTestChain(t) + defer chain.Close() + + tn := newTestNative() + chain.registerNative(tn) + + err := chain.dao.PutContractState(&state.Contract{ + Script: tn.meta.Script, + Manifest: tn.meta.Manifest, + }) + require.NoError(t, err) + + t.Run("native Policy, getFeePerByte", func(t *testing.T) { + w := io.NewBufBinWriter() + emit.AppCallWithOperationAndArgs(w.BinWriter, tn.Metadata().Hash, "callOtherContractWithoutArgs", chain.contracts.Policy.Hash, "getFeePerByte") + require.NoError(t, w.Err) + script := w.Bytes() + tx := transaction.New(chain.GetConfig().Magic, script, testSumPrice*4+10000) + validUntil := chain.blockHeight + 1 + tx.ValidUntilBlock = validUntil + addSigners(tx) + require.NoError(t, signTx(chain, tx)) + + b := chain.newBlock(tx) + require.NoError(t, chain.AddBlock(b)) + + res, err := chain.GetAppExecResult(tx.Hash()) + require.NoError(t, err) + // we expect it to be FeePerByte from Policy contract + require.Equal(t, vm.HaltState, res.VMState) + require.Equal(t, 1, len(res.Stack)) + require.Equal(t, big.NewInt(1000), res.Stack[0].Value()) + }) + + t.Run("native Policy, setFeePerByte", func(t *testing.T) { + w := io.NewBufBinWriter() + emit.AppCallWithOperationAndArgs(w.BinWriter, tn.Metadata().Hash, "callOtherContractWithArg", chain.contracts.Policy.Hash, "setFeePerByte", int64(500)) + require.NoError(t, w.Err) + script := w.Bytes() + tx := transaction.New(chain.GetConfig().Magic, script, testSumPrice*5+10000) + validUntil := chain.blockHeight + 1 + tx.ValidUntilBlock = validUntil + addSigners(tx) + // to pass policy.checkValidators + tx.Signers[0].Scopes = transaction.Global + require.NoError(t, signTx(chain, tx)) + + b := chain.newBlock(tx) + require.NoError(t, chain.AddBlock(b)) + + require.NoError(t, chain.persist()) + + res, err := chain.GetAppExecResult(tx.Hash()) + require.NoError(t, err) + // we expect it to be `true` which means that native policy value was successfully updated + require.Equal(t, vm.HaltState, res.VMState) + require.Equal(t, 1, len(res.Stack)) + require.Equal(t, true, res.Stack[0].Value()) + + require.NoError(t, chain.persist()) + + // check that feePerByte was updated + n := chain.contracts.Policy.GetFeePerByteInternal(chain.dao) + require.Equal(t, 500, int(n)) + }) + + t.Run("non-native contract", func(t *testing.T) { + // put some other contract into chain (this contract just pushes `5` on stack) + avm := []byte{byte(opcode.PUSH5), byte(opcode.RET)} + contractHash := hash.Hash160(avm) + m := manifest.NewManifest(contractHash) + m.ABI.Methods = []manifest.Method{ + { + Name: "five", + Offset: 0, + Parameters: []manifest.Parameter{}, + ReturnType: smartcontract.IntegerType, + }, + } + + err = chain.dao.PutContractState(&state.Contract{ + Script: avm, + Manifest: *m, + }) + require.NoError(t, err) + + w := io.NewBufBinWriter() + emit.AppCallWithOperationAndArgs(w.BinWriter, tn.Metadata().Hash, "callOtherContractWithoutArgs", contractHash, "five") + require.NoError(t, w.Err) + script := w.Bytes() + tx := transaction.New(chain.GetConfig().Magic, script, testSumPrice*4+10000) + validUntil := chain.blockHeight + 1 + tx.ValidUntilBlock = validUntil + addSigners(tx) + require.NoError(t, signTx(chain, tx)) + + b := chain.newBlock(tx) + require.NoError(t, chain.AddBlock(b)) + + res, err := chain.GetAppExecResult(tx.Hash()) + require.NoError(t, err) + require.Equal(t, vm.HaltState, res.VMState) + require.Equal(t, 1, len(res.Stack)) + require.Equal(t, int64(5), res.Stack[0].Value().(*big.Int).Int64()) + }) +} diff --git a/pkg/vm/vm.go b/pkg/vm/vm.go index 7d8f10fca..edb229414 100644 --- a/pkg/vm/vm.go +++ b/pkg/vm/vm.go @@ -40,13 +40,6 @@ func newError(ip int, op opcode.Opcode, err interface{}) *errorAtInstruct { // StateMessage is a vm state message which could be used as additional info for example by cli. type StateMessage string -// ScriptHashGetter defines an interface for getting calling, entry and current script hashes. -type ScriptHashGetter interface { - GetCallingScriptHash() util.Uint160 - GetEntryScriptHash() util.Uint160 - GetCurrentScriptHash() util.Uint160 -} - const ( // MaxInvocationStackSize is the maximum size of an invocation stack. MaxInvocationStackSize = 1024