neoneo-go/pkg/core/native/invocation_test.go
Roman Khimov 5d478b5514 native: add old management deploy/update call flags to Aspidochelone
d5a9af5860 is incompatible with the NeoFS
mainnet sidechain, so we add the old logic to the pre-Aspidochelone
behaviour. Changing flags at newMethodAndPrice() is a bit less convenient
unfortunately because this will affect interop validity checks, so let's have
this kludge here.
2022-12-16 23:45:47 +03:00

213 lines
7.5 KiB
Go

package native_test
import (
"encoding/json"
"fmt"
"path/filepath"
"strings"
"testing"
"github.com/nspcc-dev/neo-go/internal/contracts"
"github.com/nspcc-dev/neo-go/pkg/config"
"github.com/nspcc-dev/neo-go/pkg/core/fee"
"github.com/nspcc-dev/neo-go/pkg/core/native/nativenames"
"github.com/nspcc-dev/neo-go/pkg/core/state"
"github.com/nspcc-dev/neo-go/pkg/crypto/hash"
"github.com/nspcc-dev/neo-go/pkg/neotest"
"github.com/nspcc-dev/neo-go/pkg/neotest/chain"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger"
"github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
"github.com/stretchr/testify/require"
)
var pathToInternalContracts = filepath.Join("..", "..", "..", "internal", "contracts")
func TestNativeContract_Invoke(t *testing.T) {
const (
transferCPUFee = 1 << 17
transferStorageFee = 50
systemContractCallPrice = 1 << 15
)
bc, validator, committee := chain.NewMulti(t)
e := neotest.NewExecutor(t, bc, validator, committee)
gasHash := e.NativeHash(t, nativenames.Gas)
baseExecFee := bc.GetBaseExecFee()
price := fee.Opcode(baseExecFee, opcode.SYSCALL, // System.Contract.Call
opcode.PUSHDATA1, // contract hash (20 byte)
opcode.PUSHDATA1, // method
opcode.PUSH15, // call flags
// `transfer` args:
opcode.PUSHDATA1, // from
opcode.PUSHDATA1, // to
opcode.PUSH1, // amount
opcode.PUSHNULL, // data
// end args
opcode.PUSH4, // amount of args
opcode.PACK, // pack args
)
price += systemContractCallPrice*baseExecFee + // System.Contract.Call price
transferCPUFee*baseExecFee + // `transfer` itself
transferStorageFee*bc.GetStoragePrice() // `transfer` storage price
tx := e.NewUnsignedTx(t, gasHash, "transfer", validator.ScriptHash(), validator.ScriptHash(), 1, nil)
e.SignTx(t, tx, -1, validator)
e.AddNewBlock(t, tx)
e.CheckHalt(t, tx.Hash(), stackitem.Make(true))
// Enough for Call and other opcodes, but not enough for "transfer" call.
tx = e.NewUnsignedTx(t, gasHash, "transfer", validator.ScriptHash(), validator.ScriptHash(), 1, nil)
e.SignTx(t, tx, price-1, validator)
e.AddNewBlock(t, tx)
e.CheckFault(t, tx.Hash(), "gas limit exceeded")
}
func TestNativeContract_InvokeInternal(t *testing.T) {
bc, validator, committee := chain.NewMulti(t)
e := neotest.NewExecutor(t, bc, validator, committee)
clState := bc.GetContractState(e.NativeHash(t, nativenames.CryptoLib))
require.NotNil(t, clState)
md := clState.Manifest.ABI.GetMethod("ripemd160", 1)
require.NotNil(t, md)
t.Run("fail, bad current script hash", func(t *testing.T) {
ic, err := bc.GetTestVM(trigger.Application, nil, nil)
require.NoError(t, err)
v := ic.SpawnVM()
fakeH := util.Uint160{1, 2, 3}
v.LoadScriptWithHash(clState.NEF.Script, fakeH, callflag.All)
input := []byte{1, 2, 3, 4}
v.Estack().PushVal(input)
v.Context().Jump(md.Offset)
// Bad current script hash
err = v.Run()
require.Error(t, err)
require.True(t, strings.Contains(err.Error(), fmt.Sprintf("native contract %s (version 0) not found", fakeH.StringLE())), err.Error())
})
t.Run("fail, bad NativeUpdateHistory height", func(t *testing.T) {
bcBad, validatorBad, committeeBad := chain.NewMultiWithCustomConfig(t, func(c *config.Blockchain) {
c.NativeUpdateHistories = map[string][]uint32{
nativenames.Policy: {0},
nativenames.Neo: {0},
nativenames.Gas: {0},
nativenames.Designation: {0},
nativenames.StdLib: {0},
nativenames.Management: {0},
nativenames.Oracle: {0},
nativenames.Ledger: {0},
nativenames.CryptoLib: {1},
}
})
eBad := neotest.NewExecutor(t, bcBad, validatorBad, committeeBad)
ic, err := bcBad.GetTestVM(trigger.Application, nil, nil)
require.NoError(t, err)
v := ic.SpawnVM()
v.LoadScriptWithHash(clState.NEF.Script, clState.Hash, callflag.All) // hash is not affected by native update history
input := []byte{1, 2, 3, 4}
v.Estack().PushVal(input)
v.Context().Jump(md.Offset)
// It's prohibited to call natives before NativeUpdateHistory[0] height.
err = v.Run()
require.Error(t, err)
require.True(t, strings.Contains(err.Error(), "native contract CryptoLib is active after height = 1"))
// Add new block => CryptoLib should be active now.
eBad.AddNewBlock(t)
ic, err = bcBad.GetTestVM(trigger.Application, nil, nil)
require.NoError(t, err)
v = ic.SpawnVM()
v.LoadScriptWithHash(clState.NEF.Script, clState.Hash, callflag.All) // hash is not affected by native update history
v.Estack().PushVal(input)
v.Context().Jump(md.Offset)
require.NoError(t, v.Run())
value := v.Estack().Pop().Bytes()
require.Equal(t, hash.RipeMD160(input).BytesBE(), value)
})
manState := bc.GetContractState(e.NativeHash(t, nativenames.Management))
require.NotNil(t, manState)
mdDeploy := manState.Manifest.ABI.GetMethod("deploy", 2)
require.NotNil(t, mdDeploy)
t.Run("fail, bad call flag", func(t *testing.T) {
ic, err := bc.GetTestVM(trigger.Application, nil, nil)
require.NoError(t, err)
v := ic.SpawnVM()
v.LoadScriptWithHash(manState.NEF.Script, manState.Hash, callflag.States|callflag.AllowNotify)
input := []byte{1, 2, 3, 4}
v.Estack().PushVal(input)
v.Estack().PushVal(input)
v.Context().Jump(mdDeploy.Offset)
// Can't call with these flags, Aspidochelone is active.
err = v.Run()
require.Error(t, err)
require.True(t, strings.Contains(err.Error(), "missing call flags for native 0 `deploy` operation call"))
})
t.Run("good, pre-aspidochelone deploy", func(t *testing.T) {
bc, _, _ := chain.NewMultiWithCustomConfig(t, func(c *config.Blockchain) {
c.Hardforks = map[string]uint32{
config.HFAspidochelone.String(): 100500,
}
})
ic, err := bc.GetTestVM(trigger.Application, nil, nil)
require.NoError(t, err)
v := ic.SpawnVM()
v.LoadScriptWithHash(manState.NEF.Script, manState.Hash, callflag.States|callflag.AllowNotify)
input := []byte{1, 2, 3, 4}
v.Estack().PushVal(input)
v.Estack().PushVal(input)
v.Context().Jump(mdDeploy.Offset)
// We have an invalid input, but call flags are OK.
err = v.Run()
require.Error(t, err)
require.True(t, strings.Contains(err.Error(), "invalid NEF file"))
})
t.Run("success", func(t *testing.T) {
ic, err := bc.GetTestVM(trigger.Application, nil, nil)
require.NoError(t, err)
v := ic.SpawnVM()
v.LoadScriptWithHash(clState.NEF.Script, clState.Hash, callflag.All)
input := []byte{1, 2, 3, 4}
v.Estack().PushVal(input)
v.Context().Jump(md.Offset)
require.NoError(t, v.Run())
value := v.Estack().Pop().Bytes()
require.Equal(t, hash.RipeMD160(input).BytesBE(), value)
})
}
func TestNativeContract_InvokeOtherContract(t *testing.T) {
bc, validator, committee := chain.NewMulti(t)
e := neotest.NewExecutor(t, bc, validator, committee)
managementInvoker := e.ValidatorInvoker(e.NativeHash(t, nativenames.Management))
gasInvoker := e.ValidatorInvoker(e.NativeHash(t, nativenames.Gas))
cs, _ := contracts.GetTestContractState(t, pathToInternalContracts, 1, 2, validator.ScriptHash())
cs.Hash = state.CreateContractHash(validator.ScriptHash(), cs.NEF.Checksum, cs.Manifest.Name) // set proper hash
manifB, err := json.Marshal(cs.Manifest)
require.NoError(t, err)
nefB, err := cs.NEF.Bytes()
require.NoError(t, err)
si, err := cs.ToStackItem()
require.NoError(t, err)
managementInvoker.Invoke(t, si, "deploy", nefB, manifB)
t.Run("non-native, no return", func(t *testing.T) {
// `onNEP17Payment` will be invoked on test contract from GAS contract.
gasInvoker.Invoke(t, true, "transfer", validator.ScriptHash(), cs.Hash, 1, nil)
})
}