difftreelog
CORE-390 Add read only flag
in: master
8 files changed
pallets/common/src/erc.rsdiffbeforeafterboth87 check_is_owner(caller, self)?;87 check_is_owner(caller, self)?;888889 let sponsor = T::CrossAccountId::from_eth(sponsor);89 let sponsor = T::CrossAccountId::from_eth(sponsor);90 self.set_sponsor(sponsor.as_sub().clone());90 self.set_sponsor(sponsor.as_sub().clone()).map_err(dispatch_to_evm::<T>)?;91 save(self);91 save(self)92 Ok(())93 }92 }949395 fn confirm_collection_sponsorship(&mut self, caller: caller) -> Result<void> {94 fn confirm_collection_sponsorship(&mut self, caller: caller) -> Result<void> {96 let caller = T::CrossAccountId::from_eth(caller);95 let caller = T::CrossAccountId::from_eth(caller);97 if !self.confirm_sponsorship(caller.as_sub()) {96 if !self.confirm_sponsorship(caller.as_sub()).map_err(dispatch_to_evm::<T>)? {98 return Err(Error::Revert("Caller is not set as sponsor".into()));97 return Err(Error::Revert("Caller is not set as sponsor".into()));99 }98 }100 save(self);99 save(self)101 Ok(())102 }100 }103101104 #[solidity(rename_selector = "setCollectionLimit")]102 #[solidity(rename_selector = "setCollectionLimit")]134 }132 }135 self.limits = <Pallet<T>>::clamp_limits(self.mode.clone(), &self.limits, limits)133 self.limits = <Pallet<T>>::clamp_limits(self.mode.clone(), &self.limits, limits)136 .map_err(dispatch_to_evm::<T>)?;134 .map_err(dispatch_to_evm::<T>)?;137 save(self);135 save(self)138 Ok(())139 }136 }140137141 #[solidity(rename_selector = "setCollectionLimit")]138 #[solidity(rename_selector = "setCollectionLimit")]162 }159 }163 self.limits = <Pallet<T>>::clamp_limits(self.mode.clone(), &self.limits, limits)160 self.limits = <Pallet<T>>::clamp_limits(self.mode.clone(), &self.limits, limits)164 .map_err(dispatch_to_evm::<T>)?;161 .map_err(dispatch_to_evm::<T>)?;165 save(self);162 save(self)166 Ok(())167 }163 }168164169 fn contract_address(&self, _caller: caller) -> Result<address> {165 fn contract_address(&self, _caller: caller) -> Result<address> {296 }292 }297}293}298294299fn check_is_owner<T: Config>(caller: caller, collection: &CollectionHandle<T>) -> Result<()> {295fn check_is_owner<T: Config>(caller: caller, collection: &CollectionHandle<T>) -> Result<void> {300 let caller = T::CrossAccountId::from_eth(caller);296 let caller = T::CrossAccountId::from_eth(caller);301 collection297 collection302 .check_is_owner(&caller)298 .check_is_owner(&caller)315 Ok(caller)311 Ok(caller)316}312}317313318fn save<T: Config>(collection: &CollectionHandle<T>) {314fn save<T: Config>(collection: &CollectionHandle<T>) -> Result<void> {315 collection.check_is_read_only().map_err(dispatch_to_evm::<T>)?;319 <crate::CollectionById<T>>::insert(collection.id, collection.collection.clone());316 <crate::CollectionById<T>>::insert(collection.id, collection.collection.clone());317 Ok(())320}318}321319322pub fn token_uri_key() -> up_data_structs::PropertyKey {320pub fn token_uri_key() -> up_data_structs::PropertyKey {pallets/common/src/lib.rsdiffbeforeafterboth--- a/pallets/common/src/lib.rs
+++ b/pallets/common/src/lib.rs
@@ -148,23 +148,35 @@
.saturating_mul(writes),
))
}
-
- pub fn save(self) -> DispatchResult {
+ pub fn save(self) -> Result<(), DispatchError> {
+ self.check_is_read_only()?;
<CollectionById<T>>::insert(self.id, self.collection);
Ok(())
}
- pub fn set_sponsor(&mut self, sponsor: T::AccountId) {
+ pub fn set_sponsor(&mut self, sponsor: T::AccountId) -> DispatchResult {
+ self.check_is_read_only()?;
self.collection.sponsorship = SponsorshipState::Unconfirmed(sponsor);
+ Ok(())
}
- pub fn confirm_sponsorship(&mut self, sender: &T::AccountId) -> bool {
+ pub fn confirm_sponsorship(&mut self, sender: &T::AccountId) -> Result<bool, DispatchError> {
+ self.check_is_read_only()?;
+
if self.collection.sponsorship.pending_sponsor() != Some(sender) {
- return false;
- };
+ return Ok(false);
+ }
self.collection.sponsorship = SponsorshipState::Confirmed(sender.clone());
- true
+ Ok(true)
+ }
+
+ pub fn check_is_read_only(&self) -> DispatchResult {
+ if self.read_only {
+ return Err(<Error<T>>::CollectionNotFound)?;
+ }
+
+ Ok(())
}
}
@@ -434,6 +446,9 @@
/// Empty property keys are forbidden
EmptyPropertyKey,
+
+ /// Collection is read only
+ CollectionIsReadOnly,
}
#[pallet::storage]
@@ -669,6 +684,7 @@
sponsorship,
limits,
permissions,
+ read_only,
} = <CollectionById<T>>::get(collection)?;
let token_property_permissions = <CollectionPropertyPermissions<T>>::get(collection)
@@ -698,6 +714,7 @@
permissions,
token_property_permissions,
properties,
+ read_only,
})
}
}
@@ -778,6 +795,7 @@
Self::clamp_permissions(data.mode.clone(), &Default::default(), permissions)
})
.unwrap_or_else(|| Ok(CollectionPermissions::default()))?,
+ read_only: false,
};
let mut collection_properties = up_data_structs::CollectionProperties::get();
@@ -834,6 +852,7 @@
collection: CollectionHandle<T>,
sender: &T::CrossAccountId,
) -> DispatchResult {
+ collection.check_is_read_only()?;
ensure!(
collection.limits.owner_can_destroy(),
<Error<T>>::NoPermission,
@@ -863,6 +882,7 @@
sender: &T::CrossAccountId,
property: Property,
) -> DispatchResult {
+ collection.check_is_read_only()?;
collection.check_is_owner_or_admin(sender)?;
CollectionProperties::<T>::try_mutate(collection.id, |properties| {
@@ -908,6 +928,8 @@
sender: &T::CrossAccountId,
properties: Vec<Property>,
) -> DispatchResult {
+ collection.check_is_read_only()?;
+
for property in properties {
Self::set_collection_property(collection, sender, property)?;
}
@@ -920,6 +942,7 @@
sender: &T::CrossAccountId,
property_key: PropertyKey,
) -> DispatchResult {
+ collection.check_is_read_only()?;
collection.check_is_owner_or_admin(sender)?;
CollectionProperties::<T>::try_mutate(collection.id, |properties| {
@@ -941,6 +964,8 @@
sender: &T::CrossAccountId,
property_keys: Vec<PropertyKey>,
) -> DispatchResult {
+ collection.check_is_read_only()?;
+
for key in property_keys {
Self::delete_collection_property(collection, sender, key)?;
}
@@ -965,6 +990,7 @@
sender: &T::CrossAccountId,
property_permission: PropertyKeyPermission,
) -> DispatchResult {
+ collection.check_is_read_only()?;
collection.check_is_owner_or_admin(sender)?;
let all_permissions = CollectionPropertyPermissions::<T>::get(collection.id);
@@ -996,6 +1022,8 @@
sender: &T::CrossAccountId,
property_permissions: Vec<PropertyKeyPermission>,
) -> DispatchResult {
+ collection.check_is_read_only()?;
+
for prop_pemission in property_permissions {
Self::set_property_permission(collection, sender, prop_pemission)?;
}
@@ -1083,6 +1111,7 @@
user: &T::CrossAccountId,
allowed: bool,
) -> DispatchResult {
+ collection.check_is_read_only()?;
collection.check_is_owner_or_admin(sender)?;
// =========
@@ -1102,6 +1131,7 @@
user: &T::CrossAccountId,
admin: bool,
) -> DispatchResult {
+ collection.check_is_read_only()?;
collection.check_is_owner_or_admin(sender)?;
let was_admin = <IsAdmin<T>>::get((collection.id, user));
pallets/fungible/src/lib.rsdiffbeforeafterboth--- a/pallets/fungible/src/lib.rs
+++ b/pallets/fungible/src/lib.rs
@@ -168,6 +168,8 @@
owner: &T::CrossAccountId,
amount: u128,
) -> DispatchResult {
+ collection.check_is_read_only()?;
+
let total_supply = <TotalSupply<T>>::get(collection.id)
.checked_sub(amount)
.ok_or(<CommonError<T>>::TokenValueTooLow)?;
@@ -214,6 +216,8 @@
amount: u128,
nesting_budget: &dyn Budget,
) -> DispatchResult {
+ collection.check_is_read_only()?;
+
ensure!(
collection.limits.transfers_enabled(),
<CommonError<T>>::TransferNotAllowed,
@@ -283,6 +287,8 @@
data: BTreeMap<T::CrossAccountId, u128>,
nesting_budget: &dyn Budget,
) -> DispatchResult {
+ collection.check_is_read_only()?;
+
if !collection.is_owner_or_admin(sender) {
ensure!(
collection.permissions.mint_mode(),
@@ -384,6 +390,7 @@
spender: &T::CrossAccountId,
amount: u128,
) -> DispatchResult {
+ collection.check_is_read_only()?;
if collection.permissions.access() == AccessMode::AllowList {
collection.check_allowlist(owner)?;
collection.check_allowlist(spender)?;
pallets/nonfungible/src/lib.rsdiffbeforeafterboth--- a/pallets/nonfungible/src/lib.rs
+++ b/pallets/nonfungible/src/lib.rs
@@ -336,6 +336,8 @@
sender: &T::CrossAccountId,
token: TokenId,
) -> DispatchResult {
+ collection.check_is_read_only()?;
+
let token_data =
<TokenData<T>>::get((collection.id, token)).ok_or(<CommonError<T>>::TokenNotFound)?;
ensure!(
@@ -456,6 +458,7 @@
&property.key,
is_token_create,
)?;
+ collection.check_is_read_only()?;
<TokenProperties<T>>::try_mutate((collection.id, token_id), |properties| {
let property = property.clone();
@@ -494,6 +497,7 @@
property_key: PropertyKey,
) -> DispatchResult {
Self::check_token_change_permission(collection, sender, token_id, &property_key, false)?;
+ collection.check_is_read_only()?;
<TokenProperties<T>>::try_mutate((collection.id, token_id), |properties| {
properties.remove(&property_key)
@@ -570,6 +574,8 @@
token_id: TokenId,
property_keys: Vec<PropertyKey>,
) -> DispatchResult {
+ collection.check_is_read_only()?;
+
for key in property_keys {
Self::delete_token_property(collection, sender, token_id, key)?;
}
@@ -616,6 +622,8 @@
token: TokenId,
nesting_budget: &dyn Budget,
) -> DispatchResult {
+ collection.check_is_read_only()?;
+
ensure!(
collection.limits.transfers_enabled(),
<CommonError<T>>::TransferNotAllowed
@@ -894,6 +902,8 @@
token: TokenId,
spender: Option<&T::CrossAccountId>,
) -> DispatchResult {
+ collection.check_is_read_only()?;
+
if collection.permissions.access() == AccessMode::AllowList {
collection.check_allowlist(sender)?;
if let Some(spender) = spender {
pallets/refungible/src/lib.rsdiffbeforeafterboth--- a/pallets/refungible/src/lib.rs
+++ b/pallets/refungible/src/lib.rs
@@ -234,6 +234,7 @@
}
pub fn burn_token(collection: &RefungibleHandle<T>, token_id: TokenId) -> DispatchResult {
+ collection.check_is_read_only()?;
let burnt = <TokensBurnt<T>>::get(collection.id)
.checked_add(1)
.ok_or(ArithmeticError::Overflow)?;
@@ -253,6 +254,7 @@
token: TokenId,
amount: u128,
) -> DispatchResult {
+ collection.check_is_read_only()?;
let total_supply = <TotalSupply<T>>::get((collection.id, token))
.checked_sub(amount)
.ok_or(<CommonError<T>>::TokenValueTooLow)?;
@@ -325,6 +327,7 @@
amount: u128,
nesting_budget: &dyn Budget,
) -> DispatchResult {
+ collection.check_is_read_only()?;
ensure!(
collection.limits.transfers_enabled(),
<CommonError<T>>::TransferNotAllowed
@@ -573,6 +576,7 @@
token: TokenId,
amount: u128,
) -> DispatchResult {
+ collection.check_is_read_only()?;
if collection.permissions.access() == AccessMode::AllowList {
collection.check_allowlist(sender)?;
collection.check_allowlist(spender)?;
pallets/unique/src/lib.rsdiffbeforeafterboth--- a/pallets/unique/src/lib.rs
+++ b/pallets/unique/src/lib.rs
@@ -304,6 +304,7 @@
pub fn destroy_collection(origin, collection_id: CollectionId) -> DispatchResult {
let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);
let collection = <CollectionHandle<T>>::try_get(collection_id)?;
+ collection.check_is_read_only()?;
// =========
@@ -406,6 +407,7 @@
let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);
let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;
+ target_collection.check_is_read_only()?;
target_collection.check_is_owner(&sender)?;
target_collection.owner = new_owner.clone();
@@ -487,7 +489,7 @@
let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;
target_collection.check_is_owner(&sender)?;
- target_collection.set_sponsor(new_sponsor.clone());
+ target_collection.set_sponsor(new_sponsor.clone())?;
<Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorSet(
collection_id,
@@ -511,7 +513,7 @@
let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;
ensure!(
- target_collection.confirm_sponsorship(&sender),
+ target_collection.confirm_sponsorship(&sender)?,
Error::<T>::ConfirmUnsetSponsorFail
);
primitives/data-structs/src/lib.rsdiffbeforeafterboth--- a/primitives/data-structs/src/lib.rs
+++ b/primitives/data-structs/src/lib.rs
@@ -316,6 +316,9 @@
#[version(2.., upper(Default::default()))]
pub permissions: CollectionPermissions,
+ #[version(2.., upper(false))]
+ pub read_only: bool,
+
#[version(..2)]
pub variable_on_chain_schema: BoundedVec<u8, ConstU32<VARIABLE_ON_CHAIN_SCHEMA_LIMIT>>,
@@ -340,6 +343,7 @@
pub permissions: CollectionPermissions,
pub token_property_permissions: Vec<PropertyKeyPermission>,
pub properties: Vec<Property>,
+ pub read_only: bool,
}
#[derive(Encode, Decode, Clone, PartialEq, TypeInfo, Derivative, MaxEncodedLen)]
tests/src/createCollection.test.tsdiffbeforeafterboth--- a/tests/src/createCollection.test.ts
+++ b/tests/src/createCollection.test.ts
@@ -87,6 +87,20 @@
expect(collection.limits.accountTokenOwnershipLimit.unwrap().toNumber()).to.equal(3);
});
});
+
+ it('Create new collection is not read only', async () => {
+ await usingApi(async api => {
+ const alice = privateKey('//Alice');
+ const tx = api.tx.unique.createCollectionEx({
+ readOnly: true
+ });
+ const events = await submitTransactionAsync(alice, tx);
+ const result = getCreateCollectionResult(events);
+
+ const collection = (await getDetailedCollectionInfo(api, result.collectionId))!;
+ expect(collection.readOnly.toHuman()).to.be.false;
+ });
+ });
});
describe('(!negative test!) integration test: ext. createCollection():', () => {