diff --git a/subnet/subnet_contract.go b/subnet/subnet_contract.go index bedaba7..091756b 100644 --- a/subnet/subnet_contract.go +++ b/subnet/subnet_contract.go @@ -20,8 +20,12 @@ const ( ErrInvalidAdmin = "invalid administrator" // ErrAlreadyExists is thrown when id already exists. ErrAlreadyExists = "subnet id already exists" - // ErrNotExist is thrown when id doesn't exist. - ErrNotExist = "subnet id doesn't exist" + // ErrSubNotExist is thrown when id doesn't exist. + ErrSubNotExist = "subnet id doesn't exist" + // ErrNodeAdmNotExist is thrown when node admin is not found. + ErrNodeAdmNotExist = "node admin not found" + + errCheckWitnessFailed = "owner witness check failed" ownerPrefix = 'o' nodeAdminPrefix = 'a' @@ -94,7 +98,7 @@ func Put(id []byte, ownerKey interop.PublicKey, info []byte) { common.RemoveVotes(ctx, id) } else { if !runtime.CheckWitness(ownerKey) { - panic("put: owner witness check failed") + panic("put: " + errCheckWitnessFailed) } multiaddr := common.AlphabetAddress() @@ -110,11 +114,11 @@ func Put(id []byte, ownerKey interop.PublicKey, info []byte) { // Get returns info about subnet with the specified id. func Get(id []byte) []byte { - ctx := storage.GetContext() + ctx := storage.GetReadOnlyContext() key := append([]byte{infoPrefix}, id...) raw := storage.Get(ctx, key) if raw == nil { - panic("get: " + ErrNotExist) + panic("get: " + ErrSubNotExist) } return raw.([]byte) } @@ -125,12 +129,12 @@ func Delete(id []byte) { key := append([]byte{ownerPrefix}, id...) raw := storage.Get(ctx, key) if raw == nil { - panic("delete:" + ErrNotExist) + panic("delete:" + ErrSubNotExist) } owner := raw.([]byte) if !runtime.CheckWitness(owner) { - panic("delete: owner witness check failed") + panic("delete: " + errCheckWitnessFailed) } storage.Delete(ctx, key) @@ -153,12 +157,12 @@ func AddNodeAdmin(subnetID []byte, adminKey interop.PublicKey) { rawOwner := storage.Get(ctx, stKey) if rawOwner == nil { - panic("addNodeAdmin: " + ErrNotExist) + panic("addNodeAdmin: " + ErrSubNotExist) } owner := rawOwner.([]byte) if !runtime.CheckWitness(owner) { - panic("addNodeAdmin: owner witness check failed") + panic("addNodeAdmin: " + errCheckWitnessFailed) } stKey[0] = nodeAdminPrefix @@ -175,6 +179,37 @@ func AddNodeAdmin(subnetID []byte, adminKey interop.PublicKey) { storage.Put(ctx, append(stKey, adminKey...), []byte{1}) } +// RemoveNodeAdmin removes node administrator from the specified subnetwork. +// Must be called by subnet owner only. +func RemoveNodeAdmin(subnetID []byte, adminKey interop.PublicKey) { + if len(adminKey) != interop.PublicKeyCompressedLen { + panic("removeNodeAdmin: " + ErrInvalidAdmin) + } + + ctx := storage.GetContext() + + stOwnerKey := append([]byte{ownerPrefix}, subnetID...) + + rawOwner := storage.Get(ctx, stOwnerKey) + if rawOwner == nil { + panic("removeNodeAdmin: " + ErrSubNotExist) + } + + owner := rawOwner.([]byte) + if !runtime.CheckWitness(owner) { + panic("removeNodeAdmin: " + errCheckWitnessFailed) + } + + stOwnerKey[0] = nodeAdminPrefix + stNodeAdmKey := append(stOwnerKey, adminKey...) + + if storage.Get(ctx, stNodeAdmKey) == nil { + panic("removeNodeAdmin: " + ErrNodeAdmNotExist) + } + + storage.Delete(ctx, stNodeAdmKey) +} + // Version returns version of the contract. func Version() int { return common.Version diff --git a/tests/subnet_test.go b/tests/subnet_test.go index 4c60e59..d71361b 100644 --- a/tests/subnet_test.go +++ b/tests/subnet_test.go @@ -14,7 +14,11 @@ import ( "github.com/stretchr/testify/require" ) -const subnetPath = "../subnet" +const ( + subnetPath = "../subnet" + + errSeparator = ": " +) func deploySubnetContract(t *testing.T, e *neotest.Executor) util.Uint160 { c := neotest.CompileFile(t, e.CommitteeHash, subnetPath, path.Join(subnetPath, "config.yml")) @@ -61,51 +65,30 @@ func TestSubnet_Put(t *testing.T) { func TestSubnet_Delete(t *testing.T) { e := newSubnetInvoker(t) - acc := e.NewAccount(t) - pub, ok := vm.ParseSignatureContract(acc.Script()) - require.True(t, ok) - - id := make([]byte, 4) - binary.LittleEndian.PutUint32(id, 123) - info := randomBytes(10) - - cBoth := e.WithSigners(e.Committee, acc) - cBoth.Invoke(t, stackitem.Null{}, "put", id, pub, info) + id, owner := createSubnet(t, e) e.InvokeFail(t, "witness check failed", "delete", id) - cAcc := e.WithSigners(acc) - cAcc.InvokeFail(t, subnet.ErrNotExist, "delete", []byte{1, 1, 1, 1}) + cAcc := e.WithSigners(owner) + cAcc.InvokeFail(t, subnet.ErrSubNotExist, "delete", []byte{1, 1, 1, 1}) cAcc.Invoke(t, stackitem.Null{}, "delete", id) - cAcc.InvokeFail(t, subnet.ErrNotExist, "get", id) - cAcc.InvokeFail(t, subnet.ErrNotExist, "delete", id) + cAcc.InvokeFail(t, subnet.ErrSubNotExist, "get", id) + cAcc.InvokeFail(t, subnet.ErrSubNotExist, "delete", id) } func TestSubnet_AddNodeAdmin(t *testing.T) { e := newSubnetInvoker(t) - owner := e.NewAccount(t) - pub, ok := vm.ParseSignatureContract(owner.Script()) - require.True(t, ok) - - id := make([]byte, 4) - binary.LittleEndian.PutUint32(id, 123) - info := randomBytes(10) - - cBoth := e.WithSigners(e.Committee, owner) - cBoth.Invoke(t, stackitem.Null{}, "put", id, pub, info) + id, owner := createSubnet(t, e) adm := e.NewAccount(t) admPub, ok := vm.ParseSignatureContract(adm.Script()) require.True(t, ok) - const ( - method = "addNodeAdmin" - errSeparator = ": " - ) + const method = "addNodeAdmin" e.InvokeFail(t, method+errSeparator+subnet.ErrInvalidAdmin, method, id, admPub[1:]) - e.InvokeFail(t, method+errSeparator+subnet.ErrNotExist, method, []byte{0, 0, 0, 0}, admPub) + e.InvokeFail(t, method+errSeparator+subnet.ErrSubNotExist, method, []byte{0, 0, 0, 0}, admPub) cAdm := e.WithSigners(adm) cAdm.InvokeFail(t, method+errSeparator+"owner witness check failed", method, id, admPub) @@ -115,3 +98,48 @@ func TestSubnet_AddNodeAdmin(t *testing.T) { cOwner.InvokeFail(t, method+errSeparator+"node admin has already been added", method, id, admPub) } + +func TestSubnet_RemoveNodeAdmin(t *testing.T) { + e := newSubnetInvoker(t) + + id, owner := createSubnet(t, e) + + adm := e.NewAccount(t) + admPub, ok := vm.ParseSignatureContract(adm.Script()) + require.True(t, ok) + + const method = "removeNodeAdmin" + + e.InvokeFail(t, method+errSeparator+subnet.ErrInvalidAdmin, method, id, admPub[1:]) + e.InvokeFail(t, method+errSeparator+subnet.ErrSubNotExist, method, []byte{0, 0, 0, 0}, admPub) + + cAdm := e.WithSigners(adm) + cAdm.InvokeFail(t, method+errSeparator+"owner witness check failed", method, id, admPub) + + cOwner := e.WithSigners(owner) + + cOwner.InvokeFail(t, method+errSeparator+subnet.ErrNodeAdmNotExist, method, id, admPub) + cOwner.Invoke(t, stackitem.Null{}, "addNodeAdmin", id, admPub) + cOwner.Invoke(t, stackitem.Null{}, method, id, admPub) + cOwner.InvokeFail(t, method+errSeparator+subnet.ErrNodeAdmNotExist, method, id, admPub) +} + +func createSubnet(t *testing.T, e *neotest.ContractInvoker) (id []byte, owner neotest.Signer) { + var ( + ok bool + pub []byte + ) + + owner = e.NewAccount(t) + pub, ok = vm.ParseSignatureContract(owner.Script()) + require.True(t, ok) + + id = make([]byte, 4) + binary.LittleEndian.PutUint32(id, 123) + info := randomBytes(10) + + cBoth := e.WithSigners(e.Committee, owner) + cBoth.Invoke(t, stackitem.Null{}, "put", id, pub, info) + + return +}