package tests import ( "bytes" "path" "testing" "git.frostfs.info/TrueCloudLab/frostfs-contract/policy" "github.com/nspcc-dev/neo-go/pkg/core/interop/storage" "github.com/nspcc-dev/neo-go/pkg/neotest" "github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/vm" "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" "github.com/stretchr/testify/require" ) const policyPath = "../policy" func deployPolicyContract(t *testing.T, e *neotest.Executor) util.Uint160 { cfgPath := path.Join(policyPath, "config.yml") c := neotest.CompileFile(t, e.CommitteeHash, policyPath, cfgPath) e.DeployContract(t, c, []any{nil}) return c.Hash } func newPolicyInvoker(t *testing.T) *neotest.ContractInvoker { e := newExecutor(t) h := deployPolicyContract(t, e) return e.CommitteeInvoker(h) } func TestPolicy(t *testing.T) { e := newPolicyInvoker(t) checkChainsIteratorByPrefix(t, e, policy.Namespace, "mynamespace", "ingress", [][]byte{}) checkChainsIteratorByPrefix(t, e, policy.Container, "cnr1", "ingress", [][]byte{}) // Policies are opaque to the contract and are just raw bytes to store. p1 := []byte("chain1") p2 := []byte("chain2") p3 := []byte("chain3") p33 := []byte("chain33") e.Invoke(t, stackitem.Null{}, "addChain", policy.Namespace, "mynamespace", "ingress:123", p1) checkTargets(t, e, policy.Namespace, [][]byte{[]byte("mynamespace")}) checkChains(t, e, "mynamespace", "", "ingress", [][]byte{p1}) checkChains(t, e, "mynamespace", "", "all", nil) e.Invoke(t, stackitem.Null{}, "addChain", policy.Container, "cnr1", "ingress:myrule2", p2) checkTargets(t, e, policy.Namespace, [][]byte{[]byte("mynamespace")}) checkTargets(t, e, policy.Container, [][]byte{[]byte("cnr1")}) checkChains(t, e, "mynamespace", "", "ingress", [][]byte{p1}) // Only namespace chains. checkChains(t, e, "mynamespace", "cnr1", "ingress", [][]byte{p1, p2}) checkChains(t, e, "mynamespace", "cnr1", "all", nil) // No chains attached to 'all'. checkChains(t, e, "mynamespace", "cnr2", "ingress", [][]byte{p1}) // Only namespace, no chains for the container. e.Invoke(t, stackitem.Null{}, "addChain", policy.Container, "cnr1", "ingress:myrule3", p3) checkTargets(t, e, policy.Namespace, [][]byte{[]byte("mynamespace")}) checkTargets(t, e, policy.Container, [][]byte{[]byte("cnr1")}) checkChains(t, e, "mynamespace", "cnr1", "ingress", [][]byte{p1, p2, p3}) e.Invoke(t, stackitem.Null{}, "addChain", policy.Container, "cnr1", "ingress:myrule3", p33) checkTargets(t, e, policy.Namespace, [][]byte{[]byte("mynamespace")}) checkTargets(t, e, policy.Container, [][]byte{[]byte("cnr1")}) checkChain(t, e, policy.Container, "cnr1", "ingress:myrule3", p33) checkChains(t, e, "mynamespace", "cnr1", "ingress", [][]byte{p1, p2, p33}) // Override chain. checkChainsByPrefix(t, e, policy.Container, "cnr1", "", [][]byte{p2, p33}) checkChainsByPrefix(t, e, policy.IAM, "", "", nil) checkChainKeys(t, e, policy.Container, "cnr1", []string{"ingress:myrule2", "ingress:myrule3"}) checkChainsIteratorByPrefix(t, e, policy.Container, "cnr1", "ingress:myrule3", [][]byte{p33}) checkChainsIteratorByPrefix(t, e, policy.Container, "cnr1", "ingress", [][]byte{p2, p33}) t.Run("removal", func(t *testing.T) { t.Run("wrong name", func(t *testing.T) { e.Invoke(t, stackitem.Null{}, "removeChain", policy.Namespace, "mynamespace", "ingress") checkChains(t, e, "mynamespace", "", "ingress", [][]byte{p1}) }) e.Invoke(t, stackitem.Null{}, "removeChain", policy.Namespace, "mynamespace", "ingress:123") checkChains(t, e, "mynamespace", "", "ingress", nil) checkChains(t, e, "mynamespace", "cnr1", "ingress", [][]byte{p2, p33}) // Container chains still exist. // Remove by prefix. e.Invoke(t, stackitem.Null{}, "removeChainsByPrefix", policy.Container, "cnr1", "ingress") checkChains(t, e, "mynamespace", "cnr1", "ingress", nil) // Remove by prefix. e.Invoke(t, stackitem.Null{}, "removeChainsByPrefix", policy.Container, "cnr1", "ingress") checkChains(t, e, "mynamespace", "cnr1", "ingress", nil) checkTargets(t, e, policy.Namespace, [][]byte{}) checkTargets(t, e, policy.Container, [][]byte{}) }) t.Run("add again after removal", func(t *testing.T) { e.Invoke(t, stackitem.Null{}, "addChain", policy.Namespace, "mynamespace", "ingress:123", p1) e.Invoke(t, stackitem.Null{}, "addChain", policy.Container, "cnr1", "ingress:myrule3", p33) checkTargets(t, e, policy.Namespace, [][]byte{[]byte("mynamespace")}) checkTargets(t, e, policy.Container, [][]byte{[]byte("cnr1")}) }) } func TestAutorization(t *testing.T) { e := newPolicyInvoker(t) e.Invoke(t, stackitem.Null{}, "getAdmin") s := e.NewAccount(t, 1_0000_0000) c := e.WithSigners(s) args := []any{policy.Container, "cnr1", "ingress:myrule3", []byte("opaque")} c.InvokeFail(t, policy.ErrNotAuthorized, "addChain", args...) e.Invoke(t, stackitem.Null{}, "setAdmin", s.ScriptHash()) e.Invoke(t, stackitem.NewBuffer(s.ScriptHash().BytesBE()), "getAdmin") c.Invoke(t, stackitem.Null{}, "addChain", args...) } func checkChains(t *testing.T, e *neotest.ContractInvoker, namespace, container, name string, expected [][]byte) { s, err := e.TestInvoke(t, "listChains", namespace, container, name) require.NoError(t, err) checksChainsOnStack(t, s, expected) } func checkChainsByPrefix(t *testing.T, e *neotest.ContractInvoker, kind byte, entityName, prefix string, expected [][]byte) { s, err := e.TestInvoke(t, "listChainsByPrefix", kind, entityName, prefix) require.NoError(t, err) checksChainsOnStack(t, s, expected) } func checkChainsIteratorByPrefix(t *testing.T, e *neotest.ContractInvoker, kind byte, entityName, prefix string, expected [][]byte) { s, err := e.TestInvoke(t, "iteratorChainsByPrefix", kind, entityName, prefix) require.NoError(t, err) if s.Len() == 0 { t.Fatal("Stack is empty") } iteratorItem := s.Pop().Value().(*storage.Iterator) policys := iteratorToArray(iteratorItem) require.Equal(t, len(expected), len(policys)) for i := range expected { bytesPolicy, err := policys[i].TryBytes() require.NoError(t, err) require.Equal(t, expected[i], bytesPolicy) } } func checkChainKeys(t *testing.T, e *neotest.ContractInvoker, kind byte, entityName string, expected []string) { s, err := e.TestInvoke(t, "listChainNames", kind, entityName) require.NoError(t, err) if s.Len() == 0 { t.Fatal("Stack is empty") } iteratorItem := s.Pop().Value().(*storage.Iterator) policys := iteratorToArray(iteratorItem) require.Equal(t, len(expected), len(policys)) for i := range expected { bytesPolicy, err := policys[i].TryBytes() require.NoError(t, err) require.Equal(t, expected[i], string(bytesPolicy)) } } func checksChainsOnStack(t *testing.T, s *vm.Stack, expected [][]byte) { require.Equal(t, 1, s.Len()) var actual [][]byte arr := s.Pop().Array() for i := range arr { bs, err := arr[i].TryBytes() require.NoError(t, err) actual = append(actual, bs) } require.ElementsMatch(t, expected, actual) } func checkChain(t *testing.T, e *neotest.ContractInvoker, kind byte, entityName, name string, expected []byte) { s, err := e.TestInvoke(t, "getChain", kind, entityName, name) require.NoError(t, err) require.Equal(t, 1, s.Len()) require.True(t, bytes.Equal(expected, s.Pop().Bytes())) } func checkTargets(t *testing.T, e *neotest.ContractInvoker, kind byte, expected [][]byte) { s, err := e.TestInvoke(t, "listTargets", kind) require.NoError(t, err) require.NotEqual(t, 0, s.Len(), "stack is empty") iteratorItem := s.Pop().Value().(*storage.Iterator) targets := iteratorToArray(iteratorItem) require.Equal(t, len(expected), len(targets)) for i := range expected { bytesTargets, err := targets[i].TryBytes() require.NoError(t, err) require.Equal(t, expected[i], bytesTargets) } }