diff --git a/pkg/core/interop_neo.go b/pkg/core/interop_neo.go index 44c55b225..852666740 100644 --- a/pkg/core/interop_neo.go +++ b/pkg/core/interop_neo.go @@ -198,6 +198,38 @@ func (ic *interopContext) txGetWitnesses(v *vm.VM) error { return nil } +// invocationTx_GetScript returns invocation script from the current transaction. +func (ic *interopContext) invocationTxGetScript(v *vm.VM) error { + txInterface := v.Estack().Pop().Value() + tx, ok := txInterface.(*transaction.Transaction) + if !ok { + return errors.New("value is not a transaction") + } + inv, ok := tx.Data.(*transaction.InvocationTX) + if tx.Type != transaction.InvocationType || !ok { + return errors.New("value is not an invocation transaction") + } + // It's important not to share inv.Script slice with the code running in VM. + script := make([]byte, len(inv.Script)) + copy(script, inv.Script) + v.Estack().PushVal(script) + return nil +} + +// witnessGetVerificationScript returns current witness' script. +func (ic *interopContext) witnessGetVerificationScript(v *vm.VM) error { + witInterface := v.Estack().Pop().Value() + wit, ok := witInterface.(*transaction.Witness) + if !ok { + return errors.New("value is not a witness") + } + // It's important not to share wit.VerificationScript slice with the code running in VM. + script := make([]byte, len(wit.VerificationScript)) + copy(script, wit.VerificationScript) + v.Estack().PushVal(script) + return nil +} + // bcGetValidators returns validators. func (ic *interopContext) bcGetValidators(v *vm.VM) error { validators := ic.dao.GetValidators() diff --git a/pkg/core/interop_neo_test.go b/pkg/core/interop_neo_test.go index ffe48a92f..0013e9461 100644 --- a/pkg/core/interop_neo_test.go +++ b/pkg/core/interop_neo_test.go @@ -13,6 +13,7 @@ import ( "github.com/CityOfZion/neo-go/pkg/smartcontract/trigger" "github.com/CityOfZion/neo-go/pkg/util" "github.com/CityOfZion/neo-go/pkg/vm" + "github.com/CityOfZion/neo-go/pkg/vm/opcode" "github.com/stretchr/testify/require" ) @@ -115,6 +116,29 @@ func TestTxGetType(t *testing.T) { require.Equal(t, big.NewInt(int64(tx.Type)), value) } +func TestInvocationTxGetScript(t *testing.T) { + v, tx, context := createVMAndPushTX(t) + + err := context.invocationTxGetScript(v) + require.NoError(t, err) + value := v.Estack().Pop().Value().([]byte) + inv := tx.Data.(*transaction.InvocationTX) + require.Equal(t, inv.Script, value) +} + +func TestWitnessGetVerificationScript(t *testing.T) { + v := vm.New() + script := []byte{byte(opcode.PUSHM1), byte(opcode.RET)} + witness := transaction.Witness{InvocationScript: nil, VerificationScript: script} + + context := newInteropContext(trigger.Application, newTestChain(t), storage.NewMemoryStore(), nil, nil) + v.Estack().PushVal(vm.NewInteropItem(&witness)) + err := context.witnessGetVerificationScript(v) + require.NoError(t, err) + value := v.Estack().Pop().Value().([]byte) + require.Equal(t, witness.VerificationScript, value) +} + func TestPopInputFromVM(t *testing.T) { v, tx, _ := createVMAndTX(t) v.Estack().PushVal(vm.NewInteropItem(&tx.Inputs[0])) @@ -395,7 +419,8 @@ func createVMAndAccState(t *testing.T) (*vm.VM, *state.Account, *interopContext) func createVMAndTX(t *testing.T) (*vm.VM, *transaction.Transaction, *interopContext) { v := vm.New() - tx := newMinerTX() + script := []byte{byte(opcode.PUSH1), byte(opcode.RET)} + tx := transaction.NewInvocationTX(script, 0) bytes := make([]byte, 1) attributes := append(tx.Attributes, transaction.Attribute{ diff --git a/pkg/core/interops.go b/pkg/core/interops.go index d8413774d..49472dd3e 100644 --- a/pkg/core/interops.go +++ b/pkg/core/interops.go @@ -152,14 +152,17 @@ var neoInterops = []interopedFunction{ {Name: "Neo.Header.GetVersion", Func: (*interopContext).headerGetVersion, Price: 1}, {Name: "Neo.Input.GetHash", Func: (*interopContext).inputGetHash, Price: 1}, {Name: "Neo.Input.GetIndex", Func: (*interopContext).inputGetIndex, Price: 1}, + {Name: "Neo.InvocationTransaction.GetScript", Func: (*interopContext).invocationTxGetScript, Price: 1}, {Name: "Neo.Output.GetAssetId", Func: (*interopContext).outputGetAssetID, Price: 1}, {Name: "Neo.Output.GetScriptHash", Func: (*interopContext).outputGetScriptHash, Price: 1}, {Name: "Neo.Output.GetValue", Func: (*interopContext).outputGetValue, Price: 1}, {Name: "Neo.Runtime.CheckWitness", Func: (*interopContext).runtimeCheckWitness, Price: 200}, + {Name: "Neo.Runtime.Deserialize", Func: (*interopContext).runtimeDeserialize, Price: 1}, {Name: "Neo.Runtime.GetTime", Func: (*interopContext).runtimeGetTime, Price: 1}, {Name: "Neo.Runtime.GetTrigger", Func: (*interopContext).runtimeGetTrigger, Price: 1}, {Name: "Neo.Runtime.Log", Func: (*interopContext).runtimeLog, Price: 1}, {Name: "Neo.Runtime.Notify", Func: (*interopContext).runtimeNotify, Price: 1}, + {Name: "Neo.Runtime.Serialize", Func: (*interopContext).runtimeSerialize, Price: 1}, {Name: "Neo.Storage.Delete", Func: (*interopContext).storageDelete, Price: 100}, {Name: "Neo.Storage.Get", Func: (*interopContext).storageGet, Price: 100}, {Name: "Neo.Storage.GetContext", Func: (*interopContext).storageGetContext, Price: 1}, @@ -174,20 +177,17 @@ var neoInterops = []interopedFunction{ {Name: "Neo.Transaction.GetType", Func: (*interopContext).txGetType, Price: 1}, {Name: "Neo.Transaction.GetUnspentCoins", Func: (*interopContext).txGetUnspentCoins, Price: 200}, {Name: "Neo.Transaction.GetWitnesses", Func: (*interopContext).txGetWitnesses, Price: 200}, + {Name: "Neo.Witness.GetVerificationScript", Func: (*interopContext).witnessGetVerificationScript, Price: 100}, // {Name: "Neo.Enumerator.Concat", Func: (*interopContext).enumeratorConcat, Price: 1}, // {Name: "Neo.Enumerator.Create", Func: (*interopContext).enumeratorCreate, Price: 1}, // {Name: "Neo.Enumerator.Next", Func: (*interopContext).enumeratorNext, Price: 1}, // {Name: "Neo.Enumerator.Value", Func: (*interopContext).enumeratorValue, Price: 1}, - // {Name: "Neo.InvocationTransaction.GetScript", {ic.invocationTx_GetScript, 1}, // {Name: "Neo.Iterator.Concat", Func: (*interopContext).iteratorConcat, Price: 1}, // {Name: "Neo.Iterator.Create", Func: (*interopContext).iteratorCreate, Price: 1}, // {Name: "Neo.Iterator.Key", Func: (*interopContext).iteratorKey, Price: 1}, // {Name: "Neo.Iterator.Keys", Func: (*interopContext).iteratorKeys, Price: 1}, // {Name: "Neo.Iterator.Values", Func: (*interopContext).iteratorValues, Price: 1}, - {Name: "Neo.Runtime.Deserialize", Func: (*interopContext).runtimeDeserialize, Price: 1}, - {Name: "Neo.Runtime.Serialize", Func: (*interopContext).runtimeSerialize, Price: 1}, // {Name: "Neo.Storage.Find", Func: (*interopContext).storageFind, Price: 1}, - // {Name: "Neo.Witness.GetVerificationScript", Func: (*interopContext).witnessGetVerificationScript, Price: 100}, // Aliases. // {Name: "Neo.Iterator.Next", Func: (*interopContext).enumeratorNext, Price: 1}, diff --git a/pkg/core/interops_test.go b/pkg/core/interops_test.go new file mode 100644 index 000000000..b5a3e96b1 --- /dev/null +++ b/pkg/core/interops_test.go @@ -0,0 +1,88 @@ +package core + +import ( + "reflect" + "runtime" + "testing" + + "github.com/CityOfZion/neo-go/pkg/core/storage" + "github.com/CityOfZion/neo-go/pkg/smartcontract/trigger" + "github.com/CityOfZion/neo-go/pkg/vm" + "github.com/stretchr/testify/require" +) + +func testNonInterop(t *testing.T, value interface{}, f func(*interopContext, *vm.VM) error) { + v := vm.New() + v.Estack().PushVal(value) + context := newInteropContext(trigger.Application, newTestChain(t), storage.NewMemoryStore(), nil, nil) + require.Error(t, f(context, v)) +} + +func TestUnexpectedNonInterops(t *testing.T) { + vals := map[string]interface{}{ + "int": 1, + "bool": false, + "string": "smth", + "array": []int{1, 2, 3}, + } + + // All of these functions expect an interop item on the stack. + funcs := []func(*interopContext, *vm.VM) error{ + (*interopContext).accountGetBalance, + (*interopContext).accountGetScriptHash, + (*interopContext).accountGetVotes, + (*interopContext).assetGetAdmin, + (*interopContext).assetGetAmount, + (*interopContext).assetGetAssetID, + (*interopContext).assetGetAssetType, + (*interopContext).assetGetAvailable, + (*interopContext).assetGetIssuer, + (*interopContext).assetGetOwner, + (*interopContext).assetGetPrecision, + (*interopContext).assetRenew, + (*interopContext).attrGetData, + (*interopContext).attrGetUsage, + (*interopContext).blockGetTransaction, + (*interopContext).blockGetTransactionCount, + (*interopContext).blockGetTransactions, + (*interopContext).contractGetScript, + (*interopContext).contractGetStorageContext, + (*interopContext).contractIsPayable, + (*interopContext).headerGetConsensusData, + (*interopContext).headerGetHash, + (*interopContext).headerGetIndex, + (*interopContext).headerGetMerkleRoot, + (*interopContext).headerGetNextConsensus, + (*interopContext).headerGetPrevHash, + (*interopContext).headerGetTimestamp, + (*interopContext).headerGetVersion, + (*interopContext).inputGetHash, + (*interopContext).inputGetIndex, + (*interopContext).invocationTxGetScript, + (*interopContext).outputGetAssetID, + (*interopContext).outputGetScriptHash, + (*interopContext).outputGetValue, + (*interopContext).storageContextAsReadOnly, + (*interopContext).storageDelete, + (*interopContext).storageGet, + (*interopContext).storagePut, + (*interopContext).storagePutEx, + (*interopContext).txGetAttributes, + (*interopContext).txGetHash, + (*interopContext).txGetInputs, + (*interopContext).txGetOutputs, + (*interopContext).txGetReferences, + (*interopContext).txGetType, + (*interopContext).txGetUnspentCoins, + (*interopContext).txGetWitnesses, + (*interopContext).witnessGetVerificationScript, + } + for _, f := range funcs { + for k, v := range vals { + fname := runtime.FuncForPC(reflect.ValueOf(f).Pointer()).Name() + t.Run(k+"/"+fname, func(t *testing.T) { + testNonInterop(t, v, f) + }) + } + } +}