package policy import ( "git.frostfs.info/TrueCloudLab/frostfs-contract/common" "github.com/nspcc-dev/neo-go/pkg/interop" "github.com/nspcc-dev/neo-go/pkg/interop/iterator" "github.com/nspcc-dev/neo-go/pkg/interop/native/management" "github.com/nspcc-dev/neo-go/pkg/interop/runtime" "github.com/nspcc-dev/neo-go/pkg/interop/storage" ) // Kind represents the object the chain is attached to. // Currently only namespace and container are supported. type Kind byte const ( Namespace = 'n' Container = 'c' User = 'u' Group = 'g' IAM = 'i' ) const ( ownerKeyPrefix = 'o' ) const ( mappingKeyPrefix = 'm' counterKey = "Counter" ) const ( // ErrNotAuthorized is returned when the none of the transaction signers // belongs to the list of autorized keys. ErrNotAuthorized = "none of the signers is authorized to change the contract" ) // _deploy function sets up initial list of inner ring public keys. func _deploy(data any, isUpdate bool) { if isUpdate { args := data.([]any) common.CheckVersion(args[len(args)-1].(int)) return } args := data.(struct { Admin interop.Hash160 }) ctx := storage.GetContext() if args.Admin != nil { if len(args.Admin) != 20 { panic("invaliad admin hash length") } storage.Put(ctx, []byte{ownerKeyPrefix}, args.Admin) } storage.Put(ctx, counterKey, 0) } func checkAuthorization(ctx storage.Context) { admin := getAdmin(ctx) if admin != nil && runtime.CheckWitness(admin) { return } if runtime.CheckWitness(common.AlphabetAddress()) { return } panic(ErrNotAuthorized) } // Version returns the version of the contract. func Version() int { return common.Version } // Update method updates contract source code and manifest. It can be invoked // by committee only. func Update(script []byte, manifest []byte, data any) { if !common.HasUpdateAccess() { panic("only committee can update contract") } management.UpdateWithData(script, manifest, common.AppendVersion(data)) runtime.Log("policy contract updated") } func SetAdmin(addr interop.Hash160) { common.CheckAlphabetWitness() ctx := storage.GetContext() storage.Put(ctx, []byte{ownerKeyPrefix}, addr) } func GetAdmin() interop.Hash160 { ctx := storage.GetReadOnlyContext() return getAdmin(ctx) } func getAdmin(ctx storage.Context) interop.Hash160 { return storage.Get(ctx, []byte{ownerKeyPrefix}).(interop.Hash160) } func storageKey(prefix Kind, counter int, name []byte) []byte { key := append([]byte{byte(prefix)}, common.ToFixedWidth64(counter)...) return append(key, name...) } func mapKey(kind Kind, name []byte) []byte { return append([]byte{mappingKeyPrefix, byte(kind)}, name...) } // mapToNumeric maps a name to a number. That allows to keep more space in // a storage key shortening long names. Short entity // names are also mapped to prevent collisions in the map. func mapToNumeric(ctx storage.Context, kind Kind, name []byte) (mapped int, mappingExists bool) { mKey := mapKey(kind, name) numericID := storage.Get(ctx, mKey) if numericID == nil { return 0, false } mapped = numericID.(int) mappingExists = true return } // mapToNumericCreateIfNotExists maps a name to a number. That allows to keep // more space in a storage key shortening long names. Short entity // names are also mapped to prevent collisions in the map. // If a mapping cannot be found, then the method creates and returns it. // mapToNumericCreateIfNotExists is NOT applicable for a read-only context. func mapToNumericCreateIfNotExists(ctx storage.Context, kind Kind, name []byte) int { mKey := mapKey(kind, name) numericID := storage.Get(ctx, mKey) if numericID == nil { counter := storage.Get(ctx, counterKey).(int) counter++ storage.Put(ctx, counterKey, counter) storage.Put(ctx, mKey, counter) return counter } return numericID.(int) } func AddChain(entity Kind, entityName string, name []byte, chain []byte) { ctx := storage.GetContext() checkAuthorization(ctx) entityNameBytes := mapToNumericCreateIfNotExists(ctx, entity, []byte(entityName)) key := storageKey(entity, entityNameBytes, name) storage.Put(ctx, key, chain) } func GetChain(entity Kind, entityName string, name []byte) []byte { ctx := storage.GetReadOnlyContext() entityNameBytes, exists := mapToNumeric(ctx, entity, []byte(entityName)) if !exists { panic("not found") } key := storageKey(entity, entityNameBytes, name) data := storage.Get(ctx, key).([]byte) if data == nil { panic("not found") } return data } func RemoveChain(entity Kind, entityName string, name []byte) { ctx := storage.GetContext() checkAuthorization(ctx) entityNameNum, exists := mapToNumeric(ctx, entity, []byte(entityName)) if !exists { return } key := storageKey(entity, entityNameNum, name) storage.Delete(ctx, key) // If no chains are left for the target, then remove the mapping. prefix := append([]byte{byte(entity)}, common.ToFixedWidth64(entityNameNum)...) it := storage.Find(ctx, prefix, storage.KeysOnly) if !iterator.Next(it) { storage.Delete(ctx, mapKey(entity, []byte(entityName))) } } func RemoveChainsByPrefix(entity Kind, entityName string, name []byte) { ctx := storage.GetContext() checkAuthorization(ctx) entityNameNum, exists := mapToNumeric(ctx, entity, []byte(entityName)) if !exists { return } key := storageKey(entity, entityNameNum, name) it := storage.Find(ctx, key, storage.KeysOnly) for iterator.Next(it) { storage.Delete(ctx, iterator.Value(it).([]byte)) } // If no chains are left for the target, then remove the mapping. prefix := append([]byte{byte(entity)}, common.ToFixedWidth64(entityNameNum)...) it = storage.Find(ctx, prefix, storage.KeysOnly) if !iterator.Next(it) { storage.Delete(ctx, mapKey(entity, []byte(entityName))) } } // ListChains lists all chains for the namespace by prefix. // container may be empty. func ListChains(namespace, container string, name []byte) [][]byte { result := ListChainsByPrefix(Namespace, namespace, name) if container != "" { result = append(result, ListChainsByPrefix(Container, container, name)...) } return result } // ListChainsByPrefix list all chains for the provided kind and entity by prefix. func ListChainsByPrefix(entity Kind, entityName string, prefix []byte) [][]byte { ctx := storage.GetReadOnlyContext() result := [][]byte{} entityNameBytes, exists := mapToNumeric(ctx, entity, []byte(entityName)) if !exists { return result } keyPrefix := storageKey(entity, entityNameBytes, prefix) it := storage.Find(ctx, keyPrefix, storage.ValuesOnly) for iterator.Next(it) { result = append(result, iterator.Value(it).([]byte)) } return result } func IteratorChainsByPrefix(entity Kind, entityName string, prefix []byte) iterator.Iterator { ctx := storage.GetReadOnlyContext() id, _ := mapToNumeric(ctx, entity, []byte(entityName)) keyPrefix := storageKey(entity, id, prefix) return storage.Find(ctx, keyPrefix, storage.ValuesOnly) } // ListTargets iterates over targets for which rules are defined. func ListTargets(entity Kind) iterator.Iterator { ctx := storage.GetReadOnlyContext() mKey := mapKey(entity, []byte{}) return storage.Find(ctx, mKey, storage.KeysOnly|storage.RemovePrefix) } // ListChainNames iterates over chain names for specific target. func ListChainNames(entity Kind, entityName string) iterator.Iterator { ctx := storage.GetReadOnlyContext() id, _ := mapToNumeric(ctx, entity, []byte(entityName)) keyPrefix := storageKey(entity, id, []byte{}) return storage.Find(ctx, keyPrefix, storage.KeysOnly|storage.RemovePrefix) }