From af1ea67e3dfe9b26c16e6228bb2d415071673c86 Mon Sep 17 00:00:00 2001 From: dastansam Date: Fri, 13 Oct 2023 17:06:51 +0300 Subject: [PATCH 1/8] Consistent collection attribute namespace --- substrate/frame/nfts/src/impl_nonfungibles.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/substrate/frame/nfts/src/impl_nonfungibles.rs b/substrate/frame/nfts/src/impl_nonfungibles.rs index 4e2593b4057d..504ff13ab8db 100644 --- a/substrate/frame/nfts/src/impl_nonfungibles.rs +++ b/substrate/frame/nfts/src/impl_nonfungibles.rs @@ -106,7 +106,7 @@ impl, I: 'static> Inspect<::AccountId> for Palle Attribute::::get(( collection, Option::::None, - AttributeNamespace::CollectionOwner, + AttributeNamespace::Pallet, key, )) .map(|a| a.0.into()) From e2dfa29dda945c183d6a2cb65b31dce85cc1d681 Mon Sep 17 00:00:00 2001 From: dastansam Date: Tue, 17 Oct 2023 14:27:49 +0300 Subject: [PATCH 2/8] System attribute collection level --- .../runtimes/assets/asset-hub-westend/src/lib.rs | 4 ++-- substrate/bin/node/runtime/src/lib.rs | 4 ++-- substrate/frame/nfts/runtime-api/src/lib.rs | 2 +- substrate/frame/nfts/src/impl_nonfungibles.rs | 6 +++--- .../support/src/traits/tokens/nonfungible_v2.rs | 14 ++++++++++---- .../support/src/traits/tokens/nonfungibles_v2.rs | 4 ++-- 6 files changed, 20 insertions(+), 14 deletions(-) diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs index 943332087627..b7665aeb35c6 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs @@ -1065,10 +1065,10 @@ impl_runtime_apis! { fn system_attribute( collection: u32, - item: u32, + item: Option, key: Vec, ) -> Option> { - >::system_attribute(&collection, &item, &key) + >::system_attribute(&collection, item.as_ref(), &key) } fn collection_attribute(collection: u32, key: Vec) -> Option> { diff --git a/substrate/bin/node/runtime/src/lib.rs b/substrate/bin/node/runtime/src/lib.rs index 2070e3f12d04..6c04c79b8cdb 100644 --- a/substrate/bin/node/runtime/src/lib.rs +++ b/substrate/bin/node/runtime/src/lib.rs @@ -2627,10 +2627,10 @@ impl_runtime_apis! { fn system_attribute( collection: u32, - item: u32, + item: Option, key: Vec, ) -> Option> { - >::system_attribute(&collection, &item, &key) + >::system_attribute(&collection, item.as_ref(), &key) } fn collection_attribute(collection: u32, key: Vec) -> Option> { diff --git a/substrate/frame/nfts/runtime-api/src/lib.rs b/substrate/frame/nfts/runtime-api/src/lib.rs index cf2d444b42f8..77535c64069c 100644 --- a/substrate/frame/nfts/runtime-api/src/lib.rs +++ b/substrate/frame/nfts/runtime-api/src/lib.rs @@ -48,7 +48,7 @@ sp_api::decl_runtime_apis! { fn system_attribute( collection: CollectionId, - item: ItemId, + item: Option, key: Vec, ) -> Option>; diff --git a/substrate/frame/nfts/src/impl_nonfungibles.rs b/substrate/frame/nfts/src/impl_nonfungibles.rs index 504ff13ab8db..f33c651c08f5 100644 --- a/substrate/frame/nfts/src/impl_nonfungibles.rs +++ b/substrate/frame/nfts/src/impl_nonfungibles.rs @@ -84,12 +84,12 @@ impl, I: 'static> Inspect<::AccountId> for Palle /// By default this is `None`; no attributes are defined. fn system_attribute( collection: &Self::CollectionId, - item: &Self::ItemId, + item: Option<&Self::ItemId>, key: &[u8], ) -> Option> { let namespace = AttributeNamespace::Pallet; let key = BoundedSlice::<_, _>::try_from(key).ok()?; - Attribute::::get((collection, Some(item), namespace, key)).map(|a| a.0.into()) + Attribute::::get((collection, item, namespace, key)).map(|a| a.0.into()) } /// Returns the attribute value of `item` of `collection` corresponding to `key`. @@ -106,7 +106,7 @@ impl, I: 'static> Inspect<::AccountId> for Palle Attribute::::get(( collection, Option::::None, - AttributeNamespace::Pallet, + AttributeNamespace::CollectionOwner, key, )) .map(|a| a.0.into()) diff --git a/substrate/frame/support/src/traits/tokens/nonfungible_v2.rs b/substrate/frame/support/src/traits/tokens/nonfungible_v2.rs index c4463e0070f9..aed9d19ade16 100644 --- a/substrate/frame/support/src/traits/tokens/nonfungible_v2.rs +++ b/substrate/frame/support/src/traits/tokens/nonfungible_v2.rs @@ -63,7 +63,7 @@ pub trait Inspect { /// Returns the system attribute value of `item` corresponding to `key`. /// /// By default this is `None`; no attributes are defined. - fn system_attribute(_item: &Self::ItemId, _key: &[u8]) -> Option> { + fn system_attribute(_item: Option<&Self::ItemId>, _key: &[u8]) -> Option> { None } @@ -90,7 +90,10 @@ pub trait Inspect { /// Returns the strongly-typed system attribute value of `item` corresponding to `key`. /// /// By default this just attempts to use `system_attribute`. - fn typed_system_attribute(item: &Self::ItemId, key: &K) -> Option { + fn typed_system_attribute( + item: Option<&Self::ItemId>, + key: &K, + ) -> Option { key.using_encoded(|d| Self::system_attribute(item, d)) .and_then(|v| V::decode(&mut &v[..]).ok()) } @@ -211,7 +214,7 @@ impl< fn custom_attribute(account: &AccountId, item: &Self::ItemId, key: &[u8]) -> Option> { >::custom_attribute(account, &A::get(), item, key) } - fn system_attribute(item: &Self::ItemId, key: &[u8]) -> Option> { + fn system_attribute(item: Option<&Self::ItemId>, key: &[u8]) -> Option> { >::system_attribute(&A::get(), item, key) } fn typed_attribute(item: &Self::ItemId, key: &K) -> Option { @@ -229,7 +232,10 @@ impl< key, ) } - fn typed_system_attribute(item: &Self::ItemId, key: &K) -> Option { + fn typed_system_attribute( + item: Option<&Self::ItemId>, + key: &K, + ) -> Option { >::typed_system_attribute(&A::get(), item, key) } fn can_transfer(item: &Self::ItemId) -> bool { diff --git a/substrate/frame/support/src/traits/tokens/nonfungibles_v2.rs b/substrate/frame/support/src/traits/tokens/nonfungibles_v2.rs index ec064bdebf62..60e828cfd880 100644 --- a/substrate/frame/support/src/traits/tokens/nonfungibles_v2.rs +++ b/substrate/frame/support/src/traits/tokens/nonfungibles_v2.rs @@ -80,7 +80,7 @@ pub trait Inspect { /// By default this is `None`; no attributes are defined. fn system_attribute( _collection: &Self::CollectionId, - _item: &Self::ItemId, + _item: Option<&Self::ItemId>, _key: &[u8], ) -> Option> { None @@ -119,7 +119,7 @@ pub trait Inspect { /// By default this just attempts to use `system_attribute`. fn typed_system_attribute( collection: &Self::CollectionId, - item: &Self::ItemId, + item: Option<&Self::ItemId>, key: &K, ) -> Option { key.using_encoded(|d| Self::system_attribute(collection, item, d)) From fe31bb149f19d5a6c6c04548e5bdfdf1d23a53a8 Mon Sep 17 00:00:00 2001 From: dastansam Date: Tue, 17 Oct 2023 17:27:28 +0300 Subject: [PATCH 3/8] Update comments --- substrate/frame/nfts/src/impl_nonfungibles.rs | 4 +++- .../frame/support/src/traits/tokens/nonfungible_v2.rs | 8 ++++++-- .../frame/support/src/traits/tokens/nonfungibles_v2.rs | 9 ++++++--- 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/substrate/frame/nfts/src/impl_nonfungibles.rs b/substrate/frame/nfts/src/impl_nonfungibles.rs index f33c651c08f5..fd58d91e229c 100644 --- a/substrate/frame/nfts/src/impl_nonfungibles.rs +++ b/substrate/frame/nfts/src/impl_nonfungibles.rs @@ -79,7 +79,9 @@ impl, I: 'static> Inspect<::AccountId> for Palle Attribute::::get((collection, Some(item), namespace, key)).map(|a| a.0.into()) } - /// Returns the system attribute value of `item` of `collection` corresponding to `key`. + /// Returns the system attribute value of `item` of `collection` corresponding to `key` if + /// `item` is `Some`. Otherwise, returns the system attribute value of `collection` + /// corresponding to `key`. /// /// By default this is `None`; no attributes are defined. fn system_attribute( diff --git a/substrate/frame/support/src/traits/tokens/nonfungible_v2.rs b/substrate/frame/support/src/traits/tokens/nonfungible_v2.rs index aed9d19ade16..d14d532651a5 100644 --- a/substrate/frame/support/src/traits/tokens/nonfungible_v2.rs +++ b/substrate/frame/support/src/traits/tokens/nonfungible_v2.rs @@ -60,7 +60,9 @@ pub trait Inspect { None } - /// Returns the system attribute value of `item` corresponding to `key`. + /// Returns the system attribute value of `item` of `collection` corresponding to `key` if + /// `item` is `Some`. Otherwise, returns the system attribute value of `collection` + /// corresponding to `key`. /// /// By default this is `None`; no attributes are defined. fn system_attribute(_item: Option<&Self::ItemId>, _key: &[u8]) -> Option> { @@ -87,7 +89,9 @@ pub trait Inspect { .and_then(|v| V::decode(&mut &v[..]).ok()) } - /// Returns the strongly-typed system attribute value of `item` corresponding to `key`. + /// Returns the strongly-typed system attribute value of `item` corresponding to `key` if + /// `item` is `Some`. Otherwise, returns the strongly-typed system attribute value of + /// `collection` corresponding to `key`. /// /// By default this just attempts to use `system_attribute`. fn typed_system_attribute( diff --git a/substrate/frame/support/src/traits/tokens/nonfungibles_v2.rs b/substrate/frame/support/src/traits/tokens/nonfungibles_v2.rs index 60e828cfd880..a9ac8f21f836 100644 --- a/substrate/frame/support/src/traits/tokens/nonfungibles_v2.rs +++ b/substrate/frame/support/src/traits/tokens/nonfungibles_v2.rs @@ -75,7 +75,9 @@ pub trait Inspect { None } - /// Returns the system attribute value of `item` of `collection` corresponding to `key`. + /// Returns the system attribute value of `item` of `collection` corresponding to `key` if + /// `item` is `Some`. Otherwise, returns the system attribute value of `collection` + /// corresponding to `key`. /// /// By default this is `None`; no attributes are defined. fn system_attribute( @@ -113,8 +115,9 @@ pub trait Inspect { .and_then(|v| V::decode(&mut &v[..]).ok()) } - /// Returns the strongly-typed system attribute value of `item` of `collection` corresponding to - /// `key`. + /// Returns the strongly-typed system attribute value of `item` corresponding to `key` if + /// `item` is `Some`. Otherwise, returns the strongly-typed system attribute value of + /// `collection` corresponding to `key`. /// /// By default this just attempts to use `system_attribute`. fn typed_system_attribute( From d0523362ae4dbd612434b7085125a659afc871f5 Mon Sep 17 00:00:00 2001 From: dastansam Date: Mon, 23 Oct 2023 14:09:57 +0300 Subject: [PATCH 4/8] Revert nonfungible_v2 changes --- .../src/traits/tokens/nonfungible_v2.rs | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/substrate/frame/support/src/traits/tokens/nonfungible_v2.rs b/substrate/frame/support/src/traits/tokens/nonfungible_v2.rs index d14d532651a5..0a8c80690aab 100644 --- a/substrate/frame/support/src/traits/tokens/nonfungible_v2.rs +++ b/substrate/frame/support/src/traits/tokens/nonfungible_v2.rs @@ -65,7 +65,7 @@ pub trait Inspect { /// corresponding to `key`. /// /// By default this is `None`; no attributes are defined. - fn system_attribute(_item: Option<&Self::ItemId>, _key: &[u8]) -> Option> { + fn system_attribute(_item: &Self::ItemId, _key: &[u8]) -> Option> { None } @@ -94,10 +94,7 @@ pub trait Inspect { /// `collection` corresponding to `key`. /// /// By default this just attempts to use `system_attribute`. - fn typed_system_attribute( - item: Option<&Self::ItemId>, - key: &K, - ) -> Option { + fn typed_system_attribute(item: &Self::ItemId, key: &K) -> Option { key.using_encoded(|d| Self::system_attribute(item, d)) .and_then(|v| V::decode(&mut &v[..]).ok()) } @@ -218,8 +215,8 @@ impl< fn custom_attribute(account: &AccountId, item: &Self::ItemId, key: &[u8]) -> Option> { >::custom_attribute(account, &A::get(), item, key) } - fn system_attribute(item: Option<&Self::ItemId>, key: &[u8]) -> Option> { - >::system_attribute(&A::get(), item, key) + fn system_attribute(item: &Self::ItemId, key: &[u8]) -> Option> { + >::system_attribute(&A::get(), Some(item), key) } fn typed_attribute(item: &Self::ItemId, key: &K) -> Option { >::typed_attribute(&A::get(), item, key) @@ -236,11 +233,8 @@ impl< key, ) } - fn typed_system_attribute( - item: Option<&Self::ItemId>, - key: &K, - ) -> Option { - >::typed_system_attribute(&A::get(), item, key) + fn typed_system_attribute(item: &Self::ItemId, key: &K) -> Option { + >::typed_system_attribute(&A::get(), Some(item), key) } fn can_transfer(item: &Self::ItemId) -> bool { >::can_transfer(&A::get(), item) From 15a5254ca8b40d1d662cc6a0df032b5e473507b1 Mon Sep 17 00:00:00 2001 From: dastansam Date: Mon, 23 Oct 2023 14:12:37 +0300 Subject: [PATCH 5/8] More reverts --- .../src/traits/tokens/nonfungible_v2.rs | 24 +++++++++++++------ 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/substrate/frame/support/src/traits/tokens/nonfungible_v2.rs b/substrate/frame/support/src/traits/tokens/nonfungible_v2.rs index 0a8c80690aab..05f76e2859d2 100644 --- a/substrate/frame/support/src/traits/tokens/nonfungible_v2.rs +++ b/substrate/frame/support/src/traits/tokens/nonfungible_v2.rs @@ -60,9 +60,7 @@ pub trait Inspect { None } - /// Returns the system attribute value of `item` of `collection` corresponding to `key` if - /// `item` is `Some`. Otherwise, returns the system attribute value of `collection` - /// corresponding to `key`. + /// Returns the system attribute value of `item` corresponding to `key`. /// /// By default this is `None`; no attributes are defined. fn system_attribute(_item: &Self::ItemId, _key: &[u8]) -> Option> { @@ -89,9 +87,7 @@ pub trait Inspect { .and_then(|v| V::decode(&mut &v[..]).ok()) } - /// Returns the strongly-typed system attribute value of `item` corresponding to `key` if - /// `item` is `Some`. Otherwise, returns the strongly-typed system attribute value of - /// `collection` corresponding to `key`. + /// Returns the strongly-typed system attribute value of `item` corresponding to `key`. /// /// By default this just attempts to use `system_attribute`. fn typed_system_attribute(item: &Self::ItemId, key: &K) -> Option { @@ -123,7 +119,7 @@ pub trait InspectEnumerable: Inspect { } /// Trait for providing an interface for NFT-like items which may be minted, burned and/or have -/// attributes set on them. +/// attributes and metadata set on them. pub trait Mutate: Inspect { /// Mint some `item` to be owned by `who`. /// @@ -162,6 +158,13 @@ pub trait Mutate: Inspect { key.using_encoded(|k| value.using_encoded(|v| Self::set_attribute(item, k, v))) } + /// Set the metadata `data` of an `item`. + /// + /// By default, this is not a supported operation. + fn set_metadata(_who: &AccountId, _item: &Self::ItemId, _data: &[u8]) -> DispatchResult { + Err(TokenError::Unsupported.into()) + } + /// Clear attribute of `item`'s `key`. /// /// By default, this is not a supported operation. @@ -175,6 +178,13 @@ pub trait Mutate: Inspect { fn clear_typed_attribute(item: &Self::ItemId, key: &K) -> DispatchResult { key.using_encoded(|k| Self::clear_attribute(item, k)) } + + /// Clear the metadata of an `item`. + /// + /// By default, this is not a supported operation. + fn clear_metadata(_who: &AccountId, _item: &Self::ItemId) -> DispatchResult { + Err(TokenError::Unsupported.into()) + } } /// Trait for transferring and controlling the transfer of non-fungible sets of items. From 7774e765812b969134c9bfc36be6c32be2b5f492 Mon Sep 17 00:00:00 2001 From: dastansam Date: Tue, 24 Oct 2023 18:32:01 +0300 Subject: [PATCH 6/8] Add tests --- substrate/frame/nfts/src/tests.rs | 81 +++++++++++++++++++++++++++++++ 1 file changed, 81 insertions(+) diff --git a/substrate/frame/nfts/src/tests.rs b/substrate/frame/nfts/src/tests.rs index a82fcca01512..e9d4ccff5ee6 100644 --- a/substrate/frame/nfts/src/tests.rs +++ b/substrate/frame/nfts/src/tests.rs @@ -22,6 +22,7 @@ use enumflags2::BitFlags; use frame_support::{ assert_noop, assert_ok, traits::{ + nonfungibles_v2::Inspect, tokens::nonfungibles_v2::{Create, Destroy, Mutate}, Currency, Get, }, @@ -982,6 +983,86 @@ fn set_collection_owner_attributes_should_work() { }); } +#[test] +fn set_collection_system_attributes_should_work() { + new_test_ext().execute_with(|| { + Balances::make_free_balance_be(&account(1), 100); + + assert_ok!(Nfts::force_create( + RuntimeOrigin::root(), + account(1), + collection_config_with_all_settings_enabled() + )); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(account(1)), 0, 0, account(1), None)); + + let attribute_key = [0u8]; + let attribute_value = [0u8]; + + assert_ok!(, ItemConfig>>::set_collection_attribute( + &0, + &attribute_key, + &attribute_value + )); + + assert_eq!(attributes(0), vec![(None, AttributeNamespace::Pallet, bvec![0], bvec![0])]); + + assert_eq!( + >>::system_attribute(&0, None, &attribute_key), + Some(attribute_value.to_vec()) + ); + + // test typed system attribute + let typed_attribute_key = [0u8; 32]; + + #[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug)] + struct TypedAttributeValue(u32); + let typed_attribute_value = TypedAttributeValue(42); + + assert_ok!( + , ItemConfig>>::set_typed_collection_attribute( + &0, + &typed_attribute_key, + &typed_attribute_value + ) + ); + + assert_eq!( + >>::typed_system_attribute( + &0, + None, + &typed_attribute_key + ), + Some(typed_attribute_value) + ); + + // check storage + assert_eq!( + attributes(0), + [ + (None, AttributeNamespace::Pallet, bvec![0], bvec![0]), + ( + None, + AttributeNamespace::Pallet, + bvec![ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0 + ], + bvec![42, 0, 0, 0] + ) + ] + ); + + // destroy collection + assert_ok!(Nfts::burn(RuntimeOrigin::root(), 0, 0)); + + let w = Nfts::get_destroy_witness(&0).unwrap(); + + assert_ok!(Nfts::destroy(RuntimeOrigin::signed(account(1)), 0, w)); + + assert_eq!(attributes(0), vec![]); + }) +} + #[test] fn set_item_owner_attributes_should_work() { new_test_ext().execute_with(|| { From 7ed21293714eccc5ced7cf93dc43d07da482e9d4 Mon Sep 17 00:00:00 2001 From: dastansam Date: Tue, 24 Oct 2023 18:40:59 +0300 Subject: [PATCH 7/8] Extract collection_id in tests --- substrate/frame/nfts/src/tests.rs | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/substrate/frame/nfts/src/tests.rs b/substrate/frame/nfts/src/tests.rs index e9d4ccff5ee6..74c89debe533 100644 --- a/substrate/frame/nfts/src/tests.rs +++ b/substrate/frame/nfts/src/tests.rs @@ -995,11 +995,12 @@ fn set_collection_system_attributes_should_work() { )); assert_ok!(Nfts::mint(RuntimeOrigin::signed(account(1)), 0, 0, account(1), None)); + let collection_id = 0; let attribute_key = [0u8]; let attribute_value = [0u8]; assert_ok!(, ItemConfig>>::set_collection_attribute( - &0, + &collection_id, &attribute_key, &attribute_value )); @@ -1007,20 +1008,23 @@ fn set_collection_system_attributes_should_work() { assert_eq!(attributes(0), vec![(None, AttributeNamespace::Pallet, bvec![0], bvec![0])]); assert_eq!( - >>::system_attribute(&0, None, &attribute_key), + >>::system_attribute( + &collection_id, + None, + &attribute_key + ), Some(attribute_value.to_vec()) ); // test typed system attribute let typed_attribute_key = [0u8; 32]; - #[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug)] struct TypedAttributeValue(u32); let typed_attribute_value = TypedAttributeValue(42); assert_ok!( , ItemConfig>>::set_typed_collection_attribute( - &0, + &collection_id, &typed_attribute_key, &typed_attribute_value ) @@ -1028,7 +1032,7 @@ fn set_collection_system_attributes_should_work() { assert_eq!( >>::typed_system_attribute( - &0, + &collection_id, None, &typed_attribute_key ), @@ -1037,7 +1041,7 @@ fn set_collection_system_attributes_should_work() { // check storage assert_eq!( - attributes(0), + attributes(collection_id), [ (None, AttributeNamespace::Pallet, bvec![0], bvec![0]), ( @@ -1052,14 +1056,10 @@ fn set_collection_system_attributes_should_work() { ] ); - // destroy collection - assert_ok!(Nfts::burn(RuntimeOrigin::root(), 0, 0)); - + assert_ok!(Nfts::burn(RuntimeOrigin::root(), collection_id, 0)); let w = Nfts::get_destroy_witness(&0).unwrap(); - - assert_ok!(Nfts::destroy(RuntimeOrigin::signed(account(1)), 0, w)); - - assert_eq!(attributes(0), vec![]); + assert_ok!(Nfts::destroy(RuntimeOrigin::signed(account(1)), collection_id, w)); + assert_eq!(attributes(collection_id), vec![]); }) } From 77420ffac0a8b867c507d62d5bf602436c6b118d Mon Sep 17 00:00:00 2001 From: dastansam Date: Tue, 24 Oct 2023 18:46:48 +0300 Subject: [PATCH 8/8] Fix import --- substrate/frame/nfts/src/tests.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/substrate/frame/nfts/src/tests.rs b/substrate/frame/nfts/src/tests.rs index 74c89debe533..aeebf51b7c78 100644 --- a/substrate/frame/nfts/src/tests.rs +++ b/substrate/frame/nfts/src/tests.rs @@ -22,8 +22,7 @@ use enumflags2::BitFlags; use frame_support::{ assert_noop, assert_ok, traits::{ - nonfungibles_v2::Inspect, - tokens::nonfungibles_v2::{Create, Destroy, Mutate}, + tokens::nonfungibles_v2::{Create, Destroy, Inspect, Mutate}, Currency, Get, }, };