difftreelog
misk: Move TokenChanged event into ERC721 contracts
in: master
16 files changed
pallets/common/src/erc.rsdiffbeforeafterboth--- a/pallets/common/src/erc.rs
+++ b/pallets/common/src/erc.rs
@@ -26,7 +26,6 @@
};
use pallet_evm_coder_substrate::dispatch_to_evm;
use sp_std::{vec, vec::Vec};
-use sp_core::U256;
use up_data_structs::{
CollectionMode, CollectionPermissions, OwnerRestrictedSet, Property, SponsoringRateLimit,
SponsorshipState,
@@ -62,18 +61,9 @@
},
/// The collection has been changed.
CollectionChanged {
- /// Collection ID.
- #[indexed]
- collection_id: Address,
- },
-
- /// The token has been changed.
- TokenChanged {
/// Collection ID.
#[indexed]
collection_id: Address,
- /// Token ID.
- token_id: U256,
},
}
pallets/common/src/lib.rsdiffbeforeafterboth--- a/pallets/common/src/lib.rs
+++ b/pallets/common/src/lib.rs
@@ -1250,6 +1250,7 @@
mut stored_properties: Properties,
is_token_owner: impl Fn() -> Result<bool, DispatchError>,
set_token_properties: impl FnOnce(Properties),
+ log: evm_coder::ethereum::Log,
) -> DispatchResult {
let is_collection_admin = collection.is_owner_or_admin(sender);
let permissions = Self::property_permissions(collection.id);
@@ -1304,13 +1305,7 @@
}
}
- <PalletEvm<T>>::deposit_log(
- CollectionHelpersEvents::TokenChanged {
- collection_id: eth::collection_id_to_address(collection.id),
- token_id: token_id.into(),
- }
- .to_log(T::ContractAddress::get()),
- );
+ <PalletEvm<T>>::deposit_log(log.clone());
}
set_token_properties(stored_properties);
pallets/nonfungible/src/erc.rsdiffbeforeafterboth--- a/pallets/nonfungible/src/erc.rs
+++ b/pallets/nonfungible/src/erc.rs
@@ -50,13 +50,26 @@
TokenProperties, SelfWeightOf, weights::WeightInfo,
};
+/// Nft events.
+#[derive(ToLog)]
+pub enum ERC721TokenEvent {
+ /// The token has been changed.
+ TokenChanged {
+ /// Collection ID.
+ #[indexed]
+ collection_id: Address,
+ /// Token ID.
+ token_id: U256,
+ },
+}
+
frontier_contract! {
macro_rules! NonfungibleHandle_result {...}
impl<T: Config> Contract for NonfungibleHandle<T> {...}
}
/// @title A contract that allows to set and delete token properties and change token property permissions.
-#[solidity_interface(name = TokenProperties, enum(derive(PreDispatch)), enum_attr(weight))]
+#[solidity_interface(name = TokenProperties, events(ERC721TokenEvent), enum(derive(PreDispatch)), enum_attr(weight))]
impl<T: Config> NonfungibleHandle<T> {
/// @notice Set permissions for token property.
/// @dev Throws error if `msg.sender` is not admin or owner of the collection.
pallets/nonfungible/src/lib.rsdiffbeforeafterboth--- a/pallets/nonfungible/src/lib.rs
+++ b/pallets/nonfungible/src/lib.rs
@@ -112,7 +112,7 @@
};
use pallet_structure::{Pallet as PalletStructure, Error as StructureError};
use pallet_evm_coder_substrate::{SubstrateRecorder, WithRecorder};
-use sp_core::H160;
+use sp_core::{Get, H160};
use sp_runtime::{ArithmeticError, DispatchError, DispatchResult, TransactionOutcome};
use sp_std::{vec::Vec, vec, collections::btree_map::BTreeMap};
use core::ops::Deref;
@@ -622,6 +622,11 @@
stored_properties,
is_token_owner,
|properties| <TokenProperties<T>>::set((collection.id, token_id), properties),
+ erc::ERC721TokenEvent::TokenChanged {
+ collection_id: collection_id_to_address(collection.id),
+ token_id: token_id.into(),
+ }
+ .to_log(T::ContractAddress::get()),
)
}
pallets/nonfungible/src/stubs/UniqueNFT.soldiffbeforeafterboth--- a/pallets/nonfungible/src/stubs/UniqueNFT.sol
+++ b/pallets/nonfungible/src/stubs/UniqueNFT.sol
@@ -17,9 +17,14 @@
}
}
+/// @dev inlined interface
+contract ERC721TokenEvent {
+ event TokenChanged(address indexed collectionId, uint256 tokenId);
+}
+
/// @title A contract that allows to set and delete token properties and change token property permissions.
/// @dev the ERC-165 identifier for this interface is 0xde0695c2
-contract TokenProperties is Dummy, ERC165 {
+contract TokenProperties is Dummy, ERC165, ERC721TokenEvent {
// /// @notice Set permissions for token property.
// /// @dev Throws error if `msg.sender` is not admin or owner of the collection.
// /// @param key Property key.
pallets/refungible/src/erc.rsdiffbeforeafterboth1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617//! # Refungible Pallet EVM API for tokens18//!19//! Provides ERC-721 standart support implementation and EVM API for unique extensions for Refungible Pallet.20//! Method implementations are mostly doing parameter conversion and calling Refungible Pallet methods.2122extern crate alloc;2324use core::{25 char::{REPLACEMENT_CHARACTER, decode_utf16},26 convert::TryInto,27};28use evm_coder::{abi::AbiType, ToLog, generate_stubgen, solidity_interface, types::*};29use frame_support::{BoundedBTreeMap, BoundedVec};30use pallet_common::{31 CollectionHandle, CollectionPropertyPermissions, CommonCollectionOperations,32 Error as CommonError,33 erc::{CommonEvmHandler, CollectionCall, static_property::key},34 eth::{self, TokenUri},35};36use pallet_evm::{account::CrossAccountId, PrecompileHandle};37use pallet_evm_coder_substrate::{38 call, dispatch_to_evm,39 execution::{PreDispatch, Result, Error},40 frontier_contract,41};42use pallet_structure::{SelfWeightOf as StructureWeight, weights::WeightInfo as _};43use sp_core::{H160, U256, Get};44use sp_std::{collections::btree_map::BTreeMap, vec::Vec, vec};45use up_data_structs::{46 CollectionId, CollectionPropertiesVec, mapping::TokenAddressMapping, Property, PropertyKey,47 PropertyKeyPermission, PropertyPermission, TokenId, TokenOwnerError,48};4950use crate::{51 AccountBalance, Balance, Config, CreateItemData, Pallet, RefungibleHandle, TokenProperties,52 TokensMinted, TotalSupply, SelfWeightOf, weights::WeightInfo,53};5455frontier_contract! {56 macro_rules! RefungibleHandle_result {...}57 impl<T: Config> Contract for RefungibleHandle<T> {...}58}5960pub const ADDRESS_FOR_PARTIALLY_OWNED_TOKENS: H160 = H160::repeat_byte(0xff);6162/// @title A contract that allows to set and delete token properties and change token property permissions.63#[solidity_interface(name = TokenProperties, enum(derive(PreDispatch)), enum_attr(weight))]64impl<T: Config> RefungibleHandle<T> {65 /// @notice Set permissions for token property.66 /// @dev Throws error if `msg.sender` is not admin or owner of the collection.67 /// @param key Property key.68 /// @param isMutable Permission to mutate property.69 /// @param collectionAdmin Permission to mutate property by collection admin if property is mutable.70 /// @param tokenOwner Permission to mutate property by token owner if property is mutable.71 #[solidity(hide)]72 #[weight(<SelfWeightOf<T>>::set_token_property_permissions(1))]73 fn set_token_property_permission(74 &mut self,75 caller: Caller,76 key: String,77 is_mutable: bool,78 collection_admin: bool,79 token_owner: bool,80 ) -> Result<()> {81 let caller = T::CrossAccountId::from_eth(caller);82 <Pallet<T>>::set_token_property_permissions(83 self,84 &caller,85 vec![PropertyKeyPermission {86 key: <Vec<u8>>::from(key)87 .try_into()88 .map_err(|_| "too long key")?,89 permission: PropertyPermission {90 mutable: is_mutable,91 collection_admin,92 token_owner,93 },94 }],95 )96 .map_err(dispatch_to_evm::<T>)97 }9899 /// @notice Set permissions for token property.100 /// @dev Throws error if `msg.sender` is not admin or owner of the collection.101 /// @param permissions Permissions for keys.102 #[weight(<SelfWeightOf<T>>::set_token_property_permissions(permissions.len() as u32))]103 fn set_token_property_permissions(104 &mut self,105 caller: Caller,106 permissions: Vec<eth::TokenPropertyPermission>,107 ) -> Result<()> {108 let caller = T::CrossAccountId::from_eth(caller);109 let perms = eth::TokenPropertyPermission::into_property_key_permissions(permissions)?;110111 <Pallet<T>>::set_token_property_permissions(self, &caller, perms)112 .map_err(dispatch_to_evm::<T>)113 }114115 /// @notice Get permissions for token properties.116 fn token_property_permissions(&self) -> Result<Vec<eth::TokenPropertyPermission>> {117 let perms = <Pallet<T>>::token_property_permission(self.id);118 Ok(perms119 .into_iter()120 .map(eth::TokenPropertyPermission::from)121 .collect())122 }123124 /// @notice Set token property value.125 /// @dev Throws error if `msg.sender` has no permission to edit the property.126 /// @param tokenId ID of the token.127 /// @param key Property key.128 /// @param value Property value.129 #[solidity(hide)]130 #[weight(<SelfWeightOf<T>>::set_token_properties(1))]131 fn set_property(132 &mut self,133 caller: Caller,134 token_id: U256,135 key: String,136 value: Bytes,137 ) -> Result<()> {138 let caller = T::CrossAccountId::from_eth(caller);139 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;140 let key = <Vec<u8>>::from(key)141 .try_into()142 .map_err(|_| "key too long")?;143 let value = value.0.try_into().map_err(|_| "value too long")?;144145 let nesting_budget = self146 .recorder147 .weight_calls_budget(<StructureWeight<T>>::find_parent());148149 <Pallet<T>>::set_token_property(150 self,151 &caller,152 TokenId(token_id),153 Property { key, value },154 &nesting_budget,155 )156 .map_err(dispatch_to_evm::<T>)157 }158159 /// @notice Set token properties value.160 /// @dev Throws error if `msg.sender` has no permission to edit the property.161 /// @param tokenId ID of the token.162 /// @param properties settable properties163 #[weight(<SelfWeightOf<T>>::set_token_properties(properties.len() as u32))]164 fn set_properties(165 &mut self,166 caller: Caller,167 token_id: U256,168 properties: Vec<eth::Property>,169 ) -> Result<()> {170 let caller = T::CrossAccountId::from_eth(caller);171 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;172173 let nesting_budget = self174 .recorder175 .weight_calls_budget(<StructureWeight<T>>::find_parent());176177 let properties = properties178 .into_iter()179 .map(eth::Property::try_into)180 .collect::<Result<Vec<_>>>()?;181182 <Pallet<T>>::set_token_properties(183 self,184 &caller,185 TokenId(token_id),186 properties.into_iter(),187 false,188 &nesting_budget,189 )190 .map_err(dispatch_to_evm::<T>)191 }192193 /// @notice Delete token property value.194 /// @dev Throws error if `msg.sender` has no permission to edit the property.195 /// @param tokenId ID of the token.196 /// @param key Property key.197 #[solidity(hide)]198 #[weight(<SelfWeightOf<T>>::delete_token_properties(1))]199 fn delete_property(&mut self, token_id: U256, caller: Caller, key: String) -> Result<()> {200 let caller = T::CrossAccountId::from_eth(caller);201 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;202 let key = <Vec<u8>>::from(key)203 .try_into()204 .map_err(|_| "key too long")?;205206 let nesting_budget = self207 .recorder208 .weight_calls_budget(<StructureWeight<T>>::find_parent());209210 <Pallet<T>>::delete_token_property(self, &caller, TokenId(token_id), key, &nesting_budget)211 .map_err(dispatch_to_evm::<T>)212 }213214 /// @notice Delete token properties value.215 /// @dev Throws error if `msg.sender` has no permission to edit the property.216 /// @param tokenId ID of the token.217 /// @param keys Properties key.218 #[weight(<SelfWeightOf<T>>::delete_token_properties(keys.len() as u32))]219 fn delete_properties(220 &mut self,221 token_id: U256,222 caller: Caller,223 keys: Vec<String>,224 ) -> Result<()> {225 let caller = T::CrossAccountId::from_eth(caller);226 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;227 let keys = keys228 .into_iter()229 .map(|k| Ok(<Vec<u8>>::from(k).try_into().map_err(|_| "key too long")?))230 .collect::<Result<Vec<_>>>()?;231232 let nesting_budget = self233 .recorder234 .weight_calls_budget(<StructureWeight<T>>::find_parent());235236 <Pallet<T>>::delete_token_properties(237 self,238 &caller,239 TokenId(token_id),240 keys.into_iter(),241 &nesting_budget,242 )243 .map_err(dispatch_to_evm::<T>)244 }245246 /// @notice Get token property value.247 /// @dev Throws error if key not found248 /// @param tokenId ID of the token.249 /// @param key Property key.250 /// @return Property value bytes251 fn property(&self, token_id: U256, key: String) -> Result<Bytes> {252 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;253 let key = <Vec<u8>>::from(key)254 .try_into()255 .map_err(|_| "key too long")?;256257 let props = <TokenProperties<T>>::get((self.id, token_id));258 let prop = props.get(&key).ok_or("key not found")?;259260 Ok(prop.to_vec().into())261 }262}263264#[derive(ToLog)]265pub enum ERC721Events {266 /// @dev This event emits when NFTs are created (`from` == 0) and destroyed267 /// (`to` == 0). Exception: during contract creation, any number of RFTs268 /// may be created and assigned without emitting Transfer.269 Transfer {270 #[indexed]271 from: Address,272 #[indexed]273 to: Address,274 #[indexed]275 token_id: U256,276 },277 /// @dev Not supported278 Approval {279 #[indexed]280 owner: Address,281 #[indexed]282 approved: Address,283 #[indexed]284 token_id: U256,285 },286 /// @dev Not supported287 #[allow(dead_code)]288 ApprovalForAll {289 #[indexed]290 owner: Address,291 #[indexed]292 operator: Address,293 approved: bool,294 },295}296297/// @title ERC-721 Non-Fungible Token Standard, optional metadata extension298/// @dev See https://eips.ethereum.org/EIPS/eip-721299#[solidity_interface(name = ERC721Metadata, enum(derive(PreDispatch)), expect_selector = 0x5b5e139f)]300impl<T: Config> RefungibleHandle<T>301where302 T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>,303{304 /// @notice A descriptive name for a collection of NFTs in this contract305 /// @dev real implementation of this function lies in `ERC721UniqueExtensions`306 #[solidity(hide, rename_selector = "name")]307 fn name_proxy(&self) -> Result<String> {308 self.name()309 }310311 /// @notice An abbreviated name for NFTs in this contract312 /// @dev real implementation of this function lies in `ERC721UniqueExtensions`313 #[solidity(hide, rename_selector = "symbol")]314 fn symbol_proxy(&self) -> Result<String> {315 self.symbol()316 }317318 /// @notice A distinct Uniform Resource Identifier (URI) for a given asset.319 ///320 /// @dev If the token has a `url` property and it is not empty, it is returned.321 /// Else If the collection does not have a property with key `schemaName` or its value is not equal to `ERC721Metadata`, it return an error `tokenURI not set`.322 /// If the collection property `baseURI` is empty or absent, return "" (empty string)323 /// otherwise, if token property `suffix` present and is non-empty, return concatenation of baseURI and suffix324 /// otherwise, return concatenation of `baseURI` and stringified token id (decimal stringifying, without paddings).325 ///326 /// @return token's const_metadata327 #[solidity(rename_selector = "tokenURI")]328 fn token_uri(&self, token_id: U256) -> Result<String> {329 let token_id_u32: u32 = token_id.try_into().map_err(|_| "token id overflow")?;330331 match get_token_property(self, token_id_u32, &key::url()).as_deref() {332 Err(_) | Ok("") => (),333 Ok(url) => {334 return Ok(url.into());335 }336 };337338 let base_uri =339 pallet_common::Pallet::<T>::get_collection_property(self.id, &key::base_uri())340 .map(BoundedVec::into_inner)341 .map(String::from_utf8)342 .transpose()343 .map_err(|e| {344 Error::Revert(alloc::format!(345 "Can not convert value \"baseURI\" to string with error \"{}\"",346 e347 ))348 })?;349350 let base_uri = match base_uri.as_deref() {351 None | Some("") => {352 return Ok("".into());353 }354 Some(base_uri) => base_uri.into(),355 };356357 Ok(358 match get_token_property(self, token_id_u32, &key::suffix()).as_deref() {359 Err(_) | Ok("") => base_uri,360 Ok(suffix) => base_uri + suffix,361 },362 )363 }364}365366/// @title ERC-721 Non-Fungible Token Standard, optional enumeration extension367/// @dev See https://eips.ethereum.org/EIPS/eip-721368#[solidity_interface(name = ERC721Enumerable, enum(derive(PreDispatch)), expect_selector = 0x780e9d63)]369impl<T: Config> RefungibleHandle<T> {370 /// @notice Enumerate valid RFTs371 /// @param index A counter less than `totalSupply()`372 /// @return The token identifier for the `index`th NFT,373 /// (sort order not specified)374 fn token_by_index(&self, index: U256) -> U256 {375 index376 }377378 /// Not implemented379 fn token_of_owner_by_index(&self, _owner: Address, _index: U256) -> Result<U256> {380 // TODO: Not implemetable381 Err("not implemented".into())382 }383384 /// @notice Count RFTs tracked by this contract385 /// @return A count of valid RFTs tracked by this contract, where each one of386 /// them has an assigned and queryable owner not equal to the zero address387 fn total_supply(&self) -> Result<U256> {388 self.consume_store_reads(1)?;389 Ok(<Pallet<T>>::total_supply(self).into())390 }391}392393/// @title ERC-721 Non-Fungible Token Standard394/// @dev See https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md395#[solidity_interface(name = ERC721, events(ERC721Events), enum(derive(PreDispatch)), enum_attr(weight), expect_selector = 0x80ac58cd)]396impl<T: Config> RefungibleHandle<T> {397 /// @notice Count all RFTs assigned to an owner398 /// @dev RFTs assigned to the zero address are considered invalid, and this399 /// function throws for queries about the zero address.400 /// @param owner An address for whom to query the balance401 /// @return The number of RFTs owned by `owner`, possibly zero402 fn balance_of(&self, owner: Address) -> Result<U256> {403 self.consume_store_reads(1)?;404 let owner = T::CrossAccountId::from_eth(owner);405 let balance = <AccountBalance<T>>::get((self.id, owner));406 Ok(balance.into())407 }408409 /// @notice Find the owner of an RFT410 /// @dev RFTs assigned to zero address are considered invalid, and queries411 /// about them do throw.412 /// Returns special 0xffffffffffffffffffffffffffffffffffffffff address for413 /// the tokens that are partially owned.414 /// @param tokenId The identifier for an RFT415 /// @return The address of the owner of the RFT416 fn owner_of(&self, token_id: U256) -> Result<Address> {417 self.consume_store_reads(2)?;418 let token = token_id.try_into()?;419 let owner = <Pallet<T>>::token_owner(self.id, token);420 owner421 .map(|address| *address.as_eth())422 .or_else(|err| match err {423 TokenOwnerError::NotFound => Err(Error::Revert("token not found".into())),424 TokenOwnerError::MultipleOwners => Ok(ADDRESS_FOR_PARTIALLY_OWNED_TOKENS),425 })426 }427428 /// @dev Not implemented429 #[solidity(rename_selector = "safeTransferFrom")]430 fn safe_transfer_from_with_data(431 &mut self,432 _from: Address,433 _to: Address,434 _token_id: U256,435 _data: Bytes,436 ) -> Result<()> {437 // TODO: Not implemetable438 Err("not implemented".into())439 }440441 /// @dev Not implemented442 #[solidity(rename_selector = "safeTransferFrom")]443 fn safe_transfer_from(&mut self, _from: Address, _to: Address, _token_id: U256) -> Result<()> {444 // TODO: Not implemetable445 Err("not implemented".into())446 }447448 /// @notice Transfer ownership of an RFT -- THE CALLER IS RESPONSIBLE449 /// TO CONFIRM THAT `to` IS CAPABLE OF RECEIVING NFTS OR ELSE450 /// THEY MAY BE PERMANENTLY LOST451 /// @dev Throws unless `msg.sender` is the current owner or an authorized452 /// operator for this RFT. Throws if `from` is not the current owner. Throws453 /// if `to` is the zero address. Throws if `tokenId` is not a valid RFT.454 /// Throws if RFT pieces have multiple owners.455 /// @param from The current owner of the NFT456 /// @param to The new owner457 /// @param tokenId The NFT to transfer458 #[weight(<SelfWeightOf<T>>::transfer_from_creating_removing())]459 fn transfer_from(460 &mut self,461 caller: Caller,462 from: Address,463 to: Address,464 token_id: U256,465 ) -> Result<()> {466 let caller = T::CrossAccountId::from_eth(caller);467 let from = T::CrossAccountId::from_eth(from);468 let to = T::CrossAccountId::from_eth(to);469 let token = token_id.try_into()?;470 let budget = self471 .recorder472 .weight_calls_budget(<StructureWeight<T>>::find_parent());473474 let balance = balance(&self, token, &from)?;475 ensure_single_owner(&self, token, balance)?;476477 <Pallet<T>>::transfer_from(self, &caller, &from, &to, token, balance, &budget)478 .map_err(dispatch_to_evm::<T>)?;479480 Ok(())481 }482483 /// @dev Not implemented484 fn approve(&mut self, _caller: Caller, _approved: Address, _token_id: U256) -> Result<()> {485 Err("not implemented".into())486 }487488 /// @notice Sets or unsets the approval of a given operator.489 /// The `operator` is allowed to transfer all token pieces of the `caller` on their behalf.490 /// @param operator Operator491 /// @param approved Should operator status be granted or revoked?492 #[weight(<SelfWeightOf<T>>::set_allowance_for_all())]493 fn set_approval_for_all(494 &mut self,495 caller: Caller,496 operator: Address,497 approved: bool,498 ) -> Result<()> {499 let caller = T::CrossAccountId::from_eth(caller);500 let operator = T::CrossAccountId::from_eth(operator);501502 <Pallet<T>>::set_allowance_for_all(self, &caller, &operator, approved)503 .map_err(dispatch_to_evm::<T>)?;504 Ok(())505 }506507 /// @dev Not implemented508 fn get_approved(&self, _token_id: U256) -> Result<Address> {509 // TODO: Not implemetable510 Err("not implemented".into())511 }512513 /// @notice Tells whether the given `owner` approves the `operator`.514 #[weight(<SelfWeightOf<T>>::allowance_for_all())]515 fn is_approved_for_all(&self, owner: Address, operator: Address) -> Result<bool> {516 let owner = T::CrossAccountId::from_eth(owner);517 let operator = T::CrossAccountId::from_eth(operator);518519 Ok(<Pallet<T>>::allowance_for_all(self, &owner, &operator))520 }521}522523/// Returns amount of pieces of `token` that `owner` have524pub fn balance<T: Config>(525 collection: &RefungibleHandle<T>,526 token: TokenId,527 owner: &T::CrossAccountId,528) -> Result<u128> {529 collection.consume_store_reads(1)?;530 let balance = <Balance<T>>::get((collection.id, token, &owner));531 Ok(balance)532}533534/// Throws if `owner_balance` is lower than total amount of `token` pieces535pub fn ensure_single_owner<T: Config>(536 collection: &RefungibleHandle<T>,537 token: TokenId,538 owner_balance: u128,539) -> Result<()> {540 collection.consume_store_reads(1)?;541 let total_supply = <TotalSupply<T>>::get((collection.id, token));542543 if owner_balance == 0 {544 return Err(dispatch_to_evm::<T>(545 <CommonError<T>>::MustBeTokenOwner.into(),546 ));547 }548549 if total_supply != owner_balance {550 return Err("token has multiple owners".into());551 }552 Ok(())553}554555/// @title ERC721 Token that can be irreversibly burned (destroyed).556#[solidity_interface(name = ERC721Burnable, enum(derive(PreDispatch)), enum_attr(weight))]557impl<T: Config> RefungibleHandle<T> {558 /// @notice Burns a specific ERC721 token.559 /// @dev Throws unless `msg.sender` is the current RFT owner, or an authorized560 /// operator of the current owner.561 /// @param tokenId The RFT to approve562 #[weight(<SelfWeightOf<T>>::burn_item_fully())]563 fn burn(&mut self, caller: Caller, token_id: U256) -> Result<()> {564 let caller = T::CrossAccountId::from_eth(caller);565 let token = token_id.try_into()?;566567 let balance = balance(&self, token, &caller)?;568 ensure_single_owner(&self, token, balance)?;569570 <Pallet<T>>::burn(self, &caller, token, balance).map_err(dispatch_to_evm::<T>)?;571 Ok(())572 }573}574575/// @title ERC721 minting logic.576#[solidity_interface(name = ERC721UniqueMintable, enum(derive(PreDispatch)), enum_attr(weight))]577impl<T: Config> RefungibleHandle<T> {578 /// @notice Function to mint a token.579 /// @param to The new owner580 /// @return uint256 The id of the newly minted token581 #[weight(<SelfWeightOf<T>>::create_item())]582 fn mint(&mut self, caller: Caller, to: Address) -> Result<U256> {583 let token_id: U256 = <TokensMinted<T>>::get(self.id)584 .checked_add(1)585 .ok_or("item id overflow")?586 .into();587 self.mint_check_id(caller, to, token_id)?;588 Ok(token_id)589 }590591 /// @notice Function to mint a token.592 /// @dev `tokenId` should be obtained with `nextTokenId` method,593 /// unlike standard, you can't specify it manually594 /// @param to The new owner595 /// @param tokenId ID of the minted RFT596 #[solidity(hide, rename_selector = "mint")]597 #[weight(<SelfWeightOf<T>>::create_item())]598 fn mint_check_id(&mut self, caller: Caller, to: Address, token_id: U256) -> Result<bool> {599 let caller = T::CrossAccountId::from_eth(caller);600 let to = T::CrossAccountId::from_eth(to);601 let token_id: u32 = token_id.try_into()?;602 let budget = self603 .recorder604 .weight_calls_budget(<StructureWeight<T>>::find_parent());605606 if <TokensMinted<T>>::get(self.id)607 .checked_add(1)608 .ok_or("item id overflow")?609 != token_id610 {611 return Err("item id should be next".into());612 }613614 let users = [(to.clone(), 1)]615 .into_iter()616 .collect::<BTreeMap<_, _>>()617 .try_into()618 .unwrap();619 <Pallet<T>>::create_item(620 self,621 &caller,622 CreateItemData::<T> {623 users,624 properties: CollectionPropertiesVec::default(),625 },626 &budget,627 )628 .map_err(dispatch_to_evm::<T>)?;629630 Ok(true)631 }632633 /// @notice Function to mint token with the given tokenUri.634 /// @param to The new owner635 /// @param tokenUri Token URI that would be stored in the NFT properties636 /// @return uint256 The id of the newly minted token637 #[solidity(rename_selector = "mintWithTokenURI")]638 #[weight(<SelfWeightOf<T>>::create_item() + <SelfWeightOf<T>>::set_token_properties(1))]639 fn mint_with_token_uri(640 &mut self,641 caller: Caller,642 to: Address,643 token_uri: String,644 ) -> Result<U256> {645 let token_id: U256 = <TokensMinted<T>>::get(self.id)646 .checked_add(1)647 .ok_or("item id overflow")?648 .into();649 self.mint_with_token_uri_check_id(caller, to, token_id, token_uri)?;650 Ok(token_id)651 }652653 /// @notice Function to mint token with the given tokenUri.654 /// @dev `tokenId` should be obtained with `nextTokenId` method,655 /// unlike standard, you can't specify it manually656 /// @param to The new owner657 /// @param tokenId ID of the minted RFT658 /// @param tokenUri Token URI that would be stored in the RFT properties659 #[solidity(hide, rename_selector = "mintWithTokenURI")]660 #[weight(<SelfWeightOf<T>>::create_item() + <SelfWeightOf<T>>::set_token_properties(1))]661 fn mint_with_token_uri_check_id(662 &mut self,663 caller: Caller,664 to: Address,665 token_id: U256,666 token_uri: String,667 ) -> Result<bool> {668 let key = key::url();669 let permission = get_token_permission::<T>(self.id, &key)?;670 if !permission.collection_admin {671 return Err("Operation is not allowed".into());672 }673674 let caller = T::CrossAccountId::from_eth(caller);675 let to = T::CrossAccountId::from_eth(to);676 let token_id: u32 = token_id.try_into().map_err(|_| "amount overflow")?;677 let budget = self678 .recorder679 .weight_calls_budget(<StructureWeight<T>>::find_parent());680681 if <TokensMinted<T>>::get(self.id)682 .checked_add(1)683 .ok_or("item id overflow")?684 != token_id685 {686 return Err("item id should be next".into());687 }688689 let mut properties = CollectionPropertiesVec::default();690 properties691 .try_push(Property {692 key,693 value: token_uri694 .into_bytes()695 .try_into()696 .map_err(|_| "token uri is too long")?,697 })698 .map_err(|e| Error::Revert(alloc::format!("Can't add property: {:?}", e)))?;699700 let users = [(to.clone(), 1)]701 .into_iter()702 .collect::<BTreeMap<_, _>>()703 .try_into()704 .unwrap();705 <Pallet<T>>::create_item(706 self,707 &caller,708 CreateItemData::<T> { users, properties },709 &budget,710 )711 .map_err(dispatch_to_evm::<T>)?;712 Ok(true)713 }714}715716fn get_token_property<T: Config>(717 collection: &CollectionHandle<T>,718 token_id: u32,719 key: &up_data_structs::PropertyKey,720) -> Result<String> {721 collection.consume_store_reads(1)?;722 let properties = <TokenProperties<T>>::try_get((collection.id, token_id))723 .map_err(|_| Error::Revert("Token properties not found".into()))?;724 if let Some(property) = properties.get(key) {725 return Ok(String::from_utf8_lossy(property).into());726 }727728 Err("Property tokenURI not found".into())729}730731fn get_token_permission<T: Config>(732 collection_id: CollectionId,733 key: &PropertyKey,734) -> Result<PropertyPermission> {735 let token_property_permissions = CollectionPropertyPermissions::<T>::try_get(collection_id)736 .map_err(|_| Error::Revert("No permissions for collection".into()))?;737 let a = token_property_permissions738 .get(key)739 .map(Clone::clone)740 .ok_or_else(|| {741 let key = String::from_utf8(key.clone().into_inner()).unwrap_or_default();742 Error::Revert(alloc::format!("No permission for key {}", key))743 })?;744 Ok(a)745}746747/// @title Unique extensions for ERC721.748#[solidity_interface(name = ERC721UniqueExtensions, enum(derive(PreDispatch)), enum_attr(weight))]749impl<T: Config> RefungibleHandle<T>750where751 T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>,752{753 /// @notice A descriptive name for a collection of NFTs in this contract754 fn name(&self) -> Result<String> {755 Ok(decode_utf16(self.name.iter().copied())756 .map(|r| r.unwrap_or(REPLACEMENT_CHARACTER))757 .collect::<String>())758 }759760 /// @notice An abbreviated name for NFTs in this contract761 fn symbol(&self) -> Result<String> {762 Ok(String::from_utf8_lossy(&self.token_prefix).into())763 }764765 /// @notice A description for the collection.766 fn description(&self) -> Result<String> {767 Ok(decode_utf16(self.description.iter().copied())768 .map(|r| r.unwrap_or(REPLACEMENT_CHARACTER))769 .collect::<String>())770 }771772 /// Returns the owner (in cross format) of the token.773 ///774 /// @param tokenId Id for the token.775 fn cross_owner_of(&self, token_id: U256) -> Result<eth::CrossAddress> {776 Self::token_owner(&self, token_id.try_into()?)777 .map(|o| eth::CrossAddress::from_sub_cross_account::<T>(&o))778 .or_else(|err| match err {779 TokenOwnerError::NotFound => Err(Error::Revert("token not found".into())),780 TokenOwnerError::MultipleOwners => Ok(eth::CrossAddress::from_eth(781 ADDRESS_FOR_PARTIALLY_OWNED_TOKENS,782 )),783 })784 }785786 /// Returns the token properties.787 ///788 /// @param tokenId Id for the token.789 /// @param keys Properties keys. Empty keys for all propertyes.790 /// @return Vector of properties key/value pairs.791 fn properties(&self, token_id: U256, keys: Vec<String>) -> Result<Vec<eth::Property>> {792 let keys = keys793 .into_iter()794 .map(|key| {795 <Vec<u8>>::from(key)796 .try_into()797 .map_err(|_| Error::Revert("key too large".into()))798 })799 .collect::<Result<Vec<_>>>()?;800801 <Self as CommonCollectionOperations<T>>::token_properties(802 &self,803 token_id.try_into()?,804 if keys.is_empty() { None } else { Some(keys) },805 )806 .into_iter()807 .map(eth::Property::try_from)808 .collect::<Result<Vec<_>>>()809 }810 /// @notice Transfer ownership of an RFT811 /// @dev Throws unless `msg.sender` is the current owner. Throws if `to`812 /// is the zero address. Throws if `tokenId` is not a valid RFT.813 /// Throws if RFT pieces have multiple owners.814 /// @param to The new owner815 /// @param tokenId The RFT to transfer816 #[weight(<SelfWeightOf<T>>::transfer_creating_removing())]817 fn transfer(&mut self, caller: Caller, to: Address, token_id: U256) -> Result<()> {818 let caller = T::CrossAccountId::from_eth(caller);819 let to = T::CrossAccountId::from_eth(to);820 let token = token_id.try_into()?;821 let budget = self822 .recorder823 .weight_calls_budget(<StructureWeight<T>>::find_parent());824825 let balance = balance(self, token, &caller)?;826 ensure_single_owner(self, token, balance)?;827828 <Pallet<T>>::transfer(self, &caller, &to, token, balance, &budget)829 .map_err(dispatch_to_evm::<T>)?;830 Ok(())831 }832833 /// @notice Transfer ownership of an RFT834 /// @dev Throws unless `msg.sender` is the current owner. Throws if `to`835 /// is the zero address. Throws if `tokenId` is not a valid RFT.836 /// Throws if RFT pieces have multiple owners.837 /// @param to The new owner838 /// @param tokenId The RFT to transfer839 #[weight(<SelfWeightOf<T>>::transfer_creating_removing())]840 fn transfer_cross(841 &mut self,842 caller: Caller,843 to: eth::CrossAddress,844 token_id: U256,845 ) -> Result<()> {846 let caller = T::CrossAccountId::from_eth(caller);847 let to = to.into_sub_cross_account::<T>()?;848 let token = token_id.try_into()?;849 let budget = self850 .recorder851 .weight_calls_budget(<StructureWeight<T>>::find_parent());852853 let balance = balance(self, token, &caller)?;854 ensure_single_owner(self, token, balance)?;855856 <Pallet<T>>::transfer(self, &caller, &to, token, balance, &budget)857 .map_err(dispatch_to_evm::<T>)?;858 Ok(())859 }860861 /// @notice Transfer ownership of an RFT862 /// @dev Throws unless `msg.sender` is the current owner. Throws if `to`863 /// is the zero address. Throws if `tokenId` is not a valid RFT.864 /// Throws if RFT pieces have multiple owners.865 /// @param to The new owner866 /// @param tokenId The RFT to transfer867 #[weight(<SelfWeightOf<T>>::transfer_creating_removing())]868 fn transfer_from_cross(869 &mut self,870 caller: Caller,871 from: eth::CrossAddress,872 to: eth::CrossAddress,873 token_id: U256,874 ) -> Result<()> {875 let caller = T::CrossAccountId::from_eth(caller);876 let from = from.into_sub_cross_account::<T>()?;877 let to = to.into_sub_cross_account::<T>()?;878 let token_id = token_id.try_into()?;879 let budget = self880 .recorder881 .weight_calls_budget(<StructureWeight<T>>::find_parent());882883 let balance = balance(self, token_id, &from)?;884 ensure_single_owner(self, token_id, balance)?;885886 Pallet::<T>::transfer_from(self, &caller, &from, &to, token_id, balance, &budget)887 .map_err(dispatch_to_evm::<T>)?;888 Ok(())889 }890891 /// @notice Burns a specific ERC721 token.892 /// @dev Throws unless `msg.sender` is the current owner or an authorized893 /// operator for this RFT. Throws if `from` is not the current owner. Throws894 /// if `to` is the zero address. Throws if `tokenId` is not a valid RFT.895 /// Throws if RFT pieces have multiple owners.896 /// @param from The current owner of the RFT897 /// @param tokenId The RFT to transfer898 #[solidity(hide)]899 #[weight(<SelfWeightOf<T>>::burn_from())]900 fn burn_from(&mut self, caller: Caller, from: Address, token_id: U256) -> Result<()> {901 let caller = T::CrossAccountId::from_eth(caller);902 let from = T::CrossAccountId::from_eth(from);903 let token = token_id.try_into()?;904 let budget = self905 .recorder906 .weight_calls_budget(<StructureWeight<T>>::find_parent());907908 let balance = balance(self, token, &from)?;909 ensure_single_owner(self, token, balance)?;910911 <Pallet<T>>::burn_from(self, &caller, &from, token, balance, &budget)912 .map_err(dispatch_to_evm::<T>)?;913 Ok(())914 }915916 /// @notice Burns a specific ERC721 token.917 /// @dev Throws unless `msg.sender` is the current owner or an authorized918 /// operator for this RFT. Throws if `from` is not the current owner. Throws919 /// if `to` is the zero address. Throws if `tokenId` is not a valid RFT.920 /// Throws if RFT pieces have multiple owners.921 /// @param from The current owner of the RFT922 /// @param tokenId The RFT to transfer923 #[weight(<SelfWeightOf<T>>::burn_from())]924 fn burn_from_cross(925 &mut self,926 caller: Caller,927 from: eth::CrossAddress,928 token_id: U256,929 ) -> Result<()> {930 let caller = T::CrossAccountId::from_eth(caller);931 let from = from.into_sub_cross_account::<T>()?;932 let token = token_id.try_into()?;933 let budget = self934 .recorder935 .weight_calls_budget(<StructureWeight<T>>::find_parent());936937 let balance = balance(self, token, &from)?;938 ensure_single_owner(self, token, balance)?;939940 <Pallet<T>>::burn_from(self, &caller, &from, token, balance, &budget)941 .map_err(dispatch_to_evm::<T>)?;942 Ok(())943 }944945 /// @notice Returns next free RFT ID.946 fn next_token_id(&self) -> Result<U256> {947 self.consume_store_reads(1)?;948 Ok(<TokensMinted<T>>::get(self.id)949 .checked_add(1)950 .ok_or("item id overflow")?951 .into())952 }953954 /// @notice Function to mint multiple tokens.955 /// @dev `tokenIds` should be an array of consecutive numbers and first number956 /// should be obtained with `nextTokenId` method957 /// @param to The new owner958 /// @param tokenIds IDs of the minted RFTs959 #[solidity(hide)]960 #[weight(<SelfWeightOf<T>>::create_multiple_items(token_ids.len() as u32))]961 fn mint_bulk(&mut self, caller: Caller, to: Address, token_ids: Vec<U256>) -> Result<bool> {962 let caller = T::CrossAccountId::from_eth(caller);963 let to = T::CrossAccountId::from_eth(to);964 let mut expected_index = <TokensMinted<T>>::get(self.id)965 .checked_add(1)966 .ok_or("item id overflow")?;967 let budget = self968 .recorder969 .weight_calls_budget(<StructureWeight<T>>::find_parent());970971 let total_tokens = token_ids.len();972 for id in token_ids.into_iter() {973 let id: u32 = id.try_into().map_err(|_| "token id overflow")?;974 if id != expected_index {975 return Err("item id should be next".into());976 }977 expected_index = expected_index.checked_add(1).ok_or("item id overflow")?;978 }979 let users = [(to.clone(), 1)]980 .into_iter()981 .collect::<BTreeMap<_, _>>()982 .try_into()983 .unwrap();984 let create_item_data = CreateItemData::<T> {985 users,986 properties: CollectionPropertiesVec::default(),987 };988 let data = (0..total_tokens)989 .map(|_| create_item_data.clone())990 .collect();991992 <Pallet<T>>::create_multiple_items(self, &caller, data, &budget)993 .map_err(dispatch_to_evm::<T>)?;994 Ok(true)995 }996997 /// @notice Function to mint multiple tokens with the given tokenUris.998 /// @dev `tokenIds` is array of pairs of token ID and token URI. Token IDs should be consecutive999 /// numbers and first number should be obtained with `nextTokenId` method1000 /// @param to The new owner1001 /// @param tokens array of pairs of token ID and token URI for minted tokens1002 #[solidity(hide, rename_selector = "mintBulkWithTokenURI")]1003 #[weight(<SelfWeightOf<T>>::create_multiple_items(tokens.len() as u32) + <SelfWeightOf<T>>::set_token_properties(tokens.len() as u32))]1004 fn mint_bulk_with_token_uri(1005 &mut self,1006 caller: Caller,1007 to: Address,1008 tokens: Vec<TokenUri>,1009 ) -> Result<bool> {1010 let key = key::url();1011 let caller = T::CrossAccountId::from_eth(caller);1012 let to = T::CrossAccountId::from_eth(to);1013 let mut expected_index = <TokensMinted<T>>::get(self.id)1014 .checked_add(1)1015 .ok_or("item id overflow")?;1016 let budget = self1017 .recorder1018 .weight_calls_budget(<StructureWeight<T>>::find_parent());10191020 let mut data = Vec::with_capacity(tokens.len());1021 let users: BoundedBTreeMap<_, _, _> = [(to.clone(), 1)]1022 .into_iter()1023 .collect::<BTreeMap<_, _>>()1024 .try_into()1025 .unwrap();1026 for TokenUri { id, uri } in tokens {1027 let id: u32 = id.try_into().map_err(|_| "token id overflow")?;1028 if id != expected_index {1029 return Err("item id should be next".into());1030 }1031 expected_index = expected_index.checked_add(1).ok_or("item id overflow")?;10321033 let mut properties = CollectionPropertiesVec::default();1034 properties1035 .try_push(Property {1036 key: key.clone(),1037 value: uri1038 .into_bytes()1039 .try_into()1040 .map_err(|_| "token uri is too long")?,1041 })1042 .map_err(|e| Error::Revert(alloc::format!("Can't add property: {:?}", e)))?;10431044 let create_item_data = CreateItemData::<T> {1045 users: users.clone(),1046 properties,1047 };1048 data.push(create_item_data);1049 }10501051 <Pallet<T>>::create_multiple_items(self, &caller, data, &budget)1052 .map_err(dispatch_to_evm::<T>)?;1053 Ok(true)1054 }10551056 /// @notice Function to mint a token.1057 /// @param to The new owner crossAccountId1058 /// @param properties Properties of minted token1059 /// @return uint256 The id of the newly minted token1060 #[weight(<SelfWeightOf<T>>::create_item() + <SelfWeightOf<T>>::set_token_properties(properties.len() as u32))]1061 fn mint_cross(1062 &mut self,1063 caller: Caller,1064 to: eth::CrossAddress,1065 properties: Vec<eth::Property>,1066 ) -> Result<U256> {1067 let token_id = <TokensMinted<T>>::get(self.id)1068 .checked_add(1)1069 .ok_or("item id overflow")?;10701071 let to = to.into_sub_cross_account::<T>()?;10721073 let properties = properties1074 .into_iter()1075 .map(eth::Property::try_into)1076 .collect::<Result<Vec<_>>>()?1077 .try_into()1078 .map_err(|_| Error::Revert(alloc::format!("too many properties")))?;10791080 let caller = T::CrossAccountId::from_eth(caller);10811082 let budget = self1083 .recorder1084 .weight_calls_budget(<StructureWeight<T>>::find_parent());10851086 let users = [(to, 1)]1087 .into_iter()1088 .collect::<BTreeMap<_, _>>()1089 .try_into()1090 .unwrap();1091 <Pallet<T>>::create_item(1092 self,1093 &caller,1094 CreateItemData::<T> { users, properties },1095 &budget,1096 )1097 .map_err(dispatch_to_evm::<T>)?;10981099 Ok(token_id.into())1100 }11011102 /// Returns EVM address for refungible token1103 ///1104 /// @param token ID of the token1105 fn token_contract_address(&self, token: U256) -> Result<Address> {1106 Ok(T::EvmTokenAddressMapping::token_to_address(1107 self.id,1108 token.try_into().map_err(|_| "token id overflow")?,1109 ))1110 }11111112 /// @notice Returns collection helper contract address1113 fn collection_helper_address(&self) -> Result<Address> {1114 Ok(T::ContractAddress::get())1115 }1116}11171118#[solidity_interface(1119 name = UniqueRefungible,1120 is(1121 ERC721,1122 ERC721Enumerable,1123 ERC721UniqueExtensions,1124 ERC721UniqueMintable,1125 ERC721Burnable,1126 ERC721Metadata(if(this.flags.erc721metadata)),1127 Collection(via(common_mut returns CollectionHandle<T>)),1128 TokenProperties,1129 ),1130 enum(derive(PreDispatch)),1131)]1132impl<T: Config> RefungibleHandle<T> where T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]> {}11331134// Not a tests, but code generators1135generate_stubgen!(gen_impl, UniqueRefungibleCall<()>, true);1136generate_stubgen!(gen_iface, UniqueRefungibleCall<()>, false);11371138impl<T: Config> CommonEvmHandler for RefungibleHandle<T>1139where1140 T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>,1141{1142 const CODE: &'static [u8] = include_bytes!("./stubs/UniqueRefungible.raw");1143 fn call(1144 self,1145 handle: &mut impl PrecompileHandle,1146 ) -> Option<pallet_common::erc::PrecompileResult> {1147 call::<T, UniqueRefungibleCall<T>, _, _>(handle, self)1148 }1149}pallets/refungible/src/lib.rsdiffbeforeafterboth--- a/pallets/refungible/src/lib.rs
+++ b/pallets/refungible/src/lib.rs
@@ -100,7 +100,7 @@
Event as CommonEvent, Pallet as PalletCommon,
};
use pallet_structure::Pallet as PalletStructure;
-use sp_core::H160;
+use sp_core::{Get, H160};
use sp_runtime::{ArithmeticError, DispatchError, DispatchResult, TransactionOutcome};
use sp_std::{vec::Vec, vec, collections::btree_map::BTreeMap};
use up_data_structs::{
@@ -571,6 +571,11 @@
stored_properties,
is_token_owner,
|properties| <TokenProperties<T>>::set((collection.id, token_id), properties),
+ erc::ERC721TokenEvent::TokenChanged {
+ collection_id: collection_id_to_address(collection.id),
+ token_id: token_id.into(),
+ }
+ .to_log(T::ContractAddress::get()),
)
}
pallets/refungible/src/stubs/UniqueRefungible.soldiffbeforeafterboth--- a/pallets/refungible/src/stubs/UniqueRefungible.sol
+++ b/pallets/refungible/src/stubs/UniqueRefungible.sol
@@ -17,9 +17,14 @@
}
}
+/// @dev inlined interface
+contract ERC721TokenEvent {
+ event TokenChanged(address indexed collectionId, uint256 tokenId);
+}
+
/// @title A contract that allows to set and delete token properties and change token property permissions.
/// @dev the ERC-165 identifier for this interface is 0xde0695c2
-contract TokenProperties is Dummy, ERC165 {
+contract TokenProperties is Dummy, ERC165, ERC721TokenEvent {
// /// @notice Set permissions for token property.
// /// @dev Throws error if `msg.sender` is not admin or owner of the collection.
// /// @param key Property key.
pallets/unique/src/eth/stubs/CollectionHelpers.soldiffbeforeafterboth--- a/pallets/unique/src/eth/stubs/CollectionHelpers.sol
+++ b/pallets/unique/src/eth/stubs/CollectionHelpers.sol
@@ -22,7 +22,6 @@
event CollectionCreated(address indexed owner, address indexed collectionId);
event CollectionDestroyed(address indexed collectionId);
event CollectionChanged(address indexed collectionId);
- event TokenChanged(address indexed collectionId, uint256 tokenId);
}
/// @title Contract, which allows users to operate with collections
tests/src/eth/abi/collectionHelpers.jsondiffbeforeafterboth--- a/tests/src/eth/abi/collectionHelpers.json
+++ b/tests/src/eth/abi/collectionHelpers.json
@@ -45,25 +45,6 @@
"type": "event"
},
{
- "anonymous": false,
- "inputs": [
- {
- "indexed": true,
- "internalType": "address",
- "name": "collectionId",
- "type": "address"
- },
- {
- "indexed": false,
- "internalType": "uint256",
- "name": "tokenId",
- "type": "uint256"
- }
- ],
- "name": "TokenChanged",
- "type": "event"
- },
- {
"inputs": [
{ "internalType": "uint32", "name": "collectionId", "type": "uint32" }
],
tests/src/eth/abi/nonFungible.jsondiffbeforeafterboth--- a/tests/src/eth/abi/nonFungible.json
+++ b/tests/src/eth/abi/nonFungible.json
@@ -55,6 +55,25 @@
{
"indexed": true,
"internalType": "address",
+ "name": "collectionId",
+ "type": "address"
+ },
+ {
+ "indexed": false,
+ "internalType": "uint256",
+ "name": "tokenId",
+ "type": "uint256"
+ }
+ ],
+ "name": "TokenChanged",
+ "type": "event"
+ },
+ {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": true,
+ "internalType": "address",
"name": "from",
"type": "address"
},
tests/src/eth/abi/reFungible.jsondiffbeforeafterboth--- a/tests/src/eth/abi/reFungible.json
+++ b/tests/src/eth/abi/reFungible.json
@@ -55,6 +55,25 @@
{
"indexed": true,
"internalType": "address",
+ "name": "collectionId",
+ "type": "address"
+ },
+ {
+ "indexed": false,
+ "internalType": "uint256",
+ "name": "tokenId",
+ "type": "uint256"
+ }
+ ],
+ "name": "TokenChanged",
+ "type": "event"
+ },
+ {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": true,
+ "internalType": "address",
"name": "from",
"type": "address"
},
tests/src/eth/api/CollectionHelpers.soldiffbeforeafterboth--- a/tests/src/eth/api/CollectionHelpers.sol
+++ b/tests/src/eth/api/CollectionHelpers.sol
@@ -17,7 +17,6 @@
event CollectionCreated(address indexed owner, address indexed collectionId);
event CollectionDestroyed(address indexed collectionId);
event CollectionChanged(address indexed collectionId);
- event TokenChanged(address indexed collectionId, uint256 tokenId);
}
/// @title Contract, which allows users to operate with collections
tests/src/eth/api/UniqueNFT.soldiffbeforeafterboth--- a/tests/src/eth/api/UniqueNFT.sol
+++ b/tests/src/eth/api/UniqueNFT.sol
@@ -12,9 +12,14 @@
function supportsInterface(bytes4 interfaceID) external view returns (bool);
}
+/// @dev inlined interface
+interface ERC721TokenEvent {
+ event TokenChanged(address indexed collectionId, uint256 tokenId);
+}
+
/// @title A contract that allows to set and delete token properties and change token property permissions.
/// @dev the ERC-165 identifier for this interface is 0xde0695c2
-interface TokenProperties is Dummy, ERC165 {
+interface TokenProperties is Dummy, ERC165, ERC721TokenEvent {
// /// @notice Set permissions for token property.
// /// @dev Throws error if `msg.sender` is not admin or owner of the collection.
// /// @param key Property key.
tests/src/eth/api/UniqueRefungible.soldiffbeforeafterboth--- a/tests/src/eth/api/UniqueRefungible.sol
+++ b/tests/src/eth/api/UniqueRefungible.sol
@@ -12,9 +12,14 @@
function supportsInterface(bytes4 interfaceID) external view returns (bool);
}
+/// @dev inlined interface
+interface ERC721TokenEvent {
+ event TokenChanged(address indexed collectionId, uint256 tokenId);
+}
+
/// @title A contract that allows to set and delete token properties and change token property permissions.
/// @dev the ERC-165 identifier for this interface is 0xde0695c2
-interface TokenProperties is Dummy, ERC165 {
+interface TokenProperties is Dummy, ERC165, ERC721TokenEvent {
// /// @notice Set permissions for token property.
// /// @dev Throws error if `msg.sender` is not admin or owner of the collection.
// /// @param key Property key.
tests/src/eth/events.test.tsdiffbeforeafterboth--- a/tests/src/eth/events.test.ts
+++ b/tests/src/eth/events.test.ts
@@ -29,8 +29,10 @@
});
});
-function clearEvents(ethEvents: NormalizedEvent[], subEvents: IEvent[]) {
- ethEvents.splice(0);
+function clearEvents(ethEvents: NormalizedEvent[] | null, subEvents: IEvent[]) {
+ if (ethEvents !== null) {
+ ethEvents.splice(0);
+ }
subEvents.splice(0);
}
@@ -374,7 +376,6 @@
const owner = await helper.eth.createAccountWithBalance(donor);
const {collectionAddress} = await helper.eth.createCollection(mode, owner, 'A', 'B', 'C');
const collection = await helper.ethNativeContract.collection(collectionAddress, mode, owner);
- const collectionHelper = await helper.ethNativeContract.collectionHelpers(owner);
const result = await collection.methods.mint(owner).send({from: owner});
const tokenId = result.events.Transfer.returnValues.tokenId;
await collection.methods.setTokenPropertyPermissions([
@@ -384,38 +385,31 @@
[TokenPermissionField.CollectionAdmin, true]],
],
]).send({from: owner});
-
- const ethEvents: any = [];
- collectionHelper.events.allEvents((_: any, event: any) => {
- ethEvents.push(event);
- });
const {unsubscribe, collectedEvents: subEvents} = await helper.subscribeEvents([{section: 'common', names: ['TokenPropertySet', 'TokenPropertyDeleted']}]);
{
- await collection.methods.setProperties(tokenId, [{key: 'A', value: [1,2,3]}]).send({from: owner});
+ const result = await collection.methods.setProperties(tokenId, [{key: 'A', value: [1,2,3]}]).send({from: owner});
await helper.wait.newBlocks(1);
- expect(ethEvents).to.containSubset([
- {
- event: 'TokenChanged',
- returnValues: {
- collectionId: collectionAddress,
- },
+ expect(result.events.TokenChanged).to.be.like({
+ event: 'TokenChanged',
+ returnValues: {
+ collectionId: collectionAddress,
+ tokenId: tokenId,
},
- ]);
+ });
expect(subEvents).to.containSubset([{method: 'TokenPropertySet'}]);
- clearEvents(ethEvents, subEvents);
+ clearEvents(null, subEvents);
}
{
- await collection.methods.deleteProperties(tokenId, ['A']).send({from: owner});
+ const result = await collection.methods.deleteProperties(tokenId, ['A']).send({from: owner});
await helper.wait.newBlocks(1);
- expect(ethEvents).to.containSubset([
- {
- event: 'TokenChanged',
- returnValues: {
- collectionId: collectionAddress,
- },
+ expect(result.events.TokenChanged).to.be.like({
+ event: 'TokenChanged',
+ returnValues: {
+ collectionId: collectionAddress,
+ tokenId: tokenId,
},
- ]);
+ });
expect(subEvents).to.containSubset([{method: 'TokenPropertyDeleted'}]);
}
unsubscribe();