Merge pull request #570 from nspcc-dev/scripty-interops

Scripty interops, fixes #421.
This commit is contained in:
Roman Khimov 2019-12-23 16:28:12 +03:00 committed by GitHub
commit 629c6a7333
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 150 additions and 5 deletions

View file

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

View file

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

View file

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

88
pkg/core/interops_test.go Normal file
View file

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