From 616bb9a6898fc7ede4ed815aeaddf1283342eec9 Mon Sep 17 00:00:00 2001 From: Gustaf Kugelberg <123396602+kugel3@users.noreply.github.com> Date: Thu, 12 Oct 2023 10:03:46 +0200 Subject: [PATCH] Testing behaviors (#835) --- Package.swift | 6 +- .../GatewayAPIClient+Extension.swift | 128 +++-- .../GatewayAPITests/GatewayAPITests.swift | 12 +- .../TestVectors/behaviors1.json | 428 +++++++++++++++++ .../TestVectors/behaviors2.json | 446 ++++++++++++++++++ 5 files changed, 965 insertions(+), 55 deletions(-) create mode 100644 Tests/Clients/GatewayAPITests/TestVectors/behaviors1.json create mode 100644 Tests/Clients/GatewayAPITests/TestVectors/behaviors2.json diff --git a/Package.swift b/Package.swift index dfc1083685..d896ab3ab6 100644 --- a/Package.swift +++ b/Package.swift @@ -610,7 +610,11 @@ package.addModules([ exclude: [ "CodeGen/Input/", ], - tests: .yes(), + tests: .yes( + resources: [ + .process("TestVectors/"), + ] + ), disableConcurrencyChecks: true ), diff --git a/Sources/Clients/GatewayAPI/GatewayAPIClient+Extension.swift b/Sources/Clients/GatewayAPI/GatewayAPIClient+Extension.swift index 14c4a0fae0..c16769667b 100644 --- a/Sources/Clients/GatewayAPI/GatewayAPIClient+Extension.swift +++ b/Sources/Clients/GatewayAPI/GatewayAPIClient+Extension.swift @@ -204,89 +204,113 @@ extension GatewayAPI.EntityMetadataCollection { } } +// FIXME: This logic should not be here, will probably move to OnLedgerEntitiesClient. extension GatewayAPI.ComponentEntityRoleAssignments { - // FIXME: This logic should not be here, will probably move to OnLedgerEntitiesClient. + /** + This extracts the appropriate `AssetBehavior`s from an instance of `ComponentEntityRoleAssignments` + + __MOVEMENT BEHAVIORS__ + + For the behaviors related to movement, we first look at the current situation, using the logic under "Find performer" below, + applied to the two names `withdrawer` and `depositor`. If this results in anything other than `AllowAll`, then we add + the behavior `movementRestricted`. + + If on the other hand it turns out that movement is *not* currently restricted, we look at who can change this in the future, + by finding the updaters for `withdrawer` and `depositor`, using the logic in "Find updaters" below. If at least one of + the names has `AllowAll`, we add the `movementRestrictableInFutureByAnyone` behavior. If at least one of them has `Protected`, + we add `movementRestrictableInFuture`. + + __OTHER BEHAVIORS__ + + For the remaining behaviors the logic is as follows: + + __Find performer:__ For a given "name" (`minter`, `freezer` etc) we find the "performer", i.e. who can perform the action *currently*: + + 1. Find the first entry in `self.entries` whose `roleKey.name` corresponds to `name` + 2. Check if its `assignment` is `explicit` or points to `owner` + 3. If it's explicit, we check which rule, out of`DenyAll`, `AllowAll` and `Protected`, that is set + 4. For the `owner` case, we go to the root property `owner`, where its `rule` property should resolve to one of those three rules + + __Find updaters:__ We also find the "updater" for the name, i.e. who can *change* the performer + + 1. For the same `entry`, we look at the `updaterRoles` property, which contains a list of names + 2. For each of these names, we look up *their* corresponding entry and then the rule, like above + + __Combine result:__ For our purposes here, we don't distinguish between performers and updaters, so we consider them together + + 1. Combine the performer and all updaters into a set, removing duplicates + 2. If the set contains `AllowAll`, we add the "... by anyone" behavior + 3. If the set contains `Protected` we add the plain behavior + + At the end of all this, we check if we both `supplyIncreasable` and `.supplyDecreasable`, and if so, we replace them + with `.supplyFlexible`. We do the same check for the "by anyone" names. + + Finally, if we end up with no behaviors, we return the `.simpleAsset` behavior instead. + */ @Sendable public func extractBehaviors() -> [AssetBehavior] { typealias AssignmentEntry = GatewayAPI.ComponentEntityRoleAssignmentEntry typealias ParsedName = GatewayAPI.RoleKey.ParsedName typealias ParsedAssignment = GatewayAPI.ComponentEntityRoleAssignmentEntry.ParsedAssignment - enum Assigned { - case none, someone, anyone, unknown - - init(_ explicitAssignment: ParsedAssignment.Explicit) { - switch explicitAssignment { - case .denyAll: self = .none - case .protected: self = .someone - case .allowAll: self = .anyone - } - } - } - func findEntry(_ name: GatewayAPI.RoleKey.ParsedName) -> AssignmentEntry? { entries.first { $0.roleKey.parsedName == name } } - func resolvedOwner() -> Assigned { - guard let dict = owner.value as? [String: Any] else { return .unknown } - let rule = dict["rule"] as Any - guard let explicit = ParsedAssignment.Explicit(rule) else { return .unknown } - - return .init(explicit) + func resolvedOwner() -> ParsedAssignment.Explicit? { + guard let dict = owner.value as? [String: Any] else { return nil } + return ParsedAssignment.Explicit(dict["rule"] as Any) } - func performer(_ name: GatewayAPI.RoleKey.ParsedName) -> Assigned { - guard let parsed = findEntry(name)?.parsedAssignment else { return .unknown } - switch parsed { + func findAssigned(for parsedAssignment: ParsedAssignment) -> ParsedAssignment.Explicit? { + switch parsedAssignment { case .owner: return resolvedOwner() case let .explicit(explicit): - return .init(explicit) + return explicit } } - func updaters(_ name: GatewayAPI.RoleKey.ParsedName) -> Assigned { - guard let updaters = findEntry(name)?.updaterRoles, !updaters.isEmpty else { return .unknown } + func performer(_ name: GatewayAPI.RoleKey.ParsedName) -> ParsedAssignment.Explicit? { + guard let parsedAssignment = findEntry(name)?.parsedAssignment else { return nil } + return findAssigned(for: parsedAssignment) + } + + func updaters(_ name: GatewayAPI.RoleKey.ParsedName) -> Set { + guard let updaters = findEntry(name)?.updaterRoles, !updaters.isEmpty else { return [nil] } // Lookup the corresponding assignments, ignoring unknown and empty values - let parsed = Set(updaters.compactMap(\.parsedName).compactMap(findEntry).compactMap(\.parsedAssignment)) - - if parsed.isEmpty { - return .unknown - } else if parsed == [.explicit(.denyAll)] { - return .none - } else if parsed.contains(.explicit(.allowAll)) { - return .anyone - } else if parsed == [.owner] { - return resolvedOwner() - } else { - return .someone - } + let parsedAssignments = Set(updaters.compactMap(\.parsedName).compactMap(findEntry).compactMap(\.parsedAssignment)) + + return Set(parsedAssignments.map(findAssigned)) } var result: Set = [] - // Withdrawer and depositor areas are checked together, but we look at the performer and updater role types separately + // Other names are checked individually, but without distinguishing between the role types + func addBehavior(for rules: Set, ifSomeone: AssetBehavior, ifAnyone: AssetBehavior) { + if rules.contains(.allowAll) { + result.insert(ifAnyone) + } else if rules.contains(.protected) { + result.insert(ifSomeone) + } else if rules.contains(nil) { + loggerGlobal.warning("Failed to parse ComponentEntityRoleAssignments for \(ifSomeone)") + } + } + + // Movement behaviors: Withdrawer and depositor names are checked together, but we look + // at the performer and updater role types separately let movers: Set = [performer(.withdrawer), performer(.depositor)] - if movers != [.anyone] { + if movers != [.allowAll] { result.insert(.movementRestricted) } else { - let moverUpdaters: Set = [updaters(.withdrawer), updaters(.depositor)] - if moverUpdaters.contains(.anyone) { - result.insert(.movementRestrictableInFutureByAnyone) - } else if moverUpdaters.contains(.someone) { - result.insert(.movementRestrictableInFuture) - } + let moverUpdaters = updaters(.withdrawer).union(updaters(.depositor)) + addBehavior(for: moverUpdaters, ifSomeone: .movementRestrictableInFuture, ifAnyone: .movementRestrictableInFutureByAnyone) } // Other names are checked individually, but without distinguishing between the role types func addBehavior(for name: GatewayAPI.RoleKey.ParsedName, ifSomeone: AssetBehavior, ifAnyone: AssetBehavior) { - let either: Set = [performer(name), updaters(name)] - if either.contains(.anyone) { - result.insert(ifAnyone) - } else if either.contains(.someone) { - result.insert(ifSomeone) - } + let performersAndUpdaters = updaters(name).union([performer(name)]) + addBehavior(for: performersAndUpdaters, ifSomeone: ifSomeone, ifAnyone: ifAnyone) } addBehavior(for: .minter, ifSomeone: .supplyIncreasable, ifAnyone: .supplyIncreasableByAnyone) diff --git a/Tests/Clients/GatewayAPITests/GatewayAPITests.swift b/Tests/Clients/GatewayAPITests/GatewayAPITests.swift index e869e641c7..bde900affb 100644 --- a/Tests/Clients/GatewayAPITests/GatewayAPITests.swift +++ b/Tests/Clients/GatewayAPITests/GatewayAPITests.swift @@ -1,8 +1,16 @@ import ClientTestingPrelude @testable import GatewayAPI +import TestingPrelude final class GatewayAPITests: TestCase { - func test_trivial() { - XCTAssertTrue(true) + private func doTest(_ jsonName: String, expected: [AssetBehavior]) throws { + try testFixture(bundle: .module, jsonName: jsonName) { (assignments: GatewayAPI.ComponentEntityRoleAssignments) in + XCTAssertEqual(assignments.extractBehaviors(), expected) + } + } + + func test_behavior_extraction() throws { + try doTest("behaviors1", expected: [.supplyFlexible, .removableByThirdParty, .informationChangeable]) + try doTest("behaviors2", expected: [.supplyIncreasable, .supplyDecreasableByAnyone, .informationChangeable]) } } diff --git a/Tests/Clients/GatewayAPITests/TestVectors/behaviors1.json b/Tests/Clients/GatewayAPITests/TestVectors/behaviors1.json new file mode 100644 index 0000000000..72ec607767 --- /dev/null +++ b/Tests/Clients/GatewayAPITests/TestVectors/behaviors1.json @@ -0,0 +1,428 @@ +{ + "owner": { + "rule": { + "type": "Protected", + "access_rule": { + "type": "ProofRule", + "proof_rule": { + "type": "Require", + "requirement": { + "type": "Resource", + "resource": "resource_tdx_2_1tkckx9fynl9f7756z8wxphq7wce6vk874nuq4f2nnxgh3nzrwhjdlp" + } + } + } + }, + "updater": "Owner" + }, + "entries": [ + { + "role_key": { + "module": "Main", + "name": "burner" + }, + "assignment": { + "resolution": "Explicit", + "explicit_rule": { + "type": "Protected", + "access_rule": { + "type": "ProofRule", + "proof_rule": { + "type": "Require", + "requirement": { + "type": "NonFungible", + "non_fungible": { + "local_id": { + "id_type": "Bytes", + "sbor_hex": "5cc002209f84cfb8d12ad9bb4564e17b68ded9bdbe5e290163afce18e2573357b4b4321e", + "simple_rep": "[9f84cfb8d12ad9bb4564e17b68ded9bdbe5e290163afce18e2573357b4b4321e]" + }, + "resource_address": "resource_tdx_2_1nfxxxxxxxxxxglcllrxxxxxxxxx002350006550xxxxxxxxxqtcnwk" + } + } + } + } + } + }, + "updater_roles": [ + { + "module": "Main", + "name": "burner_updater" + } + ] + }, + { + "role_key": { + "module": "Main", + "name": "minter" + }, + "assignment": { + "resolution": "Explicit", + "explicit_rule": { + "type": "Protected", + "access_rule": { + "type": "ProofRule", + "proof_rule": { + "type": "Require", + "requirement": { + "type": "NonFungible", + "non_fungible": { + "local_id": { + "id_type": "Bytes", + "sbor_hex": "5cc002209f84cfb8d12ad9bb4564e17b68ded9bdbe5e290163afce18e2573357b4b4321e", + "simple_rep": "[9f84cfb8d12ad9bb4564e17b68ded9bdbe5e290163afce18e2573357b4b4321e]" + }, + "resource_address": "resource_tdx_2_1nfxxxxxxxxxxglcllrxxxxxxxxx002350006550xxxxxxxxxqtcnwk" + } + } + } + } + } + }, + "updater_roles": [ + { + "module": "Main", + "name": "minter_updater" + } + ] + }, + { + "role_key": { + "module": "Main", + "name": "freezer" + }, + "assignment": { + "resolution": "Explicit", + "explicit_rule": { + "type": "DenyAll" + } + }, + "updater_roles": [ + { + "module": "Main", + "name": "freezer_updater" + } + ] + }, + { + "role_key": { + "module": "Main", + "name": "recaller" + }, + "assignment": { + "resolution": "Explicit", + "explicit_rule": { + "type": "Protected", + "access_rule": { + "type": "ProofRule", + "proof_rule": { + "type": "Require", + "requirement": { + "type": "NonFungible", + "non_fungible": { + "local_id": { + "id_type": "Bytes", + "sbor_hex": "5cc002209f84cfb8d12ad9bb4564e17b68ded9bdbe5e290163afce18e2573357b4b4321e", + "simple_rep": "[9f84cfb8d12ad9bb4564e17b68ded9bdbe5e290163afce18e2573357b4b4321e]" + }, + "resource_address": "resource_tdx_2_1nfxxxxxxxxxxglcllrxxxxxxxxx002350006550xxxxxxxxxqtcnwk" + } + } + } + } + } + }, + "updater_roles": [ + { + "module": "Main", + "name": "recaller_updater" + } + ] + }, + { + "role_key": { + "module": "Main", + "name": "depositor" + }, + "assignment": { + "resolution": "Explicit", + "explicit_rule": { + "type": "AllowAll" + } + }, + "updater_roles": [ + { + "module": "Main", + "name": "depositor_updater" + } + ] + }, + { + "role_key": { + "module": "Main", + "name": "withdrawer" + }, + "assignment": { + "resolution": "Explicit", + "explicit_rule": { + "type": "AllowAll" + } + }, + "updater_roles": [ + { + "module": "Main", + "name": "withdrawer_updater" + } + ] + }, + { + "role_key": { + "module": "Main", + "name": "burner_updater" + }, + "assignment": { + "resolution": "Owner" + }, + "updater_roles": [ + { + "module": "Main", + "name": "burner_updater" + } + ] + }, + { + "role_key": { + "module": "Main", + "name": "minter_updater" + }, + "assignment": { + "resolution": "Owner" + }, + "updater_roles": [ + { + "module": "Main", + "name": "minter_updater" + } + ] + }, + { + "role_key": { + "module": "Main", + "name": "freezer_updater" + }, + "assignment": { + "resolution": "Explicit", + "explicit_rule": { + "type": "DenyAll" + } + }, + "updater_roles": [ + { + "module": "Main", + "name": "freezer_updater" + } + ] + }, + { + "role_key": { + "module": "Main", + "name": "recaller_updater" + }, + "assignment": { + "resolution": "Owner" + }, + "updater_roles": [ + { + "module": "Main", + "name": "recaller_updater" + } + ] + }, + { + "role_key": { + "module": "Main", + "name": "depositor_updater" + }, + "assignment": { + "resolution": "Explicit", + "explicit_rule": { + "type": "DenyAll" + } + }, + "updater_roles": [ + { + "module": "Main", + "name": "depositor_updater" + } + ] + }, + { + "role_key": { + "module": "Main", + "name": "withdrawer_updater" + }, + "assignment": { + "resolution": "Explicit", + "explicit_rule": { + "type": "DenyAll" + } + }, + "updater_roles": [ + { + "module": "Main", + "name": "withdrawer_updater" + } + ] + }, + { + "role_key": { + "module": "Metadata", + "name": "metadata_locker" + }, + "assignment": { + "resolution": "Owner" + }, + "updater_roles": [ + { + "module": "Metadata", + "name": "metadata_locker_updater" + } + ] + }, + { + "role_key": { + "module": "Metadata", + "name": "metadata_locker_updater" + }, + "assignment": { + "resolution": "Owner" + }, + "updater_roles": [ + { + "module": "Metadata", + "name": "metadata_locker_updater" + } + ] + }, + { + "role_key": { + "module": "Metadata", + "name": "metadata_setter" + }, + "assignment": { + "resolution": "Owner" + }, + "updater_roles": [ + { + "module": "Metadata", + "name": "metadata_setter_updater" + } + ] + }, + { + "role_key": { + "module": "Metadata", + "name": "metadata_setter_updater" + }, + "assignment": { + "resolution": "Owner" + }, + "updater_roles": [ + { + "module": "Metadata", + "name": "metadata_setter_updater" + } + ] + }, + { + "role_key": { + "module": "Royalty", + "name": "royalty_setter" + }, + "assignment": { + "resolution": "Owner" + }, + "updater_roles": [ + { + "module": "Royalty", + "name": "royalty_setter_updater" + } + ] + }, + { + "role_key": { + "module": "Royalty", + "name": "royalty_setter_updater" + }, + "assignment": { + "resolution": "Owner" + }, + "updater_roles": [ + { + "module": "Royalty", + "name": "royalty_setter_updater" + } + ] + }, + { + "role_key": { + "module": "Royalty", + "name": "royalty_locker" + }, + "assignment": { + "resolution": "Owner" + }, + "updater_roles": [ + { + "module": "Royalty", + "name": "royalty_locker_updater" + } + ] + }, + { + "role_key": { + "module": "Royalty", + "name": "royalty_locker_updater" + }, + "assignment": { + "resolution": "Owner" + }, + "updater_roles": [ + { + "module": "Royalty", + "name": "royalty_locker_updater" + } + ] + }, + { + "role_key": { + "module": "Royalty", + "name": "royalty_claimer" + }, + "assignment": { + "resolution": "Owner" + }, + "updater_roles": [ + { + "module": "Royalty", + "name": "royalty_claimer_updater" + } + ] + }, + { + "role_key": { + "module": "Royalty", + "name": "royalty_claimer_updater" + }, + "assignment": { + "resolution": "Owner" + }, + "updater_roles": [ + { + "module": "Royalty", + "name": "royalty_claimer_updater" + } + ] + } + ] +} diff --git a/Tests/Clients/GatewayAPITests/TestVectors/behaviors2.json b/Tests/Clients/GatewayAPITests/TestVectors/behaviors2.json new file mode 100644 index 0000000000..5a61912523 --- /dev/null +++ b/Tests/Clients/GatewayAPITests/TestVectors/behaviors2.json @@ -0,0 +1,446 @@ +{ + "owner": { + "rule": { + "type": "Protected", + "access_rule": { + "type": "ProofRule", + "proof_rule": { + "type": "Require", + "requirement": { + "type": "NonFungible", + "non_fungible": { + "local_id": { + "id_type": "Bytes", + "sbor_hex": "5cc0021e0de44b32f6093a1d2514ac63d7f6dddbff83b1026d7134d21fbede4d05fb", + "simple_rep": "[0de44b32f6093a1d2514ac63d7f6dddbff83b1026d7134d21fbede4d05fb]" + }, + "resource_address": "resource_tdx_2_1nfxxxxxxxxxxpkgwnrxxxxxxxxx002558553505xxxxxxxxxfzgzzk" + } + } + } + } + }, + "updater": "Owner" + }, + "entries": [ + { + "role_key": { + "module": "Main", + "name": "burner" + }, + "assignment": { + "resolution": "Explicit", + "explicit_rule": { + "type": "AllowAll" + } + }, + "updater_roles": [ + { + "module": "Main", + "name": "burner_updater" + } + ] + }, + { + "role_key": { + "module": "Main", + "name": "minter" + }, + "assignment": { + "resolution": "Explicit", + "explicit_rule": { + "type": "Protected", + "access_rule": { + "type": "ProofRule", + "proof_rule": { + "type": "Require", + "requirement": { + "type": "NonFungible", + "non_fungible": { + "local_id": { + "id_type": "Bytes", + "sbor_hex": "5cc00220d12c9530d073068636338173e8893b6d963a888412491ced590fcf1999e90056", + "simple_rep": "[d12c9530d073068636338173e8893b6d963a888412491ced590fcf1999e90056]" + }, + "resource_address": "resource_tdx_2_1nfxxxxxxxxxxglcllrxxxxxxxxx002350006550xxxxxxxxxqtcnwk" + } + } + } + } + } + }, + "updater_roles": [ + { + "module": "Main", + "name": "minter_updater" + } + ] + }, + { + "role_key": { + "module": "Main", + "name": "freezer" + }, + "assignment": { + "resolution": "Explicit", + "explicit_rule": { + "type": "DenyAll" + } + }, + "updater_roles": [ + { + "module": "Main", + "name": "freezer_updater" + } + ] + }, + { + "role_key": { + "module": "Main", + "name": "recaller" + }, + "assignment": { + "resolution": "Explicit", + "explicit_rule": { + "type": "DenyAll" + } + }, + "updater_roles": [ + { + "module": "Main", + "name": "recaller_updater" + } + ] + }, + { + "role_key": { + "module": "Main", + "name": "depositor" + }, + "assignment": { + "resolution": "Explicit", + "explicit_rule": { + "type": "AllowAll" + } + }, + "updater_roles": [ + { + "module": "Main", + "name": "depositor_updater" + } + ] + }, + { + "role_key": { + "module": "Main", + "name": "withdrawer" + }, + "assignment": { + "resolution": "Explicit", + "explicit_rule": { + "type": "AllowAll" + } + }, + "updater_roles": [ + { + "module": "Main", + "name": "withdrawer_updater" + } + ] + }, + { + "role_key": { + "module": "Main", + "name": "burner_updater" + }, + "assignment": { + "resolution": "Explicit", + "explicit_rule": { + "type": "DenyAll" + } + }, + "updater_roles": [ + { + "module": "Main", + "name": "burner_updater" + } + ] + }, + { + "role_key": { + "module": "Main", + "name": "minter_updater" + }, + "assignment": { + "resolution": "Explicit", + "explicit_rule": { + "type": "DenyAll" + } + }, + "updater_roles": [ + { + "module": "Main", + "name": "minter_updater" + } + ] + }, + { + "role_key": { + "module": "Main", + "name": "freezer_updater" + }, + "assignment": { + "resolution": "Explicit", + "explicit_rule": { + "type": "DenyAll" + } + }, + "updater_roles": [ + { + "module": "Main", + "name": "freezer_updater" + } + ] + }, + { + "role_key": { + "module": "Main", + "name": "recaller_updater" + }, + "assignment": { + "resolution": "Explicit", + "explicit_rule": { + "type": "DenyAll" + } + }, + "updater_roles": [ + { + "module": "Main", + "name": "recaller_updater" + } + ] + }, + { + "role_key": { + "module": "Main", + "name": "depositor_updater" + }, + "assignment": { + "resolution": "Explicit", + "explicit_rule": { + "type": "DenyAll" + } + }, + "updater_roles": [ + { + "module": "Main", + "name": "depositor_updater" + } + ] + }, + { + "role_key": { + "module": "Main", + "name": "withdrawer_updater" + }, + "assignment": { + "resolution": "Explicit", + "explicit_rule": { + "type": "DenyAll" + } + }, + "updater_roles": [ + { + "module": "Main", + "name": "withdrawer_updater" + } + ] + }, + { + "role_key": { + "module": "Main", + "name": "non_fungible_data_updater" + }, + "assignment": { + "resolution": "Explicit", + "explicit_rule": { + "type": "DenyAll" + } + }, + "updater_roles": [ + { + "module": "Main", + "name": "non_fungible_data_updater_updater" + } + ] + }, + { + "role_key": { + "module": "Main", + "name": "non_fungible_data_updater_updater" + }, + "assignment": { + "resolution": "Explicit", + "explicit_rule": { + "type": "DenyAll" + } + }, + "updater_roles": [ + { + "module": "Main", + "name": "non_fungible_data_updater_updater" + } + ] + }, + { + "role_key": { + "module": "Metadata", + "name": "metadata_locker" + }, + "assignment": { + "resolution": "Owner" + }, + "updater_roles": [ + { + "module": "Metadata", + "name": "metadata_locker_updater" + } + ] + }, + { + "role_key": { + "module": "Metadata", + "name": "metadata_locker_updater" + }, + "assignment": { + "resolution": "Owner" + }, + "updater_roles": [ + { + "module": "Metadata", + "name": "metadata_locker_updater" + } + ] + }, + { + "role_key": { + "module": "Metadata", + "name": "metadata_setter" + }, + "assignment": { + "resolution": "Owner" + }, + "updater_roles": [ + { + "module": "Metadata", + "name": "metadata_setter_updater" + } + ] + }, + { + "role_key": { + "module": "Metadata", + "name": "metadata_setter_updater" + }, + "assignment": { + "resolution": "Owner" + }, + "updater_roles": [ + { + "module": "Metadata", + "name": "metadata_setter_updater" + } + ] + }, + { + "role_key": { + "module": "Royalty", + "name": "royalty_setter" + }, + "assignment": { + "resolution": "Owner" + }, + "updater_roles": [ + { + "module": "Royalty", + "name": "royalty_setter_updater" + } + ] + }, + { + "role_key": { + "module": "Royalty", + "name": "royalty_setter_updater" + }, + "assignment": { + "resolution": "Owner" + }, + "updater_roles": [ + { + "module": "Royalty", + "name": "royalty_setter_updater" + } + ] + }, + { + "role_key": { + "module": "Royalty", + "name": "royalty_locker" + }, + "assignment": { + "resolution": "Owner" + }, + "updater_roles": [ + { + "module": "Royalty", + "name": "royalty_locker_updater" + } + ] + }, + { + "role_key": { + "module": "Royalty", + "name": "royalty_locker_updater" + }, + "assignment": { + "resolution": "Owner" + }, + "updater_roles": [ + { + "module": "Royalty", + "name": "royalty_locker_updater" + } + ] + }, + { + "role_key": { + "module": "Royalty", + "name": "royalty_claimer" + }, + "assignment": { + "resolution": "Owner" + }, + "updater_roles": [ + { + "module": "Royalty", + "name": "royalty_claimer_updater" + } + ] + }, + { + "role_key": { + "module": "Royalty", + "name": "royalty_claimer_updater" + }, + "assignment": { + "resolution": "Owner" + }, + "updater_roles": [ + { + "module": "Royalty", + "name": "royalty_claimer_updater" + } + ] + } + ] +}