package tests import ( "crypto/sha256" "strings" "testing" "github.com/mr-tron/base58" "github.com/nspcc-dev/neo-go/pkg/core" "github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" "github.com/nspcc-dev/neo-go/pkg/wallet" "github.com/nspcc-dev/neofs-contract/container" "github.com/nspcc-dev/neofs-contract/nns" "github.com/stretchr/testify/require" ) const containerPath = "../container" const ( containerFee = 0_0100_0000 containerAliasFee = 0_0050_0000 ) func deployContainerContract(t *testing.T, bc *core.Blockchain, addrNetmap, addrBalance, addrNNS util.Uint160) util.Uint160 { args := make([]interface{}, 6) args[0] = int64(0) args[1] = addrNetmap args[2] = addrBalance args[3] = util.Uint160{} // not needed for now args[4] = addrNNS args[5] = "neofs" return DeployContract(t, bc, containerPath, args) } func prepareContainerContract(t *testing.T, bc *core.Blockchain) (util.Uint160, util.Uint160) { addrNNS := DeployContract(t, bc, nnsPath, nil) ctrNetmap, err := ContractInfo(CommitteeAcc.Contract.ScriptHash(), netmapPath) require.NoError(t, err) ctrBalance, err := ContractInfo(CommitteeAcc.Contract.ScriptHash(), balancePath) require.NoError(t, err) ctrContainer, err := ContractInfo(CommitteeAcc.Contract.ScriptHash(), containerPath) require.NoError(t, err) deployNetmapContract(t, bc, ctrBalance.Hash, ctrContainer.Hash, container.RegistrationFeeKey, int64(containerFee), container.AliasFeeKey, int64(containerAliasFee)) balHash := deployBalanceContract(t, bc, ctrNetmap.Hash, ctrContainer.Hash) return deployContainerContract(t, bc, ctrNetmap.Hash, ctrBalance.Hash, addrNNS), balHash } func setContainerOwner(c []byte, acc *wallet.Account) { owner, _ := base58.Decode(acc.Address) copy(c[6:], owner) } type testContainer struct { id [32]byte value, sig, pub, token []byte } func dummyContainer(owner *wallet.Account) testContainer { value := randomBytes(100) value[1] = 0 // zero offset setContainerOwner(value, owner) return testContainer{ id: sha256.Sum256(value), value: value, sig: randomBytes(64), pub: randomBytes(33), token: randomBytes(42), } } func TestContainerPut(t *testing.T) { bc := NewChain(t) h, balanceHash := prepareContainerContract(t, bc) acc := NewAccount(t, bc) c := dummyContainer(acc) putArgs := []interface{}{c.value, c.sig, c.pub, c.token} tx := PrepareInvoke(t, bc, CommitteeAcc, h, "put", putArgs...) AddBlock(t, bc, tx) CheckFault(t, bc, tx.Hash(), "insufficient balance to create container") balanceMint(t, bc, acc, balanceHash, containerFee*1, []byte{}) tx = PrepareInvoke(t, bc, acc, h, "put", putArgs...) AddBlock(t, bc, tx) CheckFault(t, bc, tx.Hash(), "alphabet witness check failed") tx = PrepareInvoke(t, bc, CommitteeAcc, h, "put", putArgs...) AddBlockCheckHalt(t, bc, tx) t.Run("with nice names", func(t *testing.T) { nnsHash := contracts[nnsPath].Hash balanceMint(t, bc, acc, balanceHash, containerFee*1, []byte{}) putArgs := []interface{}{c.value, c.sig, c.pub, c.token, "mycnt", ""} t.Run("no fee for alias", func(t *testing.T) { tx = PrepareInvoke(t, bc, acc, h, "putNamed", putArgs...) AddBlock(t, bc, tx) CheckFault(t, bc, tx.Hash(), "insufficient balance to create container") }) balanceMint(t, bc, acc, balanceHash, containerAliasFee*1, []byte{}) tx = PrepareInvoke(t, bc, CommitteeAcc, h, "putNamed", putArgs...) AddBlockCheckHalt(t, bc, tx) tx = PrepareInvoke(t, bc, acc, nnsHash, "resolve", "mycnt.neofs", int64(nns.TXT)) CheckTestInvoke(t, bc, tx, stackitem.NewArray([]stackitem.Item{ stackitem.NewByteArray([]byte(base58.Encode(c.id[:]))), })) t.Run("name is already taken", func(t *testing.T) { tx = PrepareInvoke(t, bc, CommitteeAcc, h, "putNamed", putArgs...) AddBlock(t, bc, tx) CheckFault(t, bc, tx.Hash(), "name is already taken") }) tx = PrepareInvoke(t, bc, CommitteeAcc, h, "delete", c.id[:], c.sig, c.token) AddBlockCheckHalt(t, bc, tx) tx = PrepareInvoke(t, bc, CommitteeAcc, nnsHash, "resolve", "mycnt.neofs", int64(nns.TXT)) CheckTestInvoke(t, bc, tx, stackitem.Null{}) t.Run("register in advance", func(t *testing.T) { c.value[len(c.value)-1] = 10 c.id = sha256.Sum256(c.value) t.Run("bad domain owner", func(t *testing.T) { tx = PrepareInvoke(t, bc, acc, nnsHash, "register", "baddomain.neofs", acc.Contract.ScriptHash(), "whateveriwant@world.com", int64(0), int64(0), int64(0), int64(0)) AddBlockCheckHalt(t, bc, tx) tx = PrepareInvoke(t, bc, acc, h, "putNamed", c.value, c.sig, c.pub, c.token, "baddomain", "neofs") AddBlock(t, bc, tx) CheckFault(t, bc, tx.Hash(), "committee or container contract must own registered domain") }) tx = PrepareInvoke(t, bc, CommitteeAcc, nnsHash, "register", "second.neofs", CommitteeAcc.Contract.ScriptHash(), "whateveriwant@world.com", int64(0), int64(0), int64(0), int64(0)) AddBlockCheckHalt(t, bc, tx) balanceMint(t, bc, acc, balanceHash, (containerFee+containerAliasFee)*1, []byte{}) putArgs := []interface{}{c.value, c.sig, c.pub, c.token, "second", "neofs"} tx = PrepareInvoke(t, bc, []*wallet.Account{CommitteeAcc, acc}, h, "putNamed", putArgs...) AddBlockCheckHalt(t, bc, tx) tx = PrepareInvoke(t, bc, CommitteeAcc, nnsHash, "resolve", "second.neofs", int64(nns.TXT)) CheckTestInvoke(t, bc, tx, stackitem.NewArray([]stackitem.Item{ stackitem.NewByteArray([]byte(base58.Encode(c.id[:]))), })) }) }) } func TestContainerDelete(t *testing.T) { bc := NewChain(t) h, balanceHash := prepareContainerContract(t, bc) acc := NewAccount(t, bc) c := dummyContainer(acc) balanceMint(t, bc, acc, balanceHash, containerFee*1, []byte{}) tx := PrepareInvoke(t, bc, CommitteeAcc, h, "put", c.value, c.sig, c.pub, c.token) AddBlockCheckHalt(t, bc, tx) tx = PrepareInvoke(t, bc, acc, h, "delete", c.id[:], c.sig, c.token) AddBlock(t, bc, tx) CheckFault(t, bc, tx.Hash(), "delete: alphabet witness check failed") tx = PrepareInvoke(t, bc, CommitteeAcc, h, "delete", c.id[:], c.sig, c.token) AddBlockCheckHalt(t, bc, tx) t.Run("missing container", func(t *testing.T) { id := c.id id[0] ^= 0xFF tx = PrepareInvoke(t, bc, CommitteeAcc, h, "delete", id[:], c.sig, c.token) AddBlockCheckHalt(t, bc, tx) }) tx = PrepareInvoke(t, bc, acc, h, "get", c.id[:]) _, err := TestInvoke(bc, tx) require.Error(t, err) require.True(t, strings.Contains(err.Error(), container.NotFoundError)) } func TestContainerOwner(t *testing.T) { bc := NewChain(t) h, balanceHash := prepareContainerContract(t, bc) acc := NewAccount(t, bc) balanceMint(t, bc, acc, balanceHash, containerFee*1, []byte{}) c := dummyContainer(acc) tx := PrepareInvoke(t, bc, CommitteeAcc, h, "put", c.value, c.sig, c.pub, c.token) AddBlockCheckHalt(t, bc, tx) t.Run("missing container", func(t *testing.T) { id := c.id id[0] ^= 0xFF tx = PrepareInvoke(t, bc, CommitteeAcc, h, "owner", id[:]) _, err := TestInvoke(bc, tx) require.Error(t, err) require.True(t, strings.Contains(err.Error(), container.NotFoundError)) }) tx = PrepareInvoke(t, bc, CommitteeAcc, h, "owner", c.id[:]) owner, _ := base58.Decode(acc.Address) CheckTestInvoke(t, bc, tx, stackitem.NewBuffer(owner)) } func TestContainerGet(t *testing.T) { bc := NewChain(t) h, balanceHash := prepareContainerContract(t, bc) acc := NewAccount(t, bc) balanceMint(t, bc, acc, balanceHash, containerFee*1, []byte{}) c := dummyContainer(acc) tx := PrepareInvoke(t, bc, CommitteeAcc, h, "put", c.value, c.sig, c.pub, c.token) AddBlockCheckHalt(t, bc, tx) t.Run("missing container", func(t *testing.T) { id := c.id id[0] ^= 0xFF tx = PrepareInvoke(t, bc, CommitteeAcc, h, "get", id[:]) _, err := TestInvoke(bc, tx) require.Error(t, err) require.True(t, strings.Contains(err.Error(), container.NotFoundError)) }) tx = PrepareInvoke(t, bc, CommitteeAcc, h, "get", c.id[:]) CheckTestInvoke(t, bc, tx, stackitem.NewStruct([]stackitem.Item{ stackitem.NewByteArray(c.value), stackitem.NewByteArray(c.sig), stackitem.NewByteArray(c.pub), stackitem.NewByteArray(c.token), })) } type eacl struct { value []byte sig []byte pub []byte token []byte } func dummyEACL(containerID [32]byte) eacl { e := make([]byte, 50) copy(e[6:], containerID[:]) return eacl{ value: e, sig: randomBytes(64), pub: randomBytes(33), token: randomBytes(42), } } func TestContainerSetEACL(t *testing.T) { bc := NewChain(t) h, balanceHash := prepareContainerContract(t, bc) acc := NewAccount(t, bc) balanceMint(t, bc, acc, balanceHash, containerFee*1, []byte{}) c := dummyContainer(acc) tx := PrepareInvoke(t, bc, CommitteeAcc, h, "put", c.value, c.sig, c.pub, c.token) AddBlockCheckHalt(t, bc, tx) t.Run("missing container", func(t *testing.T) { id := c.id id[0] ^= 0xFF e := dummyEACL(id) tx = PrepareInvoke(t, bc, CommitteeAcc, h, "setEACL", e.value, e.sig, e.pub, e.token) _, err := TestInvoke(bc, tx) require.Error(t, err) require.True(t, strings.Contains(err.Error(), container.NotFoundError)) }) e := dummyEACL(c.id) tx = PrepareInvoke(t, bc, acc, h, "setEACL", e.value, e.sig, e.pub, e.token) AddBlock(t, bc, tx) CheckFault(t, bc, tx.Hash(), "setEACL: alphabet witness check failed") tx = PrepareInvoke(t, bc, CommitteeAcc, h, "setEACL", e.value, e.sig, e.pub, e.token) AddBlockCheckHalt(t, bc, tx) tx = PrepareInvoke(t, bc, CommitteeAcc, h, "eACL", c.id[:]) CheckTestInvoke(t, bc, tx, stackitem.NewStruct([]stackitem.Item{ stackitem.NewByteArray(e.value), stackitem.NewByteArray(e.sig), stackitem.NewByteArray(e.pub), stackitem.NewByteArray(e.token), })) }