Merge pull request #570 from nspcc-dev/scripty-interops
Scripty interops, fixes #421.
This commit is contained in:
commit
629c6a7333
4 changed files with 150 additions and 5 deletions
|
@ -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()
|
||||
|
|
|
@ -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{
|
||||
|
|
|
@ -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
88
pkg/core/interops_test.go
Normal 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)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue