forked from TrueCloudLab/neoneo-go
6fa4bcdc1d
This check is good and was present here since #1729, but it was accidently removed from the reference implementation (see the discussion in https://github.com/neo-project/neo/issues/2848). The removal of this check from the C# node leaded to the T5 testnet state diff since 1670095 heigh which causes inability to process new blocks since 2272533 height (see #3049). This check was added back to the C# node in https://github.com/neo-project/neo/pull/2849, but it is planned to be the part of the upcoming 3.6.0 C# node release. We need to keep our testnet healthy, thus, strict contract script check will be temporary removed from the node code and is planned to be added back to be a part of the next 3.6.0-compatible release. Close #3049. Signed-off-by: Anna Shaleva <shaleva.ann@nspcc.ru>
619 lines
25 KiB
Go
619 lines
25 KiB
Go
package native_test
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"fmt"
|
|
"testing"
|
|
|
|
"github.com/nspcc-dev/neo-go/internal/contracts"
|
|
"github.com/nspcc-dev/neo-go/pkg/core/chaindump"
|
|
"github.com/nspcc-dev/neo-go/pkg/core/interop/interopnames"
|
|
"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/core/storage"
|
|
"github.com/nspcc-dev/neo-go/pkg/core/storage/dbconfig"
|
|
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
|
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
|
"github.com/nspcc-dev/neo-go/pkg/io"
|
|
"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"
|
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
|
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
|
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract/nef"
|
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger"
|
|
"github.com/nspcc-dev/neo-go/pkg/vm/emit"
|
|
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
|
|
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
|
"github.com/nspcc-dev/neo-go/pkg/vm/vmstate"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func newManagementClient(t *testing.T) *neotest.ContractInvoker {
|
|
return newNativeClient(t, nativenames.Management)
|
|
}
|
|
|
|
func TestManagement_MinimumDeploymentFee(t *testing.T) {
|
|
testGetSet(t, newManagementClient(t), "MinimumDeploymentFee", 10_00000000, 0, 0)
|
|
}
|
|
|
|
func TestManagement_MinimumDeploymentFeeCache(t *testing.T) {
|
|
c := newManagementClient(t)
|
|
testGetSetCache(t, c, "MinimumDeploymentFee", 10_00000000)
|
|
}
|
|
|
|
func TestManagement_ContractCache(t *testing.T) {
|
|
c := newManagementClient(t)
|
|
managementInvoker := c.WithSigners(c.Committee)
|
|
|
|
cs1, _ := contracts.GetTestContractState(t, pathToInternalContracts, 1, 2, c.Committee.ScriptHash())
|
|
manifestBytes, err := json.Marshal(cs1.Manifest)
|
|
require.NoError(t, err)
|
|
nefBytes, err := cs1.NEF.Bytes()
|
|
require.NoError(t, err)
|
|
|
|
// Deploy contract, abort the transaction and check that Management cache wasn't persisted
|
|
// for FAULTed tx at the same block.
|
|
w := io.NewBufBinWriter()
|
|
emit.AppCall(w.BinWriter, managementInvoker.Hash, "deploy", callflag.All, nefBytes, manifestBytes)
|
|
emit.Opcodes(w.BinWriter, opcode.ABORT)
|
|
tx1 := managementInvoker.PrepareInvocation(t, w.Bytes(), managementInvoker.Signers)
|
|
tx2 := managementInvoker.PrepareInvoke(t, "getContract", cs1.Hash.BytesBE())
|
|
managementInvoker.AddNewBlock(t, tx1, tx2)
|
|
managementInvoker.CheckFault(t, tx1.Hash(), "ABORT")
|
|
managementInvoker.CheckHalt(t, tx2.Hash(), stackitem.Null{})
|
|
|
|
// Deploy the contract and check that cache was persisted for HALTed transaction at the same block.
|
|
tx1 = managementInvoker.PrepareInvoke(t, "deploy", nefBytes, manifestBytes)
|
|
tx2 = managementInvoker.PrepareInvoke(t, "getContract", cs1.Hash.BytesBE())
|
|
managementInvoker.AddNewBlock(t, tx1, tx2)
|
|
managementInvoker.CheckHalt(t, tx1.Hash())
|
|
aer, err := managementInvoker.Chain.GetAppExecResults(tx2.Hash(), trigger.Application)
|
|
require.NoError(t, err)
|
|
require.Equal(t, vmstate.Halt, aer[0].VMState, aer[0].FaultException)
|
|
require.NotEqual(t, stackitem.Null{}, aer[0].Stack)
|
|
}
|
|
|
|
func TestManagement_ContractDeploy(t *testing.T) {
|
|
c := newManagementClient(t)
|
|
managementInvoker := c.WithSigners(c.Committee)
|
|
|
|
cs1, _ := contracts.GetTestContractState(t, pathToInternalContracts, 1, 2, c.Committee.ScriptHash())
|
|
manifestBytes, err := json.Marshal(cs1.Manifest)
|
|
require.NoError(t, err)
|
|
nefBytes, err := cs1.NEF.Bytes()
|
|
require.NoError(t, err)
|
|
|
|
t.Run("no NEF", func(t *testing.T) {
|
|
managementInvoker.InvokeFail(t, "no valid NEF provided", "deploy", nil, manifestBytes)
|
|
})
|
|
t.Run("no manifest", func(t *testing.T) {
|
|
managementInvoker.InvokeFail(t, "no valid manifest provided", "deploy", nefBytes, nil)
|
|
})
|
|
t.Run("int for NEF", func(t *testing.T) {
|
|
managementInvoker.InvokeFail(t, "invalid NEF file", "deploy", int64(1), manifestBytes)
|
|
})
|
|
t.Run("zero-length NEF", func(t *testing.T) {
|
|
managementInvoker.InvokeFail(t, "invalid NEF file", "deploy", []byte{}, manifestBytes)
|
|
})
|
|
t.Run("array for NEF", func(t *testing.T) {
|
|
managementInvoker.InvokeFail(t, "invalid NEF file", "deploy", []any{int64(1)}, manifestBytes)
|
|
})
|
|
t.Run("bad script in NEF", func(t *testing.T) {
|
|
nf, err := nef.FileFromBytes(nefBytes) // make a full copy
|
|
require.NoError(t, err)
|
|
nf.Script[0] = 0xff
|
|
nf.CalculateChecksum()
|
|
nefBad, err := nf.Bytes()
|
|
require.NoError(t, err)
|
|
managementInvoker.InvokeFail(t, "invalid NEF file", "deploy", nefBad, manifestBytes)
|
|
})
|
|
t.Run("int for manifest", func(t *testing.T) {
|
|
managementInvoker.InvokeFail(t, "invalid manifest", "deploy", nefBytes, int64(1))
|
|
})
|
|
t.Run("zero-length manifest", func(t *testing.T) {
|
|
managementInvoker.InvokeFail(t, "invalid manifest", "deploy", nefBytes, []byte{})
|
|
})
|
|
t.Run("array for manifest", func(t *testing.T) {
|
|
managementInvoker.InvokeFail(t, "invalid manifest", "deploy", nefBytes, []any{int64(1)})
|
|
})
|
|
t.Run("non-utf8 manifest", func(t *testing.T) {
|
|
manifestBad := bytes.Replace(manifestBytes, []byte("TestMain"), []byte("\xff\xfe\xfd"), 1) // Replace name.
|
|
managementInvoker.InvokeFail(t, "manifest is not UTF-8 compliant", "deploy", nefBytes, manifestBad)
|
|
})
|
|
t.Run("invalid manifest", func(t *testing.T) {
|
|
pkey, err := keys.NewPrivateKey()
|
|
require.NoError(t, err)
|
|
|
|
badManifest := cs1.Manifest
|
|
badManifest.Groups = []manifest.Group{{PublicKey: pkey.PublicKey(), Signature: make([]byte, keys.SignatureLen)}}
|
|
manifB, err := json.Marshal(&badManifest)
|
|
require.NoError(t, err)
|
|
|
|
managementInvoker.InvokeFail(t, "invalid manifest", "deploy", nefBytes, manifB)
|
|
})
|
|
t.Run("bad methods in manifest 1", func(t *testing.T) {
|
|
badManifest := cs1.Manifest
|
|
badManifest.ABI.Methods = make([]manifest.Method, len(cs1.Manifest.ABI.Methods))
|
|
copy(badManifest.ABI.Methods, cs1.Manifest.ABI.Methods)
|
|
badManifest.ABI.Methods[0].Offset = 100500 // out of bounds
|
|
manifB, err := json.Marshal(&badManifest)
|
|
require.NoError(t, err)
|
|
|
|
managementInvoker.InvokeFail(t, "method add/2: offset is out of the script range", "deploy", nefBytes, manifB)
|
|
})
|
|
t.Run("duplicated methods in manifest 1", func(t *testing.T) {
|
|
badManifest := cs1.Manifest
|
|
badManifest.ABI.Methods = make([]manifest.Method, len(cs1.Manifest.ABI.Methods))
|
|
copy(badManifest.ABI.Methods, cs1.Manifest.ABI.Methods)
|
|
badManifest.ABI.Methods[0] = badManifest.ABI.Methods[1] // duplicates
|
|
manifB, err := json.Marshal(&badManifest)
|
|
require.NoError(t, err)
|
|
|
|
managementInvoker.InvokeFail(t, "duplicate method specifications", "deploy", nefBytes, manifB)
|
|
})
|
|
t.Run("duplicated events in manifest 1", func(t *testing.T) {
|
|
badManifest := cs1.Manifest
|
|
badManifest.ABI.Methods = make([]manifest.Method, len(cs1.Manifest.ABI.Methods))
|
|
copy(badManifest.ABI.Methods, cs1.Manifest.ABI.Methods)
|
|
badManifest.ABI.Events = []manifest.Event{{Name: "event"}, {Name: "event"}} // duplicates
|
|
manifB, err := json.Marshal(&badManifest)
|
|
require.NoError(t, err)
|
|
|
|
managementInvoker.InvokeFail(t, "duplicate event names", "deploy", nefBytes, manifB)
|
|
})
|
|
|
|
t.Run("not enough GAS", func(t *testing.T) {
|
|
tx := managementInvoker.NewUnsignedTx(t, managementInvoker.Hash, "deploy", nefBytes, manifestBytes)
|
|
managementInvoker.SignTx(t, tx, 1_0000_0000, managementInvoker.Signers...)
|
|
managementInvoker.AddNewBlock(t, tx)
|
|
managementInvoker.CheckFault(t, tx.Hash(), "gas limit exceeded")
|
|
})
|
|
|
|
si, err := cs1.ToStackItem()
|
|
require.NoError(t, err)
|
|
|
|
t.Run("positive", func(t *testing.T) {
|
|
tx1 := managementInvoker.PrepareInvoke(t, "deploy", nefBytes, manifestBytes)
|
|
tx2 := managementInvoker.PrepareInvoke(t, "getContract", cs1.Hash.BytesBE())
|
|
managementInvoker.AddNewBlock(t, tx1, tx2)
|
|
managementInvoker.CheckHalt(t, tx1.Hash(), si)
|
|
managementInvoker.CheckHalt(t, tx2.Hash(), si)
|
|
managementInvoker.CheckTxNotificationEvent(t, tx1.Hash(), 0, state.NotificationEvent{
|
|
ScriptHash: c.NativeHash(t, nativenames.Management),
|
|
Name: "Deploy",
|
|
Item: stackitem.NewArray([]stackitem.Item{stackitem.NewByteArray(cs1.Hash.BytesBE())}),
|
|
})
|
|
t.Run("_deploy called", func(t *testing.T) {
|
|
helperInvoker := c.Executor.CommitteeInvoker(cs1.Hash)
|
|
expected := stackitem.NewArray([]stackitem.Item{stackitem.Make("create"), stackitem.Null{}})
|
|
expectedBytes, err := stackitem.Serialize(expected)
|
|
require.NoError(t, err)
|
|
helperInvoker.Invoke(t, stackitem.NewByteArray(expectedBytes), "getValue")
|
|
})
|
|
t.Run("get after deploy", func(t *testing.T) {
|
|
managementInvoker.Invoke(t, si, "getContract", cs1.Hash.BytesBE())
|
|
})
|
|
t.Run("hasMethod after deploy", func(t *testing.T) {
|
|
managementInvoker.Invoke(t, stackitem.NewBool(true), "hasMethod", cs1.Hash.BytesBE(), "add", 2)
|
|
managementInvoker.Invoke(t, stackitem.NewBool(false), "hasMethod", cs1.Hash.BytesBE(), "add", 1)
|
|
managementInvoker.Invoke(t, stackitem.NewBool(false), "hasMethod", cs1.Hash.BytesLE(), "add", 2)
|
|
})
|
|
t.Run("get after restore", func(t *testing.T) {
|
|
w := io.NewBufBinWriter()
|
|
require.NoError(t, chaindump.Dump(c.Executor.Chain, w.BinWriter, 0, c.Executor.Chain.BlockHeight()+1))
|
|
require.NoError(t, w.Err)
|
|
|
|
r := io.NewBinReaderFromBuf(w.Bytes())
|
|
bc2, acc := chain.NewSingle(t)
|
|
e2 := neotest.NewExecutor(t, bc2, acc, acc)
|
|
managementInvoker2 := e2.CommitteeInvoker(e2.NativeHash(t, nativenames.Management))
|
|
|
|
require.NoError(t, chaindump.Restore(bc2, r, 0, c.Executor.Chain.BlockHeight()+1, nil))
|
|
require.NoError(t, r.Err)
|
|
managementInvoker2.Invoke(t, si, "getContract", cs1.Hash.BytesBE())
|
|
})
|
|
})
|
|
t.Run("contract already exists", func(t *testing.T) {
|
|
managementInvoker.InvokeFail(t, "contract already exists", "deploy", nefBytes, manifestBytes)
|
|
})
|
|
t.Run("failed _deploy", func(t *testing.T) {
|
|
deployScript := []byte{byte(opcode.ABORT)}
|
|
m := manifest.NewManifest("TestDeployAbort")
|
|
m.ABI.Methods = []manifest.Method{
|
|
{
|
|
Name: manifest.MethodDeploy,
|
|
Offset: 0,
|
|
Parameters: []manifest.Parameter{
|
|
manifest.NewParameter("data", smartcontract.AnyType),
|
|
manifest.NewParameter("isUpdate", smartcontract.BoolType),
|
|
},
|
|
ReturnType: smartcontract.VoidType,
|
|
},
|
|
}
|
|
nefD, err := nef.NewFile(deployScript)
|
|
require.NoError(t, err)
|
|
nefDb, err := nefD.Bytes()
|
|
require.NoError(t, err)
|
|
manifD, err := json.Marshal(m)
|
|
require.NoError(t, err)
|
|
managementInvoker.InvokeFail(t, "ABORT", "deploy", nefDb, manifD)
|
|
|
|
t.Run("get after failed deploy", func(t *testing.T) {
|
|
h := state.CreateContractHash(c.CommitteeHash, nefD.Checksum, m.Name)
|
|
managementInvoker.Invoke(t, stackitem.Null{}, "getContract", h.BytesBE())
|
|
})
|
|
})
|
|
t.Run("bad _deploy", func(t *testing.T) { // invalid _deploy signature
|
|
deployScript := []byte{byte(opcode.RET)}
|
|
m := manifest.NewManifest("TestBadDeploy")
|
|
m.ABI.Methods = []manifest.Method{
|
|
{
|
|
Name: manifest.MethodDeploy,
|
|
Offset: 0,
|
|
Parameters: []manifest.Parameter{
|
|
manifest.NewParameter("data", smartcontract.AnyType),
|
|
manifest.NewParameter("isUpdate", smartcontract.BoolType),
|
|
},
|
|
ReturnType: smartcontract.ArrayType,
|
|
},
|
|
}
|
|
nefD, err := nef.NewFile(deployScript)
|
|
require.NoError(t, err)
|
|
nefDb, err := nefD.Bytes()
|
|
require.NoError(t, err)
|
|
manifD, err := json.Marshal(m)
|
|
require.NoError(t, err)
|
|
managementInvoker.InvokeFail(t, "invalid return values count: expected 0, got 2", "deploy", nefDb, manifD)
|
|
|
|
t.Run("get after bad _deploy", func(t *testing.T) {
|
|
h := state.CreateContractHash(c.CommitteeHash, nefD.Checksum, m.Name)
|
|
managementInvoker.Invoke(t, stackitem.Null{}, "getContract", h.BytesBE())
|
|
})
|
|
})
|
|
}
|
|
|
|
func TestManagement_StartFromHeight(t *testing.T) {
|
|
// Create database to be able to start another chain from the same height later.
|
|
ldbDir := t.TempDir()
|
|
dbConfig := dbconfig.DBConfiguration{
|
|
Type: dbconfig.LevelDB,
|
|
LevelDBOptions: dbconfig.LevelDBOptions{
|
|
DataDirectoryPath: ldbDir,
|
|
},
|
|
}
|
|
newLevelStore, err := storage.NewLevelDBStore(dbConfig.LevelDBOptions)
|
|
require.Nil(t, err, "NewLevelDBStore error")
|
|
|
|
// Create blockchain and put contract state to it.
|
|
bc, acc := chain.NewSingleWithCustomConfigAndStore(t, nil, newLevelStore, false)
|
|
go bc.Run()
|
|
e := neotest.NewExecutor(t, bc, acc, acc)
|
|
c := e.CommitteeInvoker(e.NativeHash(t, nativenames.Management))
|
|
managementInvoker := c.WithSigners(c.Committee)
|
|
|
|
cs1, _ := contracts.GetTestContractState(t, pathToInternalContracts, 1, 2, c.CommitteeHash)
|
|
manifestBytes, err := json.Marshal(cs1.Manifest)
|
|
require.NoError(t, err)
|
|
nefBytes, err := cs1.NEF.Bytes()
|
|
require.NoError(t, err)
|
|
|
|
si, err := cs1.ToStackItem()
|
|
require.NoError(t, err)
|
|
|
|
managementInvoker.Invoke(t, si, "deploy", nefBytes, manifestBytes)
|
|
managementInvoker.Invoke(t, si, "getContract", cs1.Hash.BytesBE())
|
|
|
|
// Close current blockchain and start the new one from the same height with the same db.
|
|
bc.Close()
|
|
newLevelStore, err = storage.NewLevelDBStore(dbConfig.LevelDBOptions)
|
|
require.NoError(t, err)
|
|
bc2, acc := chain.NewSingleWithCustomConfigAndStore(t, nil, newLevelStore, true)
|
|
e2 := neotest.NewExecutor(t, bc2, acc, acc)
|
|
managementInvoker2 := e2.CommitteeInvoker(e2.NativeHash(t, nativenames.Management))
|
|
|
|
// Check that initialisation of native Management was correctly performed.
|
|
managementInvoker2.Invoke(t, si, "getContract", cs1.Hash.BytesBE())
|
|
}
|
|
|
|
func TestManagement_DeployManifestOverflow(t *testing.T) {
|
|
c := newManagementClient(t)
|
|
managementInvoker := c.WithSigners(c.Committee)
|
|
|
|
cs1, _ := contracts.GetTestContractState(t, pathToInternalContracts, 1, 2, c.CommitteeHash)
|
|
manif1, err := json.Marshal(cs1.Manifest)
|
|
require.NoError(t, err)
|
|
nef1, err := nef.NewFile(cs1.NEF.Script)
|
|
require.NoError(t, err)
|
|
nef1b, err := nef1.Bytes()
|
|
require.NoError(t, err)
|
|
|
|
w := io.NewBufBinWriter()
|
|
emit.Bytes(w.BinWriter, manif1)
|
|
emit.Int(w.BinWriter, manifest.MaxManifestSize)
|
|
emit.Opcodes(w.BinWriter, opcode.NEWBUFFER, opcode.CAT)
|
|
emit.Bytes(w.BinWriter, nef1b)
|
|
emit.Int(w.BinWriter, 2)
|
|
emit.Opcodes(w.BinWriter, opcode.PACK)
|
|
emit.AppCallNoArgs(w.BinWriter, managementInvoker.Hash, "deploy", callflag.All)
|
|
require.NoError(t, w.Err)
|
|
script := w.Bytes()
|
|
|
|
tx := transaction.New(script, 0)
|
|
tx.ValidUntilBlock = managementInvoker.Chain.BlockHeight() + 1
|
|
managementInvoker.SignTx(t, tx, 100_0000_0000, managementInvoker.Signers...)
|
|
managementInvoker.AddNewBlock(t, tx)
|
|
managementInvoker.CheckFault(t, tx.Hash(), fmt.Sprintf("invalid manifest: len is %d (max %d)", manifest.MaxManifestSize+len(manif1), manifest.MaxManifestSize))
|
|
}
|
|
|
|
func TestManagement_ContractDeployAndUpdateWithParameter(t *testing.T) {
|
|
c := newManagementClient(t)
|
|
managementInvoker := c.WithSigners(c.Committee)
|
|
|
|
cs1, _ := contracts.GetTestContractState(t, pathToInternalContracts, 1, 2, c.CommitteeHash)
|
|
cs1.Manifest.Permissions = []manifest.Permission{*manifest.NewPermission(manifest.PermissionWildcard)}
|
|
cs1.ID = 1
|
|
cs1.Hash = state.CreateContractHash(c.CommitteeHash, cs1.NEF.Checksum, cs1.Manifest.Name)
|
|
manif1, err := json.Marshal(cs1.Manifest)
|
|
require.NoError(t, err)
|
|
nef1b, err := cs1.NEF.Bytes()
|
|
require.NoError(t, err)
|
|
|
|
si, err := cs1.ToStackItem()
|
|
require.NoError(t, err)
|
|
managementInvoker.Invoke(t, si, "deploy", nef1b, manif1)
|
|
helperInvoker := c.Executor.CommitteeInvoker(cs1.Hash)
|
|
|
|
t.Run("_deploy called", func(t *testing.T) {
|
|
expected := stackitem.NewArray([]stackitem.Item{stackitem.Make("create"), stackitem.Null{}})
|
|
expectedBytes, err := stackitem.Serialize(expected)
|
|
require.NoError(t, err)
|
|
helperInvoker.Invoke(t, stackitem.NewByteArray(expectedBytes), "getValue")
|
|
})
|
|
|
|
cs1.NEF.Script = append(cs1.NEF.Script, byte(opcode.RET))
|
|
cs1.NEF.Checksum = cs1.NEF.CalculateChecksum()
|
|
nef1b, err = cs1.NEF.Bytes()
|
|
require.NoError(t, err)
|
|
cs1.UpdateCounter++
|
|
|
|
helperInvoker.Invoke(t, stackitem.Null{}, "update", nef1b, nil, "new data")
|
|
|
|
t.Run("_deploy called", func(t *testing.T) {
|
|
expected := stackitem.NewArray([]stackitem.Item{stackitem.Make("update"), stackitem.Make("new data")})
|
|
expectedBytes, err := stackitem.Serialize(expected)
|
|
require.NoError(t, err)
|
|
helperInvoker.Invoke(t, stackitem.NewByteArray(expectedBytes), "getValue")
|
|
})
|
|
}
|
|
|
|
func TestManagement_ContractUpdate(t *testing.T) {
|
|
c := newManagementClient(t)
|
|
managementInvoker := c.WithSigners(c.Committee)
|
|
|
|
cs1, _ := contracts.GetTestContractState(t, pathToInternalContracts, 1, 2, c.CommitteeHash)
|
|
// Allow calling management contract.
|
|
cs1.Manifest.Permissions = []manifest.Permission{*manifest.NewPermission(manifest.PermissionWildcard)}
|
|
manifestBytes, err := json.Marshal(cs1.Manifest)
|
|
require.NoError(t, err)
|
|
nefBytes, err := cs1.NEF.Bytes()
|
|
require.NoError(t, err)
|
|
|
|
si, err := cs1.ToStackItem()
|
|
require.NoError(t, err)
|
|
managementInvoker.Invoke(t, si, "deploy", nefBytes, manifestBytes)
|
|
helperInvoker := c.Executor.CommitteeInvoker(cs1.Hash)
|
|
|
|
t.Run("unknown contract", func(t *testing.T) {
|
|
managementInvoker.InvokeFail(t, "contract doesn't exist", "update", nefBytes, manifestBytes)
|
|
})
|
|
t.Run("zero-length NEF", func(t *testing.T) {
|
|
helperInvoker.InvokeFail(t, "invalid NEF file: empty", "update", []byte{}, manifestBytes)
|
|
})
|
|
t.Run("zero-length manifest", func(t *testing.T) {
|
|
helperInvoker.InvokeFail(t, "invalid manifest: empty", "update", nefBytes, []byte{})
|
|
})
|
|
t.Run("no real params", func(t *testing.T) {
|
|
helperInvoker.InvokeFail(t, "both NEF and manifest are nil", "update", nil, nil)
|
|
})
|
|
t.Run("invalid manifest", func(t *testing.T) {
|
|
pkey, err := keys.NewPrivateKey()
|
|
require.NoError(t, err)
|
|
|
|
var badManifest = cs1.Manifest
|
|
badManifest.Groups = []manifest.Group{{PublicKey: pkey.PublicKey(), Signature: make([]byte, keys.SignatureLen)}}
|
|
manifB, err := json.Marshal(badManifest)
|
|
require.NoError(t, err)
|
|
|
|
helperInvoker.InvokeFail(t, "invalid manifest: incorrect group signature", "update", nefBytes, manifB)
|
|
})
|
|
t.Run("manifest and script mismatch", func(t *testing.T) {
|
|
nf, err := nef.FileFromBytes(nefBytes) // Make a full copy.
|
|
require.NoError(t, err)
|
|
nf.Script = append(nf.Script, byte(opcode.RET))
|
|
copy(nf.Script[1:], nf.Script) // Now all method offsets are wrong.
|
|
nf.Script[0] = byte(opcode.RET) // Even though the script is correct.
|
|
nf.CalculateChecksum()
|
|
nefnew, err := nf.Bytes()
|
|
require.NoError(t, err)
|
|
helperInvoker.InvokeFail(t, "invalid NEF file: checksum verification failure", "update", nefnew, manifestBytes)
|
|
})
|
|
|
|
t.Run("change name", func(t *testing.T) {
|
|
var badManifest = cs1.Manifest
|
|
badManifest.Name += "tail"
|
|
manifB, err := json.Marshal(badManifest)
|
|
require.NoError(t, err)
|
|
|
|
helperInvoker.InvokeFail(t, "contract name can't be changed", "update", nefBytes, manifB)
|
|
})
|
|
|
|
cs1.NEF.Script = append(cs1.NEF.Script, byte(opcode.RET))
|
|
cs1.NEF.Checksum = cs1.NEF.CalculateChecksum()
|
|
nefBytes, err = cs1.NEF.Bytes()
|
|
require.NoError(t, err)
|
|
cs1.UpdateCounter++
|
|
si, err = cs1.ToStackItem()
|
|
require.NoError(t, err)
|
|
|
|
t.Run("update script, positive", func(t *testing.T) {
|
|
tx1 := helperInvoker.PrepareInvoke(t, "update", nefBytes, nil)
|
|
tx2 := managementInvoker.PrepareInvoke(t, "getContract", cs1.Hash.BytesBE())
|
|
managementInvoker.AddNewBlock(t, tx1, tx2)
|
|
managementInvoker.CheckHalt(t, tx1.Hash(), stackitem.Null{})
|
|
managementInvoker.CheckHalt(t, tx2.Hash(), si)
|
|
managementInvoker.CheckTxNotificationEvent(t, tx1.Hash(), 0, state.NotificationEvent{
|
|
ScriptHash: c.NativeHash(t, nativenames.Management),
|
|
Name: "Update",
|
|
Item: stackitem.NewArray([]stackitem.Item{stackitem.NewByteArray(cs1.Hash.BytesBE())}),
|
|
})
|
|
t.Run("_deploy called", func(t *testing.T) {
|
|
helperInvoker := c.Executor.CommitteeInvoker(cs1.Hash)
|
|
expected := stackitem.NewArray([]stackitem.Item{stackitem.Make("update"), stackitem.Null{}})
|
|
expectedBytes, err := stackitem.Serialize(expected)
|
|
require.NoError(t, err)
|
|
helperInvoker.Invoke(t, stackitem.NewByteArray(expectedBytes), "getValue")
|
|
})
|
|
t.Run("check contract", func(t *testing.T) {
|
|
managementInvoker.Invoke(t, si, "getContract", cs1.Hash.BytesBE())
|
|
})
|
|
})
|
|
|
|
cs1.Manifest.Extra = []byte(`"update me"`)
|
|
manifestBytes, err = json.Marshal(cs1.Manifest)
|
|
require.NoError(t, err)
|
|
cs1.UpdateCounter++
|
|
si, err = cs1.ToStackItem()
|
|
require.NoError(t, err)
|
|
|
|
t.Run("update manifest, positive", func(t *testing.T) {
|
|
updHash := helperInvoker.Invoke(t, stackitem.Null{}, "update", nil, manifestBytes)
|
|
helperInvoker.CheckTxNotificationEvent(t, updHash, 0, state.NotificationEvent{
|
|
ScriptHash: helperInvoker.NativeHash(t, nativenames.Management),
|
|
Name: "Update",
|
|
Item: stackitem.NewArray([]stackitem.Item{stackitem.NewByteArray(cs1.Hash.BytesBE())}),
|
|
})
|
|
t.Run("check contract", func(t *testing.T) {
|
|
managementInvoker.Invoke(t, si, "getContract", cs1.Hash.BytesBE())
|
|
})
|
|
})
|
|
|
|
cs1.NEF.Script = append(cs1.NEF.Script, byte(opcode.ABORT))
|
|
cs1.NEF.Checksum = cs1.NEF.CalculateChecksum()
|
|
nefBytes, err = cs1.NEF.Bytes()
|
|
require.NoError(t, err)
|
|
cs1.Manifest.Extra = []byte(`"update me once more"`)
|
|
manifestBytes, err = json.Marshal(cs1.Manifest)
|
|
require.NoError(t, err)
|
|
cs1.UpdateCounter++
|
|
si, err = cs1.ToStackItem()
|
|
require.NoError(t, err)
|
|
|
|
t.Run("update both script and manifest", func(t *testing.T) {
|
|
updHash := helperInvoker.Invoke(t, stackitem.Null{}, "update", nefBytes, manifestBytes)
|
|
helperInvoker.CheckTxNotificationEvent(t, updHash, 0, state.NotificationEvent{
|
|
ScriptHash: helperInvoker.NativeHash(t, nativenames.Management),
|
|
Name: "Update",
|
|
Item: stackitem.NewArray([]stackitem.Item{stackitem.NewByteArray(cs1.Hash.BytesBE())}),
|
|
})
|
|
t.Run("check contract", func(t *testing.T) {
|
|
managementInvoker.Invoke(t, si, "getContract", cs1.Hash.BytesBE())
|
|
})
|
|
})
|
|
}
|
|
|
|
func TestManagement_GetContract(t *testing.T) {
|
|
c := newManagementClient(t)
|
|
managementInvoker := c.WithSigners(c.Committee)
|
|
|
|
cs1, _ := contracts.GetTestContractState(t, pathToInternalContracts, 1, 2, c.CommitteeHash)
|
|
manifestBytes, err := json.Marshal(cs1.Manifest)
|
|
require.NoError(t, err)
|
|
nefBytes, err := cs1.NEF.Bytes()
|
|
require.NoError(t, err)
|
|
|
|
si, err := cs1.ToStackItem()
|
|
require.NoError(t, err)
|
|
managementInvoker.Invoke(t, si, "deploy", nefBytes, manifestBytes)
|
|
|
|
t.Run("bad parameter type", func(t *testing.T) {
|
|
managementInvoker.InvokeFail(t, "invalid conversion: Array/ByteString", "getContract", []any{int64(1)})
|
|
})
|
|
t.Run("not a hash", func(t *testing.T) {
|
|
managementInvoker.InvokeFail(t, "expected byte size of 20 got 3", "getContract", []byte{1, 2, 3})
|
|
})
|
|
t.Run("positive", func(t *testing.T) {
|
|
managementInvoker.Invoke(t, si, "getContract", cs1.Hash.BytesBE())
|
|
})
|
|
t.Run("by ID, bad parameter type", func(t *testing.T) {
|
|
managementInvoker.InvokeFail(t, "invalid conversion: Array/Integer", "getContractById", []any{int64(1)})
|
|
})
|
|
t.Run("by ID, bad num", func(t *testing.T) {
|
|
managementInvoker.InvokeFail(t, "id is not a correct int32", "getContractById", []byte{1, 2, 3, 4, 5})
|
|
})
|
|
t.Run("by ID, positive", func(t *testing.T) {
|
|
managementInvoker.Invoke(t, si, "getContractById", cs1.ID)
|
|
})
|
|
t.Run("by ID, native", func(t *testing.T) {
|
|
csm := managementInvoker.Executor.Chain.GetContractState(managementInvoker.Hash)
|
|
require.NotNil(t, csm)
|
|
sim, err := csm.ToStackItem()
|
|
require.NoError(t, err)
|
|
managementInvoker.Invoke(t, sim, "getContractById", -1)
|
|
})
|
|
t.Run("by ID, empty", func(t *testing.T) {
|
|
managementInvoker.Invoke(t, stackitem.Null{}, "getContractById", -100)
|
|
})
|
|
t.Run("contract hashes", func(t *testing.T) {
|
|
w := io.NewBufBinWriter()
|
|
emit.AppCall(w.BinWriter, managementInvoker.Hash, "getContractHashes", callflag.All)
|
|
emit.Opcodes(w.BinWriter, opcode.DUP) // Iterator.
|
|
emit.Syscall(w.BinWriter, interopnames.SystemIteratorNext)
|
|
emit.Opcodes(w.BinWriter, opcode.ASSERT) // Has one element.
|
|
emit.Opcodes(w.BinWriter, opcode.DUP) // Iterator.
|
|
emit.Syscall(w.BinWriter, interopnames.SystemIteratorValue)
|
|
emit.Opcodes(w.BinWriter, opcode.SWAP) // Iterator to the top.
|
|
emit.Syscall(w.BinWriter, interopnames.SystemIteratorNext)
|
|
emit.Opcodes(w.BinWriter, opcode.NOT)
|
|
emit.Opcodes(w.BinWriter, opcode.ASSERT) // No more elements, single value left on the stack.
|
|
require.NoError(t, w.Err)
|
|
h := managementInvoker.InvokeScript(t, w.Bytes(), managementInvoker.Signers)
|
|
managementInvoker.Executor.CheckHalt(t, h, stackitem.NewStruct([]stackitem.Item{stackitem.Make([]byte{0, 0, 0, 1}), stackitem.Make(cs1.Hash.BytesBE())}))
|
|
})
|
|
}
|
|
|
|
func TestManagement_ContractDestroy(t *testing.T) {
|
|
c := newManagementClient(t)
|
|
managementInvoker := c.WithSigners(c.Committee)
|
|
|
|
cs1, _ := contracts.GetTestContractState(t, pathToInternalContracts, 1, 2, c.CommitteeHash)
|
|
// Allow calling management contract.
|
|
cs1.Manifest.Permissions = []manifest.Permission{*manifest.NewPermission(manifest.PermissionWildcard)}
|
|
manifestBytes, err := json.Marshal(cs1.Manifest)
|
|
require.NoError(t, err)
|
|
nefBytes, err := cs1.NEF.Bytes()
|
|
require.NoError(t, err)
|
|
|
|
si, err := cs1.ToStackItem()
|
|
require.NoError(t, err)
|
|
managementInvoker.Invoke(t, si, "deploy", nefBytes, manifestBytes)
|
|
helperInvoker := c.Executor.CommitteeInvoker(cs1.Hash)
|
|
|
|
t.Run("no contract", func(t *testing.T) {
|
|
managementInvoker.InvokeFail(t, "key not found", "destroy")
|
|
})
|
|
t.Run("positive", func(t *testing.T) {
|
|
dstrHash := helperInvoker.Invoke(t, stackitem.Null{}, "destroy")
|
|
helperInvoker.CheckTxNotificationEvent(t, dstrHash, 0, state.NotificationEvent{
|
|
ScriptHash: helperInvoker.NativeHash(t, nativenames.Management),
|
|
Name: "Destroy",
|
|
Item: stackitem.NewArray([]stackitem.Item{stackitem.NewByteArray(cs1.Hash.BytesBE())}),
|
|
})
|
|
t.Run("check contract", func(t *testing.T) {
|
|
managementInvoker.Invoke(t, stackitem.Null{}, "getContract", cs1.Hash.BytesBE())
|
|
})
|
|
// deploy after destroy should fail
|
|
managementInvoker.InvokeFail(t, fmt.Sprintf("the contract %s has been blocked", cs1.Hash.StringLE()), "deploy", nefBytes, manifestBytes)
|
|
})
|
|
}
|