package core import ( "encoding/json" "math/big" "testing" "github.com/nspcc-dev/neo-go/internal/testchain" "github.com/nspcc-dev/neo-go/pkg/config" "github.com/nspcc-dev/neo-go/pkg/core/chaindump" "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/crypto/keys" "github.com/nspcc-dev/neo-go/pkg/io" "github.com/nspcc-dev/neo-go/pkg/smartcontract" "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/util" "github.com/nspcc-dev/neo-go/pkg/vm" "github.com/nspcc-dev/neo-go/pkg/vm/opcode" "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" "github.com/stretchr/testify/require" ) // This is in a separate test because test test for long manifest // prevents chain from being dumped. In any real scenario // restrictions on tx script length will be applied before // restrictions on manifest size. In this test providing manifest of max size // leads to tx deserialization failure. func TestRestoreAfterDeploy(t *testing.T) { bc := newTestChain(t) defer bc.Close() // nef.NewFile() cares about version a lot. config.Version = "0.90.0-test" mgmtHash := bc.ManagementContractHash() cs1, _ := getTestContractState(bc) cs1.ID = 1 cs1.Hash = state.CreateContractHash(testchain.MultisigScriptHash(), cs1.Script) manif1, err := json.Marshal(cs1.Manifest) require.NoError(t, err) nef1, err := nef.NewFile(cs1.Script) require.NoError(t, err) nef1b, err := nef1.Bytes() require.NoError(t, err) res, err := invokeContractMethod(bc, 100_00000000, mgmtHash, "deploy", nef1b, append(manif1, make([]byte, manifest.MaxManifestSize)...)) require.NoError(t, err) checkFAULTState(t, res) } type memoryStore struct { *storage.MemoryStore } func (memoryStore) Close() error { return nil } func TestStartFromHeight(t *testing.T) { st := memoryStore{storage.NewMemoryStore()} bc := newTestChainWithCustomCfgAndStore(t, st, nil) cs1, _ := getTestContractState(bc) func() { defer bc.Close() require.NoError(t, bc.contracts.Management.PutContractState(bc.dao, cs1)) checkContractState(t, bc, cs1.Hash, cs1) _, err := bc.dao.Store.Persist() require.NoError(t, err) }() bc2 := newTestChainWithCustomCfgAndStore(t, st, nil) checkContractState(t, bc2, cs1.Hash, cs1) } func TestContractDeploy(t *testing.T) { bc := newTestChain(t) defer bc.Close() // nef.NewFile() cares about version a lot. config.Version = "0.90.0-test" mgmtHash := bc.ManagementContractHash() cs1, _ := getTestContractState(bc) cs1.ID = 1 cs1.Hash = state.CreateContractHash(testchain.MultisigScriptHash(), cs1.Script) manif1, err := json.Marshal(cs1.Manifest) require.NoError(t, err) nef1, err := nef.NewFile(cs1.Script) require.NoError(t, err) nef1b, err := nef1.Bytes() require.NoError(t, err) t.Run("no NEF", func(t *testing.T) { res, err := invokeContractMethod(bc, 11_00000000, mgmtHash, "deploy", nil, manif1) require.NoError(t, err) checkFAULTState(t, res) }) t.Run("no manifest", func(t *testing.T) { res, err := invokeContractMethod(bc, 11_00000000, mgmtHash, "deploy", nef1b, nil) require.NoError(t, err) checkFAULTState(t, res) }) t.Run("int for NEF", func(t *testing.T) { res, err := invokeContractMethod(bc, 11_00000000, mgmtHash, "deploy", int64(1), manif1) require.NoError(t, err) checkFAULTState(t, res) }) t.Run("zero-length NEF", func(t *testing.T) { res, err := invokeContractMethod(bc, 11_00000000, mgmtHash, "deploy", []byte{}, manif1) require.NoError(t, err) checkFAULTState(t, res) }) t.Run("array for NEF", func(t *testing.T) { res, err := invokeContractMethod(bc, 11_00000000, mgmtHash, "deploy", []interface{}{int64(1)}, manif1) require.NoError(t, err) checkFAULTState(t, res) }) t.Run("int for manifest", func(t *testing.T) { res, err := invokeContractMethod(bc, 11_00000000, mgmtHash, "deploy", nef1b, int64(1)) require.NoError(t, err) checkFAULTState(t, res) }) t.Run("zero-length manifest", func(t *testing.T) { res, err := invokeContractMethod(bc, 11_00000000, mgmtHash, "deploy", nef1b, []byte{}) require.NoError(t, err) checkFAULTState(t, res) }) t.Run("array for manifest", func(t *testing.T) { res, err := invokeContractMethod(bc, 11_00000000, mgmtHash, "deploy", nef1b, []interface{}{int64(1)}) require.NoError(t, err) checkFAULTState(t, res) }) 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, 64)}} manifB, err := json.Marshal(badManifest) require.NoError(t, err) res, err := invokeContractMethod(bc, 11_00000000, mgmtHash, "deploy", nef1b, manifB) require.NoError(t, err) checkFAULTState(t, res) }) t.Run("not enough GAS", func(t *testing.T) { res, err := invokeContractMethod(bc, 1_00000000, mgmtHash, "deploy", nef1b, manif1) require.NoError(t, err) checkFAULTState(t, res) }) t.Run("positive", func(t *testing.T) { tx1, err := prepareContractMethodInvoke(bc, 11_00000000, mgmtHash, "deploy", nef1b, manif1) require.NoError(t, err) tx2, err := prepareContractMethodInvoke(bc, 1_00000000, mgmtHash, "getContract", cs1.Hash.BytesBE()) require.NoError(t, err) aers, err := persistBlock(bc, tx1, tx2) require.NoError(t, err) for _, res := range aers { require.Equal(t, vm.HaltState, res.VMState) require.Equal(t, 1, len(res.Stack)) compareContractStates(t, cs1, res.Stack[0]) } require.Equal(t, aers[0].Events, []state.NotificationEvent{{ ScriptHash: mgmtHash, Name: "Deploy", Item: stackitem.NewArray([]stackitem.Item{stackitem.NewByteArray(cs1.Hash.BytesBE())}), }}) t.Run("_deploy called", func(t *testing.T) { res, err := invokeContractMethod(bc, 1_00000000, cs1.Hash, "getValue") require.NoError(t, err) require.Equal(t, 1, len(res.Stack)) require.Equal(t, []byte("create"), res.Stack[0].Value()) }) t.Run("get after deploy", func(t *testing.T) { checkContractState(t, bc, cs1.Hash, cs1) }) t.Run("get after restore", func(t *testing.T) { w := io.NewBufBinWriter() require.NoError(t, chaindump.Dump(bc, w.BinWriter, 0, bc.BlockHeight()+1)) require.NoError(t, w.Err) r := io.NewBinReaderFromBuf(w.Bytes()) bc2 := newTestChain(t) defer bc2.Close() require.NoError(t, chaindump.Restore(bc2, r, 0, bc.BlockHeight()+1, nil)) require.NoError(t, r.Err) checkContractState(t, bc2, cs1.Hash, cs1) }) }) t.Run("contract already exists", func(t *testing.T) { res, err := invokeContractMethod(bc, 11_00000000, mgmtHash, "deploy", nef1b, manif1) require.NoError(t, err) checkFAULTState(t, res) }) 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("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) res, err := invokeContractMethod(bc, 11_00000000, mgmtHash, "deploy", nefDb, manifD) require.NoError(t, err) checkFAULTState(t, res) t.Run("get after failed deploy", func(t *testing.T) { h := state.CreateContractHash(neoOwner, deployScript) checkContractState(t, bc, h, nil) }) }) 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("isUpdate", smartcontract.BoolType), manifest.NewParameter("param", smartcontract.IntegerType), }, 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) res, err := invokeContractMethod(bc, 11_00000000, mgmtHash, "deploy", nefDb, manifD) require.NoError(t, err) checkFAULTState(t, res) t.Run("get after bad _deploy", func(t *testing.T) { h := state.CreateContractHash(neoOwner, deployScript) checkContractState(t, bc, h, nil) }) }) } func checkContractState(t *testing.T, bc *Blockchain, h util.Uint160, cs *state.Contract) { mgmtHash := bc.contracts.Management.Hash res, err := invokeContractMethod(bc, 1_00000000, mgmtHash, "getContract", h.BytesBE()) require.NoError(t, err) if cs == nil { require.Equal(t, vm.FaultState, res.VMState) return } require.Equal(t, vm.HaltState, res.VMState) require.Equal(t, 1, len(res.Stack)) compareContractStates(t, cs, res.Stack[0]) } func TestContractUpdate(t *testing.T) { bc := newTestChain(t) defer bc.Close() // nef.NewFile() cares about version a lot. config.Version = "0.90.0-test" mgmtHash := bc.ManagementContractHash() cs1, _ := getTestContractState(bc) // Allow calling management contract. cs1.Manifest.Permissions = []manifest.Permission{*manifest.NewPermission(manifest.PermissionWildcard)} err := bc.contracts.Management.PutContractState(bc.dao, cs1) require.NoError(t, err) manif1, err := json.Marshal(cs1.Manifest) require.NoError(t, err) nef1, err := nef.NewFile(cs1.Script) require.NoError(t, err) nef1b, err := nef1.Bytes() require.NoError(t, err) t.Run("no contract", func(t *testing.T) { res, err := invokeContractMethod(bc, 10_00000000, mgmtHash, "update", nef1b, manif1) require.NoError(t, err) checkFAULTState(t, res) }) t.Run("zero-length NEF", func(t *testing.T) { res, err := invokeContractMethod(bc, 10_00000000, cs1.Hash, "update", []byte{}, manif1) require.NoError(t, err) checkFAULTState(t, res) }) t.Run("zero-length manifest", func(t *testing.T) { res, err := invokeContractMethod(bc, 10_00000000, cs1.Hash, "update", nef1b, []byte{}) require.NoError(t, err) checkFAULTState(t, res) }) t.Run("not enough GAS", func(t *testing.T) { res, err := invokeContractMethod(bc, 1_00000000, cs1.Hash, "update", nef1b, manif1) require.NoError(t, err) checkFAULTState(t, res) }) t.Run("no real params", func(t *testing.T) { res, err := invokeContractMethod(bc, 10_00000000, cs1.Hash, "update", nil, nil) require.NoError(t, err) checkFAULTState(t, res) }) 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, 64)}} manifB, err := json.Marshal(badManifest) require.NoError(t, err) res, err := invokeContractMethod(bc, 10_00000000, cs1.Hash, "update", nef1b, manifB) require.NoError(t, err) checkFAULTState(t, res) }) cs1.Script = append(cs1.Script, byte(opcode.RET)) nef1, err = nef.NewFile(cs1.Script) require.NoError(t, err) nef1b, err = nef1.Bytes() require.NoError(t, err) cs1.UpdateCounter++ t.Run("update script, positive", func(t *testing.T) { tx1, err := prepareContractMethodInvoke(bc, 10_00000000, cs1.Hash, "update", nef1b, nil) require.NoError(t, err) tx2, err := prepareContractMethodInvoke(bc, 1_00000000, mgmtHash, "getContract", cs1.Hash.BytesBE()) require.NoError(t, err) aers, err := persistBlock(bc, tx1, tx2) require.NoError(t, err) require.Equal(t, vm.HaltState, aers[0].VMState) require.Equal(t, vm.HaltState, aers[1].VMState) require.Equal(t, 1, len(aers[1].Stack)) compareContractStates(t, cs1, aers[1].Stack[0]) require.Equal(t, aers[0].Events, []state.NotificationEvent{{ ScriptHash: mgmtHash, Name: "Update", Item: stackitem.NewArray([]stackitem.Item{stackitem.NewByteArray(cs1.Hash.BytesBE())}), }}) t.Run("_deploy called", func(t *testing.T) { res, err := invokeContractMethod(bc, 1_00000000, cs1.Hash, "getValue") require.NoError(t, err) require.Equal(t, 1, len(res.Stack)) require.Equal(t, []byte("update"), res.Stack[0].Value()) }) t.Run("check contract", func(t *testing.T) { checkContractState(t, bc, cs1.Hash, cs1) }) }) cs1.Manifest.Extra = "update me" manif1, err = json.Marshal(cs1.Manifest) require.NoError(t, err) cs1.UpdateCounter++ t.Run("update manifest, positive", func(t *testing.T) { res, err := invokeContractMethod(bc, 10_00000000, cs1.Hash, "update", nil, manif1) require.NoError(t, err) require.Equal(t, vm.HaltState, res.VMState) require.Equal(t, res.Events, []state.NotificationEvent{{ ScriptHash: mgmtHash, Name: "Update", Item: stackitem.NewArray([]stackitem.Item{stackitem.NewByteArray(cs1.Hash.BytesBE())}), }}) t.Run("check contract", func(t *testing.T) { checkContractState(t, bc, cs1.Hash, cs1) }) }) cs1.Script = append(cs1.Script, byte(opcode.ABORT)) nef1, err = nef.NewFile(cs1.Script) require.NoError(t, err) nef1b, err = nef1.Bytes() require.NoError(t, err) cs1.Manifest.Extra = "update me once more" manif1, err = json.Marshal(cs1.Manifest) require.NoError(t, err) cs1.UpdateCounter++ t.Run("update both script and manifest", func(t *testing.T) { res, err := invokeContractMethod(bc, 10_00000000, cs1.Hash, "update", nef1b, manif1) require.NoError(t, err) require.Equal(t, vm.HaltState, res.VMState) require.Equal(t, res.Events, []state.NotificationEvent{{ ScriptHash: mgmtHash, Name: "Update", Item: stackitem.NewArray([]stackitem.Item{stackitem.NewByteArray(cs1.Hash.BytesBE())}), }}) t.Run("check contract", func(t *testing.T) { checkContractState(t, bc, cs1.Hash, cs1) }) }) } func TestGetContract(t *testing.T) { bc := newTestChain(t) defer bc.Close() mgmtHash := bc.ManagementContractHash() cs1, _ := getTestContractState(bc) err := bc.contracts.Management.PutContractState(bc.dao, cs1) require.NoError(t, err) t.Run("bad parameter type", func(t *testing.T) { res, err := invokeContractMethod(bc, 1_00000000, mgmtHash, "getContract", []interface{}{int64(1)}) require.NoError(t, err) checkFAULTState(t, res) }) t.Run("not a hash", func(t *testing.T) { res, err := invokeContractMethod(bc, 1_00000000, mgmtHash, "getContract", []byte{1, 2, 3}) require.NoError(t, err) checkFAULTState(t, res) }) t.Run("positive", func(t *testing.T) { res, err := invokeContractMethod(bc, 1_00000000, mgmtHash, "getContract", cs1.Hash.BytesBE()) require.NoError(t, err) require.Equal(t, 1, len(res.Stack)) compareContractStates(t, cs1, res.Stack[0]) }) } func TestContractDestroy(t *testing.T) { bc := newTestChain(t) defer bc.Close() mgmtHash := bc.ManagementContractHash() cs1, _ := getTestContractState(bc) // Allow calling management contract. cs1.Manifest.Permissions = []manifest.Permission{*manifest.NewPermission(manifest.PermissionWildcard)} err := bc.contracts.Management.PutContractState(bc.dao, cs1) require.NoError(t, err) err = bc.dao.PutStorageItem(cs1.ID, []byte{1, 2, 3}, &state.StorageItem{Value: []byte{3, 2, 1}}) require.NoError(t, err) require.NoError(t, bc.dao.UpdateMPT()) t.Run("no contract", func(t *testing.T) { res, err := invokeContractMethod(bc, 1_00000000, mgmtHash, "destroy") require.NoError(t, err) checkFAULTState(t, res) }) t.Run("positive", func(t *testing.T) { res, err := invokeContractMethod(bc, 1_00000000, cs1.Hash, "destroy") require.NoError(t, err) require.Equal(t, vm.HaltState, res.VMState) require.Equal(t, res.Events, []state.NotificationEvent{{ ScriptHash: mgmtHash, Name: "Destroy", Item: stackitem.NewArray([]stackitem.Item{stackitem.NewByteArray(cs1.Hash.BytesBE())}), }}) t.Run("check contract", func(t *testing.T) { res, err := invokeContractMethod(bc, 1_00000000, mgmtHash, "getContract", cs1.Hash.BytesBE()) require.NoError(t, err) checkFAULTState(t, res) }) }) } func compareContractStates(t *testing.T, expected *state.Contract, actual stackitem.Item) { act, ok := actual.Value().([]stackitem.Item) require.True(t, ok) expectedManifest, err := json.Marshal(expected.Manifest) require.NoError(t, err) require.Equal(t, 5, len(act)) require.Equal(t, expected.ID, int32(act[0].Value().(*big.Int).Int64())) require.Equal(t, expected.UpdateCounter, uint16(act[1].Value().(*big.Int).Int64())) require.Equal(t, expected.Hash.BytesBE(), act[2].Value().([]byte)) require.Equal(t, expected.Script, act[3].Value().([]byte)) require.Equal(t, expectedManifest, act[4].Value().([]byte)) } func TestMinimumDeploymentFee(t *testing.T) { chain := newTestChain(t) defer chain.Close() t.Run("get, internal method", func(t *testing.T) { n := chain.contracts.Management.GetMinimumDeploymentFee(chain.dao) require.Equal(t, 10_00000000, int(n)) }) testGetSet(t, chain, chain.contracts.Management.Hash, "MinimumDeploymentFee", 10_00000000, 0, 0) }