Merge pull request #1271 from nspcc-dev/core/call_from_native

core: contractCall from native contracts
This commit is contained in:
Roman Khimov 2020-08-07 21:24:50 +03:00 committed by GitHub
commit f5131491b7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
22 changed files with 558 additions and 394 deletions

View file

@ -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())

View file

@ -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
}

View file

@ -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,
}))

View file

@ -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,

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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)
}

View file

@ -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)
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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))
})
})
}

View file

@ -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,

View file

@ -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
}

View file

@ -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 {

View file

@ -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())
})
}

View file

@ -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