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
|
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.
|
// bcGetValidators returns validators.
|
||||||
func (ic *interopContext) bcGetValidators(v *vm.VM) error {
|
func (ic *interopContext) bcGetValidators(v *vm.VM) error {
|
||||||
validators := ic.dao.GetValidators()
|
validators := ic.dao.GetValidators()
|
||||||
|
|
|
@ -13,6 +13,7 @@ import (
|
||||||
"github.com/CityOfZion/neo-go/pkg/smartcontract/trigger"
|
"github.com/CityOfZion/neo-go/pkg/smartcontract/trigger"
|
||||||
"github.com/CityOfZion/neo-go/pkg/util"
|
"github.com/CityOfZion/neo-go/pkg/util"
|
||||||
"github.com/CityOfZion/neo-go/pkg/vm"
|
"github.com/CityOfZion/neo-go/pkg/vm"
|
||||||
|
"github.com/CityOfZion/neo-go/pkg/vm/opcode"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -115,6 +116,29 @@ func TestTxGetType(t *testing.T) {
|
||||||
require.Equal(t, big.NewInt(int64(tx.Type)), value)
|
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) {
|
func TestPopInputFromVM(t *testing.T) {
|
||||||
v, tx, _ := createVMAndTX(t)
|
v, tx, _ := createVMAndTX(t)
|
||||||
v.Estack().PushVal(vm.NewInteropItem(&tx.Inputs[0]))
|
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) {
|
func createVMAndTX(t *testing.T) (*vm.VM, *transaction.Transaction, *interopContext) {
|
||||||
v := vm.New()
|
v := vm.New()
|
||||||
tx := newMinerTX()
|
script := []byte{byte(opcode.PUSH1), byte(opcode.RET)}
|
||||||
|
tx := transaction.NewInvocationTX(script, 0)
|
||||||
|
|
||||||
bytes := make([]byte, 1)
|
bytes := make([]byte, 1)
|
||||||
attributes := append(tx.Attributes, transaction.Attribute{
|
attributes := append(tx.Attributes, transaction.Attribute{
|
||||||
|
|
|
@ -152,14 +152,17 @@ var neoInterops = []interopedFunction{
|
||||||
{Name: "Neo.Header.GetVersion", Func: (*interopContext).headerGetVersion, Price: 1},
|
{Name: "Neo.Header.GetVersion", Func: (*interopContext).headerGetVersion, Price: 1},
|
||||||
{Name: "Neo.Input.GetHash", Func: (*interopContext).inputGetHash, Price: 1},
|
{Name: "Neo.Input.GetHash", Func: (*interopContext).inputGetHash, Price: 1},
|
||||||
{Name: "Neo.Input.GetIndex", Func: (*interopContext).inputGetIndex, 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.GetAssetId", Func: (*interopContext).outputGetAssetID, Price: 1},
|
||||||
{Name: "Neo.Output.GetScriptHash", Func: (*interopContext).outputGetScriptHash, Price: 1},
|
{Name: "Neo.Output.GetScriptHash", Func: (*interopContext).outputGetScriptHash, Price: 1},
|
||||||
{Name: "Neo.Output.GetValue", Func: (*interopContext).outputGetValue, Price: 1},
|
{Name: "Neo.Output.GetValue", Func: (*interopContext).outputGetValue, Price: 1},
|
||||||
{Name: "Neo.Runtime.CheckWitness", Func: (*interopContext).runtimeCheckWitness, Price: 200},
|
{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.GetTime", Func: (*interopContext).runtimeGetTime, Price: 1},
|
||||||
{Name: "Neo.Runtime.GetTrigger", Func: (*interopContext).runtimeGetTrigger, Price: 1},
|
{Name: "Neo.Runtime.GetTrigger", Func: (*interopContext).runtimeGetTrigger, Price: 1},
|
||||||
{Name: "Neo.Runtime.Log", Func: (*interopContext).runtimeLog, Price: 1},
|
{Name: "Neo.Runtime.Log", Func: (*interopContext).runtimeLog, Price: 1},
|
||||||
{Name: "Neo.Runtime.Notify", Func: (*interopContext).runtimeNotify, 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.Delete", Func: (*interopContext).storageDelete, Price: 100},
|
||||||
{Name: "Neo.Storage.Get", Func: (*interopContext).storageGet, Price: 100},
|
{Name: "Neo.Storage.Get", Func: (*interopContext).storageGet, Price: 100},
|
||||||
{Name: "Neo.Storage.GetContext", Func: (*interopContext).storageGetContext, Price: 1},
|
{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.GetType", Func: (*interopContext).txGetType, Price: 1},
|
||||||
{Name: "Neo.Transaction.GetUnspentCoins", Func: (*interopContext).txGetUnspentCoins, Price: 200},
|
{Name: "Neo.Transaction.GetUnspentCoins", Func: (*interopContext).txGetUnspentCoins, Price: 200},
|
||||||
{Name: "Neo.Transaction.GetWitnesses", Func: (*interopContext).txGetWitnesses, 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.Concat", Func: (*interopContext).enumeratorConcat, Price: 1},
|
||||||
// {Name: "Neo.Enumerator.Create", Func: (*interopContext).enumeratorCreate, Price: 1},
|
// {Name: "Neo.Enumerator.Create", Func: (*interopContext).enumeratorCreate, Price: 1},
|
||||||
// {Name: "Neo.Enumerator.Next", Func: (*interopContext).enumeratorNext, Price: 1},
|
// {Name: "Neo.Enumerator.Next", Func: (*interopContext).enumeratorNext, Price: 1},
|
||||||
// {Name: "Neo.Enumerator.Value", Func: (*interopContext).enumeratorValue, 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.Concat", Func: (*interopContext).iteratorConcat, Price: 1},
|
||||||
// {Name: "Neo.Iterator.Create", Func: (*interopContext).iteratorCreate, Price: 1},
|
// {Name: "Neo.Iterator.Create", Func: (*interopContext).iteratorCreate, Price: 1},
|
||||||
// {Name: "Neo.Iterator.Key", Func: (*interopContext).iteratorKey, Price: 1},
|
// {Name: "Neo.Iterator.Key", Func: (*interopContext).iteratorKey, Price: 1},
|
||||||
// {Name: "Neo.Iterator.Keys", Func: (*interopContext).iteratorKeys, Price: 1},
|
// {Name: "Neo.Iterator.Keys", Func: (*interopContext).iteratorKeys, Price: 1},
|
||||||
// {Name: "Neo.Iterator.Values", Func: (*interopContext).iteratorValues, 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.Storage.Find", Func: (*interopContext).storageFind, Price: 1},
|
||||||
// {Name: "Neo.Witness.GetVerificationScript", Func: (*interopContext).witnessGetVerificationScript, Price: 100},
|
|
||||||
|
|
||||||
// Aliases.
|
// Aliases.
|
||||||
// {Name: "Neo.Iterator.Next", Func: (*interopContext).enumeratorNext, Price: 1},
|
// {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