difftreelog
Merge remote-tracking branch 'origin/feature/erc-721-for-refungible' into develop
in: master
13 files changed
Cargo.lockdiffbeforeafterboth631163116312[[package]]6312[[package]]6313name = "pallet-refungible"6313name = "pallet-refungible"6314version = "0.2.0"6314version = "0.2.1"6315dependencies = [6315dependencies = [6316 "ethereum",6316 "ethereum",6317 "evm-coder",6317 "evm-coder",Makefilediffbeforeafterboth15NONFUNGIBLE_EVM_STUBS=./pallets/nonfungible/src/stubs15NONFUNGIBLE_EVM_STUBS=./pallets/nonfungible/src/stubs16NONFUNGIBLE_EVM_ABI=./tests/src/eth/nonFungibleAbi.json16NONFUNGIBLE_EVM_ABI=./tests/src/eth/nonFungibleAbi.json171718REFUNGIBLE_EVM_STUBS=./pallets/refungible/src/stubs18RENFUNGIBLE_EVM_ABI=./tests/src/eth/reFungibleAbi.json19REFUNGIBLE_EVM_ABI=./tests/src/eth/reFungibleAbi.json19RENFUNGIBLE_TOKEN_EVM_ABI=./tests/src/eth/reFungibleTokenAbi.json20REFUNGIBLE_TOKEN_EVM_ABI=./tests/src/eth/reFungibleTokenAbi.json202121CONTRACT_HELPERS_STUBS=./pallets/evm-contract-helpers/src/stubs/22CONTRACT_HELPERS_STUBS=./pallets/evm-contract-helpers/src/stubs/22CONTRACT_HELPERS_ABI=./tests/src/eth/util/contractHelpersAbi.json23CONTRACT_HELPERS_ABI=./tests/src/eth/util/contractHelpersAbi.json36UniqueNFT.sol:37UniqueNFT.sol:37 PACKAGE=pallet-nonfungible NAME=erc::gen_iface OUTPUT=$(TESTS_API)/$@ ./.maintain/scripts/generate_sol.sh38 PACKAGE=pallet-nonfungible NAME=erc::gen_iface OUTPUT=$(TESTS_API)/$@ ./.maintain/scripts/generate_sol.sh38 PACKAGE=pallet-nonfungible NAME=erc::gen_impl OUTPUT=$(NONFUNGIBLE_EVM_STUBS)/$@ ./.maintain/scripts/generate_sol.sh39 PACKAGE=pallet-nonfungible NAME=erc::gen_impl OUTPUT=$(NONFUNGIBLE_EVM_STUBS)/$@ ./.maintain/scripts/generate_sol.sh39 4040UniqueRefungible.sol:41UniqueRefungible.sol:41 PACKAGE=pallet-refungible NAME=erc::gen_iface OUTPUT=$(TESTS_API)/$@ ./.maintain/scripts/generate_sol.sh42 PACKAGE=pallet-refungible NAME=erc::gen_iface OUTPUT=$(TESTS_API)/$@ ./.maintain/scripts/generate_sol.sh42 PACKAGE=pallet-refungible NAME=erc::gen_impl OUTPUT=$(REFUNGIBLE_EVM_STUBS)/$@ ./.maintain/scripts/generate_sol.sh43 PACKAGE=pallet-refungible NAME=erc::gen_impl OUTPUT=$(REFUNGIBLE_EVM_STUBS)/$@ ./.maintain/scripts/generate_sol.sh61 INPUT=$(NONFUNGIBLE_EVM_STUBS)/$< OUTPUT=$(NONFUNGIBLE_EVM_STUBS)/UniqueNFT.raw ./.maintain/scripts/compile_stub.sh62 INPUT=$(NONFUNGIBLE_EVM_STUBS)/$< OUTPUT=$(NONFUNGIBLE_EVM_STUBS)/UniqueNFT.raw ./.maintain/scripts/compile_stub.sh62 INPUT=$(NONFUNGIBLE_EVM_STUBS)/$< OUTPUT=$(NONFUNGIBLE_EVM_ABI) ./.maintain/scripts/generate_abi.sh63 INPUT=$(NONFUNGIBLE_EVM_STUBS)/$< OUTPUT=$(NONFUNGIBLE_EVM_ABI) ./.maintain/scripts/generate_abi.sh636464UniqueRefungibleToken: UniqueRefungibleToken.sol65UniqueRefungible: UniqueRefungible.sol65 INPUT=$(REFUNGIBLE_EVM_STUBS)/$< OUTPUT=$(REFUNGIBLE_EVM_STUBS)/UniqueRefungibleToken.raw ./.maintain/scripts/compile_stub.sh66 INPUT=$(REFUNGIBLE_EVM_STUBS)/$< OUTPUT=$(REFUNGIBLE_EVM_STUBS)/UniqueRefungible.raw ./.maintain/scripts/compile_stub.sh66 INPUT=$(REFUNGIBLE_EVM_STUBS)/$< OUTPUT=$(RENFUNGIBLE_TOKEN_EVM_ABI) ./.maintain/scripts/generate_abi.sh67 INPUT=$(REFUNGIBLE_EVM_STUBS)/$< OUTPUT=$(REFUNGIBLE_EVM_ABI) ./.maintain/scripts/generate_abi.sh676868UniqueRefungible: UniqueRefungible.sol69UniqueRefungibleToken: UniqueRefungibleToken.sol69 INPUT=$(REFUNGIBLE_EVM_STUBS)/$< OUTPUT=$(REFUNGIBLE_EVM_STUBS)/UniqueRefungible.raw ./.maintain/scripts/compile_stub.sh70 INPUT=$(REFUNGIBLE_EVM_STUBS)/$< OUTPUT=$(REFUNGIBLE_EVM_STUBS)/UniqueRefungibleToken.raw ./.maintain/scripts/compile_stub.sh70 INPUT=$(REFUNGIBLE_EVM_STUBS)/$< OUTPUT=$(REFUNGIBLE_EVM_ABI) ./.maintain/scripts/generate_abi.sh71 INPUT=$(REFUNGIBLE_EVM_STUBS)/$< OUTPUT=$(REFUNGIBLE_TOKEN_EVM_ABI) ./.maintain/scripts/generate_abi.sh717272ContractHelpers: ContractHelpers.sol73ContractHelpers: ContractHelpers.sol73 INPUT=$(CONTRACT_HELPERS_STUBS)/$< OUTPUT=$(CONTRACT_HELPERS_STUBS)/ContractHelpers.raw ./.maintain/scripts/compile_stub.sh74 INPUT=$(CONTRACT_HELPERS_STUBS)/$< OUTPUT=$(CONTRACT_HELPERS_STUBS)/ContractHelpers.raw ./.maintain/scripts/compile_stub.shpallets/refungible/CHANGELOG.mddiffbeforeafterboth223All notable changes to this project will be documented in this file.3All notable changes to this project will be documented in this file.445## [v0.2.1] - 2022-07-2767### New features89Implementation of ERC-721 EVM API ([#452](https://github.com/UniqueNetwork/unique-chain/pull/452))105## [v0.2.0] - 2022-08-0111## [v0.2.0] - 2022-08-01126### Deprecated13### Deprecated1415`const_data` field is removed167- `ItemData`17- `ItemData`8- `TokenData`18- `TokenData`91915test(refungible-pallet): add tests for ERC-20 EVM API for RFT token pieces ([#413](https://github.com/UniqueNetwork/unique-chain/pull/413))25test(refungible-pallet): add tests for ERC-20 EVM API for RFT token pieces ([#413](https://github.com/UniqueNetwork/unique-chain/pull/413))162617## [v0.1.1] - 2022-07-1427## [v0.1.1] - 2022-07-142818### Added29### Added features3019- Support for properties for RFT collections and tokens.31- Support for properties for RFT collections and tokens.203221### Other changes33### Other changes223423- feat: RPC method `token_owners` returning 10 owners in no particular order.35- feat: RPC method `token_owners` returning 10 owners in no particular order.243625This was an internal request to improve the web interface and support fractionalization event. 37This was an internal request to improve the web interface and support fractionalization event.2638pallets/refungible/Cargo.tomldiffbeforeafterboth1[package]1[package]2name = "pallet-refungible"2name = "pallet-refungible"3version = "0.2.0"3version = "0.2.1"4license = "GPLv3"4license = "GPLv3"5edition = "2021"5edition = "2021"66pallets/refungible/src/erc.rsdiffbeforeafterboth14// You should have received a copy of the GNU General Public License14// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.15// 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.162117extern crate alloc;22extern crate alloc;2324use alloc::string::ToString;25use core::{26 char::{REPLACEMENT_CHARACTER, decode_utf16},27 convert::TryInto,28};18use evm_coder::{generate_stubgen, solidity_interface, types::*};29use evm_coder::{ToLog, execution::*, generate_stubgen, solidity, solidity_interface, types::*, weight};1930use frame_support::{BoundedBTreeMap, BoundedVec};20use pallet_common::{CollectionHandle, erc::CollectionCall, erc::CommonEvmHandler};31use pallet_common::{2132 CollectionHandle, CollectionPropertyPermissions,33 erc::{34 CommonEvmHandler, CollectionCall,35 static_property::{key, value as property_value},36 },37};22use pallet_evm::PrecompileHandle;38use pallet_evm::{account::CrossAccountId, PrecompileHandle};23use pallet_evm_coder_substrate::call;39use pallet_evm_coder_substrate::{call, dispatch_to_evm};40use pallet_structure::{SelfWeightOf as StructureWeight, weights::WeightInfo as _};41use sp_core::H160;42use sp_std::{collections::btree_map::BTreeMap, vec::Vec, vec};43use up_data_structs::{44 CollectionId, CollectionPropertiesVec, Property, PropertyKey, PropertyKeyPermission,45 PropertyPermission, TokenId,46};244725use crate::{Config, RefungibleHandle};48use crate::{49 AccountBalance, Balance, Config, CreateItemData, Pallet, RefungibleHandle, SelfWeightOf,50 TokenProperties, TokensMinted, TotalSupply, weights::WeightInfo,51};5253pub const ADDRESS_FOR_PARTIALLY_OWNED_TOKENS: H160 = H160::repeat_byte(0xff);5455/// @title A contract that allows to set and delete token properties and change token property permissions.56#[solidity_interface(name = "TokenProperties")]57impl<T: Config> RefungibleHandle<T> {58 /// @notice Set permissions for token property.59 /// @dev Throws error if `msg.sender` is not admin or owner of the collection.60 /// @param key Property key.61 /// @param is_mutable Permission to mutate property.62 /// @param collection_admin Permission to mutate property by collection admin if property is mutable.63 /// @param token_owner Permission to mutate property by token owner if property is mutable.64 fn set_token_property_permission(65 &mut self,66 caller: caller,67 key: string,68 is_mutable: bool,69 collection_admin: bool,70 token_owner: bool,71 ) -> Result<()> {72 let caller = T::CrossAccountId::from_eth(caller);73 <Pallet<T>>::set_token_property_permissions(74 self,75 &caller,76 vec![PropertyKeyPermission {77 key: <Vec<u8>>::from(key)78 .try_into()79 .map_err(|_| "too long key")?,80 permission: PropertyPermission {81 mutable: is_mutable,82 collection_admin,83 token_owner,84 },85 }],86 )87 .map_err(dispatch_to_evm::<T>)88 }8990 /// @notice Set token property value.91 /// @dev Throws error if `msg.sender` has no permission to edit the property.92 /// @param tokenId ID of the token.93 /// @param key Property key.94 /// @param value Property value.95 fn set_property(96 &mut self,97 caller: caller,98 token_id: uint256,99 key: string,100 value: bytes,101 ) -> Result<()> {102 let caller = T::CrossAccountId::from_eth(caller);103 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;104 let key = <Vec<u8>>::from(key)105 .try_into()106 .map_err(|_| "key too long")?;107 let value = value.try_into().map_err(|_| "value too long")?;108109 let nesting_budget = self110 .recorder111 .weight_calls_budget(<StructureWeight<T>>::find_parent());112113 <Pallet<T>>::set_token_property(114 self,115 &caller,116 TokenId(token_id),117 Property { key, value },118 &nesting_budget,119 )120 .map_err(dispatch_to_evm::<T>)121 }122123 /// @notice Delete token property value.124 /// @dev Throws error if `msg.sender` has no permission to edit the property.125 /// @param tokenId ID of the token.126 /// @param key Property key.127 fn delete_property(&mut self, token_id: uint256, caller: caller, key: string) -> Result<()> {128 let caller = T::CrossAccountId::from_eth(caller);129 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;130 let key = <Vec<u8>>::from(key)131 .try_into()132 .map_err(|_| "key too long")?;133134 let nesting_budget = self135 .recorder136 .weight_calls_budget(<StructureWeight<T>>::find_parent());137138 <Pallet<T>>::delete_token_property(self, &caller, TokenId(token_id), key, &nesting_budget)139 .map_err(dispatch_to_evm::<T>)140 }141142 /// @notice Get token property value.143 /// @dev Throws error if key not found144 /// @param tokenId ID of the token.145 /// @param key Property key.146 /// @return Property value bytes147 fn property(&self, token_id: uint256, key: string) -> Result<bytes> {148 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;149 let key = <Vec<u8>>::from(key)150 .try_into()151 .map_err(|_| "key too long")?;152153 let props = <TokenProperties<T>>::get((self.id, token_id));154 let prop = props.get(&key).ok_or("key not found")?;155156 Ok(prop.to_vec())157 }158}159160#[derive(ToLog)]161pub enum ERC721Events {162 /// @dev This event emits when NFTs are created (`from` == 0) and destroyed163 /// (`to` == 0). Exception: during contract creation, any number of RFTs164 /// may be created and assigned without emitting Transfer.165 Transfer {166 #[indexed]167 from: address,168 #[indexed]169 to: address,170 #[indexed]171 token_id: uint256,172 },173 /// @dev Not supported174 Approval {175 #[indexed]176 owner: address,177 #[indexed]178 approved: address,179 #[indexed]180 token_id: uint256,181 },182 /// @dev Not supported183 #[allow(dead_code)]184 ApprovalForAll {185 #[indexed]186 owner: address,187 #[indexed]188 operator: address,189 approved: bool,190 },191}192193#[derive(ToLog)]194pub enum ERC721MintableEvents {195 /// @dev Not supported196 #[allow(dead_code)]197 MintingFinished {},198}199200#[solidity_interface(name = "ERC721Metadata")]201impl<T: Config> RefungibleHandle<T> {202 /// @notice A descriptive name for a collection of RFTs in this contract203 fn name(&self) -> Result<string> {204 Ok(decode_utf16(self.name.iter().copied())205 .map(|r| r.unwrap_or(REPLACEMENT_CHARACTER))206 .collect::<string>())207 }208209 /// @notice An abbreviated name for RFTs in this contract210 fn symbol(&self) -> Result<string> {211 Ok(string::from_utf8_lossy(&self.token_prefix).into())212 }213214 /// @notice A distinct Uniform Resource Identifier (URI) for a given asset.215 ///216 /// @dev If the token has a `url` property and it is not empty, it is returned.217 /// 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`.218 /// If the collection property `baseURI` is empty or absent, return "" (empty string)219 /// otherwise, if token property `suffix` present and is non-empty, return concatenation of baseURI and suffix220 /// otherwise, return concatenation of `baseURI` and stringified token id (decimal stringifying, without paddings).221 ///222 /// @return token's const_metadata223 #[solidity(rename_selector = "tokenURI")]224 fn token_uri(&self, token_id: uint256) -> Result<string> {225 let token_id_u32: u32 = token_id.try_into().map_err(|_| "token id overflow")?;226227 if let Ok(url) = get_token_property(self, token_id_u32, &key::url()) {228 if !url.is_empty() {229 return Ok(url);230 }231 } else if !is_erc721_metadata_compatible::<T>(self.id) {232 return Err("tokenURI not set".into());233 }234235 if let Some(base_uri) =236 pallet_common::Pallet::<T>::get_collection_property(self.id, &key::base_uri())237 {238 if !base_uri.is_empty() {239 let base_uri = string::from_utf8(base_uri.into_inner()).map_err(|e| {240 Error::Revert(alloc::format!(241 "Can not convert value \"baseURI\" to string with error \"{}\"",242 e243 ))244 })?;245 if let Ok(suffix) = get_token_property(self, token_id_u32, &key::suffix()) {246 if !suffix.is_empty() {247 return Ok(base_uri + suffix.as_str());248 }249 }250251 return Ok(base_uri + token_id.to_string().as_str());252 }253 }254255 Ok("".into())256 }257}258259/// @title ERC-721 Non-Fungible Token Standard, optional enumeration extension260/// @dev See https://eips.ethereum.org/EIPS/eip-721261#[solidity_interface(name = "ERC721Enumerable")]262impl<T: Config> RefungibleHandle<T> {263 /// @notice Enumerate valid RFTs264 /// @param index A counter less than `totalSupply()`265 /// @return The token identifier for the `index`th NFT,266 /// (sort order not specified)267 fn token_by_index(&self, index: uint256) -> Result<uint256> {268 Ok(index)269 }270271 /// Not implemented272 fn token_of_owner_by_index(&self, _owner: address, _index: uint256) -> Result<uint256> {273 // TODO: Not implemetable274 Err("not implemented".into())275 }276277 /// @notice Count RFTs tracked by this contract278 /// @return A count of valid RFTs tracked by this contract, where each one of279 /// them has an assigned and queryable owner not equal to the zero address280 fn total_supply(&self) -> Result<uint256> {281 self.consume_store_reads(1)?;282 Ok(<Pallet<T>>::total_supply(self).into())283 }284}285286/// @title ERC-721 Non-Fungible Token Standard287/// @dev See https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md288#[solidity_interface(name = "ERC721", events(ERC721Events))]289impl<T: Config> RefungibleHandle<T> {290 /// @notice Count all RFTs assigned to an owner291 /// @dev RFTs assigned to the zero address are considered invalid, and this292 /// function throws for queries about the zero address.293 /// @param owner An address for whom to query the balance294 /// @return The number of RFTs owned by `owner`, possibly zero295 fn balance_of(&self, owner: address) -> Result<uint256> {296 self.consume_store_reads(1)?;297 let owner = T::CrossAccountId::from_eth(owner);298 let balance = <AccountBalance<T>>::get((self.id, owner));299 Ok(balance.into())300 }301302 /// @notice Find the owner of an RFT303 /// @dev RFTs assigned to zero address are considered invalid, and queries304 /// about them do throw.305 /// Returns special 0xffffffffffffffffffffffffffffffffffffffff address for306 /// the tokens that are partially owned.307 /// @param tokenId The identifier for an RFT308 /// @return The address of the owner of the RFT309 fn owner_of(&self, token_id: uint256) -> Result<address> {310 self.consume_store_reads(2)?;311 let token = token_id.try_into()?;312 let owner = <Pallet<T>>::token_owner(self.id, token);313 Ok(owner314 .map(|address| *address.as_eth())315 .unwrap_or_else(|| ADDRESS_FOR_PARTIALLY_OWNED_TOKENS))316 }317318 /// @dev Not implemented319 fn safe_transfer_from_with_data(320 &mut self,321 _from: address,322 _to: address,323 _token_id: uint256,324 _data: bytes,325 _value: value,326 ) -> Result<void> {327 // TODO: Not implemetable328 Err("not implemented".into())329 }330331 /// @dev Not implemented332 fn safe_transfer_from(333 &mut self,334 _from: address,335 _to: address,336 _token_id: uint256,337 _value: value,338 ) -> Result<void> {339 // TODO: Not implemetable340 Err("not implemented".into())341 }342343 /// @notice Transfer ownership of an RFT -- THE CALLER IS RESPONSIBLE344 /// TO CONFIRM THAT `to` IS CAPABLE OF RECEIVING NFTS OR ELSE345 /// THEY MAY BE PERMANENTLY LOST346 /// @dev Throws unless `msg.sender` is the current owner or an authorized347 /// operator for this RFT. Throws if `from` is not the current owner. Throws348 /// if `to` is the zero address. Throws if `tokenId` is not a valid RFT.349 /// Throws if RFT pieces have multiple owners.350 /// @param from The current owner of the NFT351 /// @param to The new owner352 /// @param tokenId The NFT to transfer353 /// @param _value Not used for an NFT354 #[weight(<SelfWeightOf<T>>::transfer_from_creating_removing())]355 fn transfer_from(356 &mut self,357 caller: caller,358 from: address,359 to: address,360 token_id: uint256,361 _value: value,362 ) -> Result<void> {363 let caller = T::CrossAccountId::from_eth(caller);364 let from = T::CrossAccountId::from_eth(from);365 let to = T::CrossAccountId::from_eth(to);366 let token = token_id.try_into()?;367 let budget = self368 .recorder369 .weight_calls_budget(<StructureWeight<T>>::find_parent());370371 let balance = balance(&self, token, &from)?;372 ensure_single_owner(&self, token, balance)?;373374 <Pallet<T>>::transfer_from(self, &caller, &from, &to, token, balance, &budget)375 .map_err(dispatch_to_evm::<T>)?;376377 Ok(())378 }379380 /// @dev Not implemented381 fn approve(382 &mut self,383 _caller: caller,384 _approved: address,385 _token_id: uint256,386 _value: value,387 ) -> Result<void> {388 Err("not implemented".into())389 }390391 /// @dev Not implemented392 fn set_approval_for_all(393 &mut self,394 _caller: caller,395 _operator: address,396 _approved: bool,397 ) -> Result<void> {398 // TODO: Not implemetable399 Err("not implemented".into())400 }401402 /// @dev Not implemented403 fn get_approved(&self, _token_id: uint256) -> Result<address> {404 // TODO: Not implemetable405 Err("not implemented".into())406 }407408 /// @dev Not implemented409 fn is_approved_for_all(&self, _owner: address, _operator: address) -> Result<address> {410 // TODO: Not implemetable411 Err("not implemented".into())412 }413}414415/// Returns amount of pieces of `token` that `owner` have416fn balance<T: Config>(417 collection: &RefungibleHandle<T>,418 token: TokenId,419 owner: &T::CrossAccountId,420) -> Result<u128> {421 collection.consume_store_reads(1)?;422 let balance = <Balance<T>>::get((collection.id, token, &owner));423 Ok(balance)424}425426/// Throws if `owner_balance` is lower than total amount of `token` pieces427fn ensure_single_owner<T: Config>(428 collection: &RefungibleHandle<T>,429 token: TokenId,430 owner_balance: u128,431) -> Result<()> {432 collection.consume_store_reads(1)?;433 let total_supply = <TotalSupply<T>>::get((collection.id, token));434 if total_supply != owner_balance {435 return Err("token has multiple owners".into());436 }437 Ok(())438}439440/// @title ERC721 Token that can be irreversibly burned (destroyed).441#[solidity_interface(name = "ERC721Burnable")]442impl<T: Config> RefungibleHandle<T> {443 /// @notice Burns a specific ERC721 token.444 /// @dev Throws unless `msg.sender` is the current RFT owner, or an authorized445 /// operator of the current owner.446 /// @param tokenId The RFT to approve447 #[weight(<SelfWeightOf<T>>::burn_item_fully())]448 fn burn(&mut self, caller: caller, token_id: uint256) -> Result<void> {449 let caller = T::CrossAccountId::from_eth(caller);450 let token = token_id.try_into()?;451452 let balance = balance(&self, token, &caller)?;453 ensure_single_owner(&self, token, balance)?;454455 <Pallet<T>>::burn(self, &caller, token, balance).map_err(dispatch_to_evm::<T>)?;456 Ok(())457 }458}459460/// @title ERC721 minting logic.461#[solidity_interface(name = "ERC721Mintable", events(ERC721MintableEvents))]462impl<T: Config> RefungibleHandle<T> {463 fn minting_finished(&self) -> Result<bool> {464 Ok(false)465 }466467 /// @notice Function to mint token.468 /// @dev `tokenId` should be obtained with `nextTokenId` method,469 /// unlike standard, you can't specify it manually470 /// @param to The new owner471 /// @param tokenId ID of the minted RFT472 #[weight(<SelfWeightOf<T>>::create_item())]473 fn mint(&mut self, caller: caller, to: address, token_id: uint256) -> Result<bool> {474 let caller = T::CrossAccountId::from_eth(caller);475 let to = T::CrossAccountId::from_eth(to);476 let token_id: u32 = token_id.try_into()?;477 let budget = self478 .recorder479 .weight_calls_budget(<StructureWeight<T>>::find_parent());480481 if <TokensMinted<T>>::get(self.id)482 .checked_add(1)483 .ok_or("item id overflow")?484 != token_id485 {486 return Err("item id should be next".into());487 }488489 let users = [(to.clone(), 1)]490 .into_iter()491 .collect::<BTreeMap<_, _>>()492 .try_into()493 .unwrap();494 <Pallet<T>>::create_item(495 self,496 &caller,497 CreateItemData::<T> {498 users,499 properties: CollectionPropertiesVec::default(),500 },501 &budget,502 )503 .map_err(dispatch_to_evm::<T>)?;504505 Ok(true)506 }507508 /// @notice Function to mint token with the given tokenUri.509 /// @dev `tokenId` should be obtained with `nextTokenId` method,510 /// unlike standard, you can't specify it manually511 /// @param to The new owner512 /// @param tokenId ID of the minted RFT513 /// @param tokenUri Token URI that would be stored in the RFT properties514 #[solidity(rename_selector = "mintWithTokenURI")]515 #[weight(<SelfWeightOf<T>>::create_item())]516 fn mint_with_token_uri(517 &mut self,518 caller: caller,519 to: address,520 token_id: uint256,521 token_uri: string,522 ) -> Result<bool> {523 let key = key::url();524 let permission = get_token_permission::<T>(self.id, &key)?;525 if !permission.collection_admin {526 return Err("Operation is not allowed".into());527 }528529 let caller = T::CrossAccountId::from_eth(caller);530 let to = T::CrossAccountId::from_eth(to);531 let token_id: u32 = token_id.try_into().map_err(|_| "amount overflow")?;532 let budget = self533 .recorder534 .weight_calls_budget(<StructureWeight<T>>::find_parent());535536 if <TokensMinted<T>>::get(self.id)537 .checked_add(1)538 .ok_or("item id overflow")?539 != token_id540 {541 return Err("item id should be next".into());542 }543544 let mut properties = CollectionPropertiesVec::default();545 properties546 .try_push(Property {547 key,548 value: token_uri549 .into_bytes()550 .try_into()551 .map_err(|_| "token uri is too long")?,552 })553 .map_err(|e| Error::Revert(alloc::format!("Can't add property: {:?}", e)))?;554555 let users = [(to.clone(), 1)]556 .into_iter()557 .collect::<BTreeMap<_, _>>()558 .try_into()559 .unwrap();560 <Pallet<T>>::create_item(561 self,562 &caller,563 CreateItemData::<T> {564 users,565 properties,566 },567 &budget,568 )569 .map_err(dispatch_to_evm::<T>)?;570 Ok(true)571 }572573 /// @dev Not implemented574 fn finish_minting(&mut self, _caller: caller) -> Result<bool> {575 Err("not implementable".into())576 }577}578579fn get_token_property<T: Config>(580 collection: &CollectionHandle<T>,581 token_id: u32,582 key: &up_data_structs::PropertyKey,583) -> Result<string> {584 collection.consume_store_reads(1)?;585 let properties = <TokenProperties<T>>::try_get((collection.id, token_id))586 .map_err(|_| Error::Revert("Token properties not found".into()))?;587 if let Some(property) = properties.get(key) {588 return Ok(string::from_utf8_lossy(property).into());589 }590591 Err("Property tokenURI not found".into())592}593594fn is_erc721_metadata_compatible<T: Config>(collection_id: CollectionId) -> bool {595 if let Some(shema_name) =596 pallet_common::Pallet::<T>::get_collection_property(collection_id, &key::schema_name())597 {598 let shema_name = shema_name.into_inner();599 shema_name == property_value::ERC721_METADATA600 } else {601 false602 }603}604605fn get_token_permission<T: Config>(606 collection_id: CollectionId,607 key: &PropertyKey,608) -> Result<PropertyPermission> {609 let token_property_permissions = CollectionPropertyPermissions::<T>::try_get(collection_id)610 .map_err(|_| Error::Revert("No permissions for collection".into()))?;611 let a = token_property_permissions612 .get(key)613 .map(Clone::clone)614 .ok_or_else(|| {615 let key = string::from_utf8(key.clone().into_inner()).unwrap_or_default();616 Error::Revert(alloc::format!("No permission for key {}", key))617 })?;618 Ok(a)619}620621/// @title Unique extensions for ERC721.622#[solidity_interface(name = "ERC721UniqueExtensions")]623impl<T: Config> RefungibleHandle<T> {624 /// @notice Transfer ownership of an RFT625 /// @dev Throws unless `msg.sender` is the current owner. Throws if `to`626 /// is the zero address. Throws if `tokenId` is not a valid RFT.627 /// Throws if RFT pieces have multiple owners.628 /// @param to The new owner629 /// @param tokenId The RFT to transfer630 /// @param _value Not used for an RFT631 #[weight(<SelfWeightOf<T>>::transfer_creating_removing())]632 fn transfer(633 &mut self,634 caller: caller,635 to: address,636 token_id: uint256,637 _value: value,638 ) -> Result<void> {639 let caller = T::CrossAccountId::from_eth(caller);640 let to = T::CrossAccountId::from_eth(to);641 let token = token_id.try_into()?;642 let budget = self643 .recorder644 .weight_calls_budget(<StructureWeight<T>>::find_parent());645646 let balance = balance(&self, token, &caller)?;647 ensure_single_owner(&self, token, balance)?;648649 <Pallet<T>>::transfer(self, &caller, &to, token, balance, &budget)650 .map_err(dispatch_to_evm::<T>)?;651 Ok(())652 }653654 /// @notice Burns a specific ERC721 token.655 /// @dev Throws unless `msg.sender` is the current owner or an authorized656 /// operator for this RFT. Throws if `from` is not the current owner. Throws657 /// if `to` is the zero address. Throws if `tokenId` is not a valid RFT.658 /// Throws if RFT pieces have multiple owners.659 /// @param from The current owner of the RFT660 /// @param tokenId The RFT to transfer661 /// @param _value Not used for an RFT662 #[weight(<SelfWeightOf<T>>::burn_from())]663 fn burn_from(664 &mut self,665 caller: caller,666 from: address,667 token_id: uint256,668 _value: value,669 ) -> Result<void> {670 let caller = T::CrossAccountId::from_eth(caller);671 let from = T::CrossAccountId::from_eth(from);672 let token = token_id.try_into()?;673 let budget = self674 .recorder675 .weight_calls_budget(<StructureWeight<T>>::find_parent());676677 let balance = balance(&self, token, &caller)?;678 ensure_single_owner(&self, token, balance)?;679680 <Pallet<T>>::burn_from(self, &caller, &from, token, balance, &budget)681 .map_err(dispatch_to_evm::<T>)?;682 Ok(())683 }684685 /// @notice Returns next free RFT ID.686 fn next_token_id(&self) -> Result<uint256> {687 self.consume_store_reads(1)?;688 Ok(<TokensMinted<T>>::get(self.id)689 .checked_add(1)690 .ok_or("item id overflow")?691 .into())692 }693694 /// @notice Function to mint multiple tokens.695 /// @dev `tokenIds` should be an array of consecutive numbers and first number696 /// should be obtained with `nextTokenId` method697 /// @param to The new owner698 /// @param tokenIds IDs of the minted RFTs699 #[weight(<SelfWeightOf<T>>::create_multiple_items(token_ids.len() as u32))]700 fn mint_bulk(&mut self, caller: caller, to: address, token_ids: Vec<uint256>) -> Result<bool> {701 let caller = T::CrossAccountId::from_eth(caller);702 let to = T::CrossAccountId::from_eth(to);703 let mut expected_index = <TokensMinted<T>>::get(self.id)704 .checked_add(1)705 .ok_or("item id overflow")?;706 let budget = self707 .recorder708 .weight_calls_budget(<StructureWeight<T>>::find_parent());709710 let total_tokens = token_ids.len();711 for id in token_ids.into_iter() {712 let id: u32 = id.try_into().map_err(|_| "token id overflow")?;713 if id != expected_index {714 return Err("item id should be next".into());715 }716 expected_index = expected_index.checked_add(1).ok_or("item id overflow")?;717 }718 let users = [(to.clone(), 1)]719 .into_iter()720 .collect::<BTreeMap<_, _>>()721 .try_into()722 .unwrap();723 let create_item_data = CreateItemData::<T> {724 users,725 properties: CollectionPropertiesVec::default(),726 };727 let data = (0..total_tokens)728 .map(|_| create_item_data.clone())729 .collect();730731 <Pallet<T>>::create_multiple_items(self, &caller, data, &budget)732 .map_err(dispatch_to_evm::<T>)?;733 Ok(true)734 }735736 /// @notice Function to mint multiple tokens with the given tokenUris.737 /// @dev `tokenIds` is array of pairs of token ID and token URI. Token IDs should be consecutive738 /// numbers and first number should be obtained with `nextTokenId` method739 /// @param to The new owner740 /// @param tokens array of pairs of token ID and token URI for minted tokens741 #[solidity(rename_selector = "mintBulkWithTokenURI")]742 #[weight(<SelfWeightOf<T>>::create_multiple_items(tokens.len() as u32))]743 fn mint_bulk_with_token_uri(744 &mut self,745 caller: caller,746 to: address,747 tokens: Vec<(uint256, string)>,748 ) -> Result<bool> {749 let key = key::url();750 let caller = T::CrossAccountId::from_eth(caller);751 let to = T::CrossAccountId::from_eth(to);752 let mut expected_index = <TokensMinted<T>>::get(self.id)753 .checked_add(1)754 .ok_or("item id overflow")?;755 let budget = self756 .recorder757 .weight_calls_budget(<StructureWeight<T>>::find_parent());758759 let mut data = Vec::with_capacity(tokens.len());760 let users: BoundedBTreeMap<_, _, _> = [(to.clone(), 1)]761 .into_iter()762 .collect::<BTreeMap<_, _>>()763 .try_into()764 .unwrap();765 for (id, token_uri) in tokens {766 let id: u32 = id.try_into().map_err(|_| "token id overflow")?;767 if id != expected_index {768 return Err("item id should be next".into());769 }770 expected_index = expected_index.checked_add(1).ok_or("item id overflow")?;771772 let mut properties = CollectionPropertiesVec::default();773 properties774 .try_push(Property {775 key: key.clone(),776 value: token_uri777 .into_bytes()778 .try_into()779 .map_err(|_| "token uri is too long")?,780 })781 .map_err(|e| Error::Revert(alloc::format!("Can't add property: {:?}", e)))?;782783 let create_item_data = CreateItemData::<T> {784 users: users.clone(),785 properties,786 };787 data.push(create_item_data);788 }789790 <Pallet<T>>::create_multiple_items(self, &caller, data, &budget)791 .map_err(dispatch_to_evm::<T>)?;792 Ok(true)793 }794}2679527#[solidity_interface(796#[solidity_interface(28 name = "UniqueRefungible",797 name = "UniqueRefungible",29 is(via("CollectionHandle<T>", common_mut, Collection),)798 is(799 ERC721,800 ERC721Metadata,801 ERC721Enumerable,802 ERC721UniqueExtensions,803 ERC721Mintable,804 ERC721Burnable,805 via("CollectionHandle<T>", common_mut, Collection),806 TokenProperties,807 )30)]808)]31impl<T: Config> RefungibleHandle<T> where T::AccountId: From<[u8; 32]> {}809impl<T: Config> RefungibleHandle<T> where T::AccountId: From<[u8; 32]> {}pallets/refungible/src/lib.rsdiffbeforeafterboth88#![cfg_attr(not(feature = "std"), no_std)]88#![cfg_attr(not(feature = "std"), no_std)]898990use crate::erc_token::ERC20Events;90use crate::erc_token::ERC20Events;91use crate::erc::ERC721Events;919292use codec::{Encode, Decode, MaxEncodedLen};93use codec::{Encode, Decode, MaxEncodedLen};93use core::ops::Deref;94use core::ops::Deref;97use pallet_evm_coder_substrate::WithRecorder;98use pallet_evm_coder_substrate::WithRecorder;98use pallet_common::{99use pallet_common::{99 CommonCollectionOperations, Error as CommonError, Event as CommonEvent, Pallet as PalletCommon,100 CommonCollectionOperations, Error as CommonError, Event as CommonEvent,101 eth::collection_id_to_address, Pallet as PalletCommon,100};102};101use pallet_structure::Pallet as PalletStructure;103use pallet_structure::Pallet as PalletStructure;102use scale_info::TypeInfo;104use scale_info::TypeInfo;118pub mod erc_token;120pub mod erc_token;119pub mod weights;121pub mod weights;122123pub type CreateItemData<T> =124 CreateRefungibleExData<<T as pallet_evm::account::Config>::CrossAccountId>;120pub(crate) type SelfWeightOf<T> = <T as Config>::WeightInfo;125pub(crate) type SelfWeightOf<T> = <T as Config>::WeightInfo;121126122/// Token data, stored independently from other data used to describe it127/// Token data, stored independently from other data used to describe it396401397 pub fn burn_token_unchecked(402 pub fn burn_token_unchecked(398 collection: &RefungibleHandle<T>,403 collection: &RefungibleHandle<T>,404 owner: &T::CrossAccountId,399 token_id: TokenId,405 token_id: TokenId,400 ) -> DispatchResult {406 ) -> DispatchResult {401 let burnt = <TokensBurnt<T>>::get(collection.id)407 let burnt = <TokensBurnt<T>>::get(collection.id)407 <TotalSupply<T>>::remove((collection.id, token_id));413 <TotalSupply<T>>::remove((collection.id, token_id));408 <Balance<T>>::remove_prefix((collection.id, token_id), None);414 <Balance<T>>::remove_prefix((collection.id, token_id), None);409 <Allowance<T>>::remove_prefix((collection.id, token_id), None);415 <Allowance<T>>::remove_prefix((collection.id, token_id), None);410 // TODO: ERC721 transfer event416417 <PalletEvm<T>>::deposit_log(418 ERC721Events::Transfer {419 from: *owner.as_eth(),420 to: H160::default(),421 token_id: token_id.into(),422 }423 .to_log(collection_id_to_address(collection.id)),424 );411 Ok(())425 Ok(())412 }426 }413427449 <Owned<T>>::remove((collection.id, owner, token));463 <Owned<T>>::remove((collection.id, owner, token));450 <PalletStructure<T>>::unnest_if_nested(owner, collection.id, token);464 <PalletStructure<T>>::unnest_if_nested(owner, collection.id, token);451 <AccountBalance<T>>::insert((collection.id, owner), account_balance);465 <AccountBalance<T>>::insert((collection.id, owner), account_balance);452 Self::burn_token_unchecked(collection, token)?;466 Self::burn_token_unchecked(collection, owner, token)?;467 <PalletEvm<T>>::deposit_log(468 ERC20Events::Transfer {469 from: *owner.as_eth(),470 to: H160::default(),471 value: amount.into(),472 }473 .to_log(collection_id_to_address(collection.id)),474 );453 <PalletCommon<T>>::deposit_event(CommonEvent::ItemDestroyed(475 <PalletCommon<T>>::deposit_event(CommonEvent::ItemDestroyed(454 collection.id,476 collection.id,455 token,477 token,479 <Balance<T>>::remove((collection.id, token, owner));501 <Balance<T>>::remove((collection.id, token, owner));480 <AccountBalance<T>>::insert((collection.id, owner), account_balance);502 <AccountBalance<T>>::insert((collection.id, owner), account_balance);503504 if let Some(user) = Self::token_owner(collection.id, token) {505 <PalletEvm<T>>::deposit_log(506 ERC721Events::Transfer {507 from: erc::ADDRESS_FOR_PARTIALLY_OWNED_TOKENS,508 to: *user.as_eth(),509 token_id: token.into(),510 }511 .to_log(collection_id_to_address(collection.id)),512 );513 }481 } else {514 } else {482 <Balance<T>>::insert((collection.id, token, owner), balance);515 <Balance<T>>::insert((collection.id, token, owner), balance);483 }516 }695 }728 }696 <PalletCommon<T>>::ensure_correct_receiver(to)?;729 <PalletCommon<T>>::ensure_correct_receiver(to)?;697730698 let balance_from = <Balance<T>>::get((collection.id, token, from))731 let initial_balance_from = <Balance<T>>::get((collection.id, token, from));732 let updated_balance_from = initial_balance_from699 .checked_sub(amount)733 .checked_sub(amount)700 .ok_or(<CommonError<T>>::TokenValueTooLow)?;734 .ok_or(<CommonError<T>>::TokenValueTooLow)?;701 let mut create_target = false;735 let mut create_target = false;702 let from_to_differ = from != to;736 let from_to_differ = from != to;703 let balance_to = if from != to {737 let updated_balance_to = if from != to {704 let old_balance = <Balance<T>>::get((collection.id, token, to));738 let old_balance = <Balance<T>>::get((collection.id, token, to));705 if old_balance == 0 {739 if old_balance == 0 {706 create_target = true;740 create_target = true;714 None748 None715 };749 };716750717 let account_balance_from = if balance_from == 0 {751 let account_balance_from = if updated_balance_from == 0 {718 Some(752 Some(719 <AccountBalance<T>>::get((collection.id, from))753 <AccountBalance<T>>::get((collection.id, from))720 .checked_sub(1)754 .checked_sub(1)750 nesting_budget,784 nesting_budget,751 )?;785 )?;752786753 if let Some(balance_to) = balance_to {787 if let Some(updated_balance_to) = updated_balance_to {754 // from != to788 // from != to755 if balance_from == 0 {789 if updated_balance_from == 0 {756 <Balance<T>>::remove((collection.id, token, from));790 <Balance<T>>::remove((collection.id, token, from));757 <PalletStructure<T>>::unnest_if_nested(from, collection.id, token);791 <PalletStructure<T>>::unnest_if_nested(from, collection.id, token);758 } else {792 } else {759 <Balance<T>>::insert((collection.id, token, from), balance_from);793 <Balance<T>>::insert((collection.id, token, from), updated_balance_from);760 }794 }761 <Balance<T>>::insert((collection.id, token, to), balance_to);795 <Balance<T>>::insert((collection.id, token, to), updated_balance_to);762 if let Some(account_balance_from) = account_balance_from {796 if let Some(account_balance_from) = account_balance_from {763 <AccountBalance<T>>::insert((collection.id, from), account_balance_from);797 <AccountBalance<T>>::insert((collection.id, from), account_balance_from);764 <Owned<T>>::remove((collection.id, from, token));798 <Owned<T>>::remove((collection.id, from, token));788 amount,823 amount,789 ));824 ));825826 let total_supply = <TotalSupply<T>>::get((collection.id, token));827828 if amount == total_supply {829 // if token was fully owned by `from` and will be fully owned by `to` after transfer830 <PalletEvm<T>>::deposit_log(831 ERC721Events::Transfer {832 from: *from.as_eth(),833 to: *to.as_eth(),834 token_id: token.into(),835 }836 .to_log(collection_id_to_address(collection.id)),837 );838 } else if let Some(updated_balance_to) = updated_balance_to {839 // if `from` not equals `to`. This condition is needed to avoid sending event840 // when `from` fully owns token and sends part of token pieces to itself.841 if initial_balance_from == total_supply {842 // if token was fully owned by `from` and will be only partially owned by `to`843 // and `from` after transfer844 <PalletEvm<T>>::deposit_log(845 ERC721Events::Transfer {846 from: *from.as_eth(),847 to: erc::ADDRESS_FOR_PARTIALLY_OWNED_TOKENS,848 token_id: token.into(),849 }850 .to_log(collection_id_to_address(collection.id)),851 );852 } else if updated_balance_to == total_supply {853 // if token was partially owned by `from` and will be fully owned by `to` after transfer854 <PalletEvm<T>>::deposit_log(855 ERC721Events::Transfer {856 from: erc::ADDRESS_FOR_PARTIALLY_OWNED_TOKENS,857 to: *to.as_eth(),858 token_id: token.into(),859 }860 .to_log(collection_id_to_address(collection.id)),861 );862 }863 }864790 Ok(())865 Ok(())791 }866 }798 pub fn create_multiple_items(873 pub fn create_multiple_items(799 collection: &RefungibleHandle<T>,874 collection: &RefungibleHandle<T>,800 sender: &T::CrossAccountId,875 sender: &T::CrossAccountId,801 data: Vec<CreateRefungibleExData<T::CrossAccountId>>,876 data: Vec<CreateItemData<T>>,802 nesting_budget: &dyn Budget,877 nesting_budget: &dyn Budget,803 ) -> DispatchResult {878 ) -> DispatchResult {804 if !collection.is_owner_or_admin(sender) {879 if !collection.is_owner_or_admin(sender) {920 for (i, token) in data.into_iter().enumerate() {995 for (i, token) in data.into_iter().enumerate() {921 let token_id = first_token_id + i as u32 + 1;996 let token_id = first_token_id + i as u32 + 1;997998 let receivers = token999 .users1000 .into_iter()1001 .filter(|(_, amount)| *amount > 0)1002 .collect::<Vec<_>>();10031004 if let [(user, _)] = receivers.as_slice() {1005 // if there is exactly one receiver1006 <PalletEvm<T>>::deposit_log(1007 ERC721Events::Transfer {1008 from: H160::default(),1009 to: *user.as_eth(),1010 token_id: token_id.into(),1011 }1012 .to_log(collection_id_to_address(collection.id)),1013 );1014 } else if let [_, ..] = receivers.as_slice() {1015 // if there is more than one receiver1016 <PalletEvm<T>>::deposit_log(1017 ERC721Events::Transfer {1018 from: H160::default(),1019 to: erc::ADDRESS_FOR_PARTIALLY_OWNED_TOKENS,1020 token_id: token_id.into(),1021 }1022 .to_log(collection_id_to_address(collection.id)),1023 );1024 }9221025923 for (user, amount) in token.users.into_iter() {1026 for (user, amount) in receivers.into_iter() {924 if amount == 0 {925 continue;926 }927928 <PalletEvm<T>>::deposit_log(1027 <PalletEvm<T>>::deposit_log(929 ERC20Events::Transfer {1028 ERC20Events::Transfer {1114 pub fn create_item(1213 pub fn create_item(1115 collection: &RefungibleHandle<T>,1214 collection: &RefungibleHandle<T>,1116 sender: &T::CrossAccountId,1215 sender: &T::CrossAccountId,1117 data: CreateRefungibleExData<T::CrossAccountId>,1216 data: CreateItemData<T>,1118 nesting_budget: &dyn Budget,1217 nesting_budget: &dyn Budget,1119 ) -> DispatchResult {1218 ) -> DispatchResult {1120 Self::create_multiple_items(collection, sender, vec![data], nesting_budget)1219 Self::create_multiple_items(collection, sender, vec![data], nesting_budget)pallets/refungible/src/stubs/UniqueRefungible.rawdiffbeforeafterbothbinary blob — no preview
pallets/refungible/src/stubs/UniqueRefungible.soldiffbeforeafterboth334pragma solidity >=0.8.0 <0.9.0;4pragma solidity >=0.8.0 <0.9.0;56// Anonymous struct7struct Tuple0 {8 uint256 field_0;9 string field_1;10}5116// Common stubs holder12// Common stubs holder7contract Dummy {13contract Dummy {21 }27 }22}28}2930// Inline31contract ERC721Events {32 event Transfer(33 address indexed from,34 address indexed to,35 uint256 indexed tokenId36 );37 event Approval(38 address indexed owner,39 address indexed approved,40 uint256 indexed tokenId41 );42 event ApprovalForAll(43 address indexed owner,44 address indexed operator,45 bool approved46 );47}4849// Inline50contract ERC721MintableEvents {51 event MintingFinished();52}5354// Selector: 4136937755contract TokenProperties is Dummy, ERC165 {56 // @notice Set permissions for token property.57 // @dev Throws error if `msg.sender` is not admin or owner of the collection.58 // @param key Property key.59 // @param is_mutable Permission to mutate property.60 // @param collection_admin Permission to mutate property by collection admin if property is mutable.61 // @param token_owner Permission to mutate property by token owner if property is mutable.62 //63 // Selector: setTokenPropertyPermission(string,bool,bool,bool) 222d97fa64 function setTokenPropertyPermission(65 string memory key,66 bool isMutable,67 bool collectionAdmin,68 bool tokenOwner69 ) public {70 require(false, stub_error);71 key;72 isMutable;73 collectionAdmin;74 tokenOwner;75 dummy = 0;76 }7778 // @notice Set token property value.79 // @dev Throws error if `msg.sender` has no permission to edit the property.80 // @param tokenId ID of the token.81 // @param key Property key.82 // @param value Property value.83 //84 // Selector: setProperty(uint256,string,bytes) 1752d67b85 function setProperty(86 uint256 tokenId,87 string memory key,88 bytes memory value89 ) public {90 require(false, stub_error);91 tokenId;92 key;93 value;94 dummy = 0;95 }9697 // @notice Delete token property value.98 // @dev Throws error if `msg.sender` has no permission to edit the property.99 // @param tokenId ID of the token.100 // @param key Property key.101 //102 // Selector: deleteProperty(uint256,string) 066111d1103 function deleteProperty(uint256 tokenId, string memory key) public {104 require(false, stub_error);105 tokenId;106 key;107 dummy = 0;108 }109110 // @notice Get token property value.111 // @dev Throws error if key not found112 // @param tokenId ID of the token.113 // @param key Property key.114 // @return Property value bytes115 //116 // Selector: property(uint256,string) 7228c327117 function property(uint256 tokenId, string memory key)118 public119 view120 returns (bytes memory)121 {122 require(false, stub_error);123 tokenId;124 key;125 dummy;126 return hex"";127 }128}129130// Selector: 42966c68131contract ERC721Burnable is Dummy, ERC165 {132 // @notice Burns a specific ERC721 token.133 // @dev Throws unless `msg.sender` is the current RFT owner, or an authorized134 // operator of the current owner.135 // @param tokenId The RFT to approve136 //137 // Selector: burn(uint256) 42966c68138 function burn(uint256 tokenId) public {139 require(false, stub_error);140 tokenId;141 dummy = 0;142 }143}144145// Selector: 58800161146contract ERC721 is Dummy, ERC165, ERC721Events {147 // @notice Count all RFTs assigned to an owner148 // @dev RFTs assigned to the zero address are considered invalid, and this149 // function throws for queries about the zero address.150 // @param owner An address for whom to query the balance151 // @return The number of RFTs owned by `owner`, possibly zero152 //153 // Selector: balanceOf(address) 70a08231154 function balanceOf(address owner) public view returns (uint256) {155 require(false, stub_error);156 owner;157 dummy;158 return 0;159 }160161 // @notice Find the owner of an RFT162 // @dev RFTs assigned to zero address are considered invalid, and queries163 // about them do throw.164 // Returns special 0xffffffffffffffffffffffffffffffffffffffff address for165 // the tokens that are partially owned.166 // @param tokenId The identifier for an RFT167 // @return The address of the owner of the RFT168 //169 // Selector: ownerOf(uint256) 6352211e170 function ownerOf(uint256 tokenId) public view returns (address) {171 require(false, stub_error);172 tokenId;173 dummy;174 return 0x0000000000000000000000000000000000000000;175 }176177 // @dev Not implemented178 //179 // Selector: safeTransferFromWithData(address,address,uint256,bytes) 60a11672180 function safeTransferFromWithData(181 address from,182 address to,183 uint256 tokenId,184 bytes memory data185 ) public {186 require(false, stub_error);187 from;188 to;189 tokenId;190 data;191 dummy = 0;192 }193194 // @dev Not implemented195 //196 // Selector: safeTransferFrom(address,address,uint256) 42842e0e197 function safeTransferFrom(198 address from,199 address to,200 uint256 tokenId201 ) public {202 require(false, stub_error);203 from;204 to;205 tokenId;206 dummy = 0;207 }208209 // @notice Transfer ownership of an RFT -- THE CALLER IS RESPONSIBLE210 // TO CONFIRM THAT `to` IS CAPABLE OF RECEIVING NFTS OR ELSE211 // THEY MAY BE PERMANENTLY LOST212 // @dev Throws unless `msg.sender` is the current owner or an authorized213 // operator for this RFT. Throws if `from` is not the current owner. Throws214 // if `to` is the zero address. Throws if `tokenId` is not a valid RFT.215 // Throws if RFT pieces have multiple owners.216 // @param from The current owner of the NFT217 // @param to The new owner218 // @param tokenId The NFT to transfer219 // @param _value Not used for an NFT220 //221 // Selector: transferFrom(address,address,uint256) 23b872dd222 function transferFrom(223 address from,224 address to,225 uint256 tokenId226 ) public {227 require(false, stub_error);228 from;229 to;230 tokenId;231 dummy = 0;232 }233234 // @dev Not implemented235 //236 // Selector: approve(address,uint256) 095ea7b3237 function approve(address approved, uint256 tokenId) public {238 require(false, stub_error);239 approved;240 tokenId;241 dummy = 0;242 }243244 // @dev Not implemented245 //246 // Selector: setApprovalForAll(address,bool) a22cb465247 function setApprovalForAll(address operator, bool approved) public {248 require(false, stub_error);249 operator;250 approved;251 dummy = 0;252 }253254 // @dev Not implemented255 //256 // Selector: getApproved(uint256) 081812fc257 function getApproved(uint256 tokenId) public view returns (address) {258 require(false, stub_error);259 tokenId;260 dummy;261 return 0x0000000000000000000000000000000000000000;262 }263264 // @dev Not implemented265 //266 // Selector: isApprovedForAll(address,address) e985e9c5267 function isApprovedForAll(address owner, address operator)268 public269 view270 returns (address)271 {272 require(false, stub_error);273 owner;274 operator;275 dummy;276 return 0x0000000000000000000000000000000000000000;277 }278}279280// Selector: 5b5e139f281contract ERC721Metadata is Dummy, ERC165 {282 // @notice A descriptive name for a collection of RFTs in this contract283 //284 // Selector: name() 06fdde03285 function name() public view returns (string memory) {286 require(false, stub_error);287 dummy;288 return "";289 }290291 // @notice An abbreviated name for RFTs in this contract292 //293 // Selector: symbol() 95d89b41294 function symbol() public view returns (string memory) {295 require(false, stub_error);296 dummy;297 return "";298 }299300 // @notice A distinct Uniform Resource Identifier (URI) for a given asset.301 //302 // @dev If the token has a `url` property and it is not empty, it is returned.303 // 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`.304 // If the collection property `baseURI` is empty or absent, return "" (empty string)305 // otherwise, if token property `suffix` present and is non-empty, return concatenation of baseURI and suffix306 // otherwise, return concatenation of `baseURI` and stringified token id (decimal stringifying, without paddings).307 //308 // @return token's const_metadata309 //310 // Selector: tokenURI(uint256) c87b56dd311 function tokenURI(uint256 tokenId) public view returns (string memory) {312 require(false, stub_error);313 tokenId;314 dummy;315 return "";316 }317}318319// Selector: 68ccfe89320contract ERC721Mintable is Dummy, ERC165, ERC721MintableEvents {321 // Selector: mintingFinished() 05d2035b322 function mintingFinished() public view returns (bool) {323 require(false, stub_error);324 dummy;325 return false;326 }327328 // @notice Function to mint token.329 // @dev `tokenId` should be obtained with `nextTokenId` method,330 // unlike standard, you can't specify it manually331 // @param to The new owner332 // @param tokenId ID of the minted RFT333 //334 // Selector: mint(address,uint256) 40c10f19335 function mint(address to, uint256 tokenId) public returns (bool) {336 require(false, stub_error);337 to;338 tokenId;339 dummy = 0;340 return false;341 }342343 // @notice Function to mint token with the given tokenUri.344 // @dev `tokenId` should be obtained with `nextTokenId` method,345 // unlike standard, you can't specify it manually346 // @param to The new owner347 // @param tokenId ID of the minted RFT348 // @param tokenUri Token URI that would be stored in the RFT properties349 //350 // Selector: mintWithTokenURI(address,uint256,string) 50bb4e7f351 function mintWithTokenURI(352 address to,353 uint256 tokenId,354 string memory tokenUri355 ) public returns (bool) {356 require(false, stub_error);357 to;358 tokenId;359 tokenUri;360 dummy = 0;361 return false;362 }363364 // @dev Not implemented365 //366 // Selector: finishMinting() 7d64bcb4367 function finishMinting() public returns (bool) {368 require(false, stub_error);369 dummy = 0;370 return false;371 }372}373374// Selector: 780e9d63375contract ERC721Enumerable is Dummy, ERC165 {376 // @notice Enumerate valid RFTs377 // @param index A counter less than `totalSupply()`378 // @return The token identifier for the `index`th NFT,379 // (sort order not specified)380 //381 // Selector: tokenByIndex(uint256) 4f6ccce7382 function tokenByIndex(uint256 index) public view returns (uint256) {383 require(false, stub_error);384 index;385 dummy;386 return 0;387 }388389 // Not implemented390 //391 // Selector: tokenOfOwnerByIndex(address,uint256) 2f745c59392 function tokenOfOwnerByIndex(address owner, uint256 index)393 public394 view395 returns (uint256)396 {397 require(false, stub_error);398 owner;399 index;400 dummy;401 return 0;402 }403404 // @notice Count RFTs tracked by this contract405 // @return A count of valid RFTs tracked by this contract, where each one of406 // them has an assigned and queryable owner not equal to the zero address407 //408 // Selector: totalSupply() 18160ddd409 function totalSupply() public view returns (uint256) {410 require(false, stub_error);411 dummy;412 return 0;413 }414}2341524// Selector: 7d9262e6416// Selector: 7d9262e625contract Collection is Dummy, ERC165 {417contract Collection is Dummy, ERC165 {248 }640 }249}641}642643// Selector: d74d154f644contract ERC721UniqueExtensions is Dummy, ERC165 {645 // @notice Transfer ownership of an RFT646 // @dev Throws unless `msg.sender` is the current owner. Throws if `to`647 // is the zero address. Throws if `tokenId` is not a valid RFT.648 // Throws if RFT pieces have multiple owners.649 // @param to The new owner650 // @param tokenId The RFT to transfer651 // @param _value Not used for an RFT652 //653 // Selector: transfer(address,uint256) a9059cbb654 function transfer(address to, uint256 tokenId) public {655 require(false, stub_error);656 to;657 tokenId;658 dummy = 0;659 }660661 // @notice Burns a specific ERC721 token.662 // @dev Throws unless `msg.sender` is the current owner or an authorized663 // operator for this RFT. Throws if `from` is not the current owner. Throws664 // if `to` is the zero address. Throws if `tokenId` is not a valid RFT.665 // Throws if RFT pieces have multiple owners.666 // @param from The current owner of the RFT667 // @param tokenId The RFT to transfer668 // @param _value Not used for an RFT669 //670 // Selector: burnFrom(address,uint256) 79cc6790671 function burnFrom(address from, uint256 tokenId) public {672 require(false, stub_error);673 from;674 tokenId;675 dummy = 0;676 }677678 // @notice Returns next free RFT ID.679 //680 // Selector: nextTokenId() 75794a3c681 function nextTokenId() public view returns (uint256) {682 require(false, stub_error);683 dummy;684 return 0;685 }686687 // @notice Function to mint multiple tokens.688 // @dev `tokenIds` should be an array of consecutive numbers and first number689 // should be obtained with `nextTokenId` method690 // @param to The new owner691 // @param tokenIds IDs of the minted RFTs692 //693 // Selector: mintBulk(address,uint256[]) 44a9945e694 function mintBulk(address to, uint256[] memory tokenIds)695 public696 returns (bool)697 {698 require(false, stub_error);699 to;700 tokenIds;701 dummy = 0;702 return false;703 }704705 // @notice Function to mint multiple tokens with the given tokenUris.706 // @dev `tokenIds` is array of pairs of token ID and token URI. Token IDs should be consecutive707 // numbers and first number should be obtained with `nextTokenId` method708 // @param to The new owner709 // @param tokens array of pairs of token ID and token URI for minted tokens710 //711 // Selector: mintBulkWithTokenURI(address,(uint256,string)[]) 36543006712 function mintBulkWithTokenURI(address to, Tuple0[] memory tokens)713 public714 returns (bool)715 {716 require(false, stub_error);717 to;718 tokens;719 dummy = 0;720 return false;721 }722}250723251contract UniqueRefungible is Dummy, ERC165, Collection {}724contract UniqueRefungible is725 Dummy,726 ERC165,727 ERC721,728 ERC721Metadata,729 ERC721Enumerable,730 ERC721UniqueExtensions,731 ERC721Mintable,732 ERC721Burnable,733 Collection,734 TokenProperties735{}252736tests/src/eth/api/UniqueRefungible.soldiffbeforeafterboth334pragma solidity >=0.8.0 <0.9.0;4pragma solidity >=0.8.0 <0.9.0;56// Anonymous struct7struct Tuple0 {8 uint256 field_0;9 string field_1;10}5116// Common stubs holder12// Common stubs holder7interface Dummy {13interface Dummy {12 function supportsInterface(bytes4 interfaceID) external view returns (bool);18 function supportsInterface(bytes4 interfaceID) external view returns (bool);13}19}2021// Inline22interface ERC721Events {23 event Transfer(24 address indexed from,25 address indexed to,26 uint256 indexed tokenId27 );28 event Approval(29 address indexed owner,30 address indexed approved,31 uint256 indexed tokenId32 );33 event ApprovalForAll(34 address indexed owner,35 address indexed operator,36 bool approved37 );38}3940// Inline41interface ERC721MintableEvents {42 event MintingFinished();43}4445// Selector: 4136937746interface TokenProperties is Dummy, ERC165 {47 // @notice Set permissions for token property.48 // @dev Throws error if `msg.sender` is not admin or owner of the collection.49 // @param key Property key.50 // @param is_mutable Permission to mutate property.51 // @param collection_admin Permission to mutate property by collection admin if property is mutable.52 // @param token_owner Permission to mutate property by token owner if property is mutable.53 //54 // Selector: setTokenPropertyPermission(string,bool,bool,bool) 222d97fa55 function setTokenPropertyPermission(56 string memory key,57 bool isMutable,58 bool collectionAdmin,59 bool tokenOwner60 ) external;6162 // @notice Set token property value.63 // @dev Throws error if `msg.sender` has no permission to edit the property.64 // @param tokenId ID of the token.65 // @param key Property key.66 // @param value Property value.67 //68 // Selector: setProperty(uint256,string,bytes) 1752d67b69 function setProperty(70 uint256 tokenId,71 string memory key,72 bytes memory value73 ) external;7475 // @notice Delete token property value.76 // @dev Throws error if `msg.sender` has no permission to edit the property.77 // @param tokenId ID of the token.78 // @param key Property key.79 //80 // Selector: deleteProperty(uint256,string) 066111d181 function deleteProperty(uint256 tokenId, string memory key) external;8283 // @notice Get token property value.84 // @dev Throws error if key not found85 // @param tokenId ID of the token.86 // @param key Property key.87 // @return Property value bytes88 //89 // Selector: property(uint256,string) 7228c32790 function property(uint256 tokenId, string memory key)91 external92 view93 returns (bytes memory);94}9596// Selector: 42966c6897interface ERC721Burnable is Dummy, ERC165 {98 // @notice Burns a specific ERC721 token.99 // @dev Throws unless `msg.sender` is the current RFT owner, or an authorized100 // operator of the current owner.101 // @param tokenId The RFT to approve102 //103 // Selector: burn(uint256) 42966c68104 function burn(uint256 tokenId) external;105}106107// Selector: 58800161108interface ERC721 is Dummy, ERC165, ERC721Events {109 // @notice Count all RFTs assigned to an owner110 // @dev RFTs assigned to the zero address are considered invalid, and this111 // function throws for queries about the zero address.112 // @param owner An address for whom to query the balance113 // @return The number of RFTs owned by `owner`, possibly zero114 //115 // Selector: balanceOf(address) 70a08231116 function balanceOf(address owner) external view returns (uint256);117118 // @notice Find the owner of an RFT119 // @dev RFTs assigned to zero address are considered invalid, and queries120 // about them do throw.121 // Returns special 0xffffffffffffffffffffffffffffffffffffffff address for122 // the tokens that are partially owned.123 // @param tokenId The identifier for an RFT124 // @return The address of the owner of the RFT125 //126 // Selector: ownerOf(uint256) 6352211e127 function ownerOf(uint256 tokenId) external view returns (address);128129 // @dev Not implemented130 //131 // Selector: safeTransferFromWithData(address,address,uint256,bytes) 60a11672132 function safeTransferFromWithData(133 address from,134 address to,135 uint256 tokenId,136 bytes memory data137 ) external;138139 // @dev Not implemented140 //141 // Selector: safeTransferFrom(address,address,uint256) 42842e0e142 function safeTransferFrom(143 address from,144 address to,145 uint256 tokenId146 ) external;147148 // @notice Transfer ownership of an RFT -- THE CALLER IS RESPONSIBLE149 // TO CONFIRM THAT `to` IS CAPABLE OF RECEIVING NFTS OR ELSE150 // THEY MAY BE PERMANENTLY LOST151 // @dev Throws unless `msg.sender` is the current owner or an authorized152 // operator for this RFT. Throws if `from` is not the current owner. Throws153 // if `to` is the zero address. Throws if `tokenId` is not a valid RFT.154 // Throws if RFT pieces have multiple owners.155 // @param from The current owner of the NFT156 // @param to The new owner157 // @param tokenId The NFT to transfer158 // @param _value Not used for an NFT159 //160 // Selector: transferFrom(address,address,uint256) 23b872dd161 function transferFrom(162 address from,163 address to,164 uint256 tokenId165 ) external;166167 // @dev Not implemented168 //169 // Selector: approve(address,uint256) 095ea7b3170 function approve(address approved, uint256 tokenId) external;171172 // @dev Not implemented173 //174 // Selector: setApprovalForAll(address,bool) a22cb465175 function setApprovalForAll(address operator, bool approved) external;176177 // @dev Not implemented178 //179 // Selector: getApproved(uint256) 081812fc180 function getApproved(uint256 tokenId) external view returns (address);181182 // @dev Not implemented183 //184 // Selector: isApprovedForAll(address,address) e985e9c5185 function isApprovedForAll(address owner, address operator)186 external187 view188 returns (address);189}190191// Selector: 5b5e139f192interface ERC721Metadata is Dummy, ERC165 {193 // @notice A descriptive name for a collection of RFTs in this contract194 //195 // Selector: name() 06fdde03196 function name() external view returns (string memory);197198 // @notice An abbreviated name for RFTs in this contract199 //200 // Selector: symbol() 95d89b41201 function symbol() external view returns (string memory);202203 // @notice A distinct Uniform Resource Identifier (URI) for a given asset.204 //205 // @dev If the token has a `url` property and it is not empty, it is returned.206 // 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`.207 // If the collection property `baseURI` is empty or absent, return "" (empty string)208 // otherwise, if token property `suffix` present and is non-empty, return concatenation of baseURI and suffix209 // otherwise, return concatenation of `baseURI` and stringified token id (decimal stringifying, without paddings).210 //211 // @return token's const_metadata212 //213 // Selector: tokenURI(uint256) c87b56dd214 function tokenURI(uint256 tokenId) external view returns (string memory);215}216217// Selector: 68ccfe89218interface ERC721Mintable is Dummy, ERC165, ERC721MintableEvents {219 // Selector: mintingFinished() 05d2035b220 function mintingFinished() external view returns (bool);221222 // @notice Function to mint token.223 // @dev `tokenId` should be obtained with `nextTokenId` method,224 // unlike standard, you can't specify it manually225 // @param to The new owner226 // @param tokenId ID of the minted RFT227 //228 // Selector: mint(address,uint256) 40c10f19229 function mint(address to, uint256 tokenId) external returns (bool);230231 // @notice Function to mint token with the given tokenUri.232 // @dev `tokenId` should be obtained with `nextTokenId` method,233 // unlike standard, you can't specify it manually234 // @param to The new owner235 // @param tokenId ID of the minted RFT236 // @param tokenUri Token URI that would be stored in the RFT properties237 //238 // Selector: mintWithTokenURI(address,uint256,string) 50bb4e7f239 function mintWithTokenURI(240 address to,241 uint256 tokenId,242 string memory tokenUri243 ) external returns (bool);244245 // @dev Not implemented246 //247 // Selector: finishMinting() 7d64bcb4248 function finishMinting() external returns (bool);249}250251// Selector: 780e9d63252interface ERC721Enumerable is Dummy, ERC165 {253 // @notice Enumerate valid RFTs254 // @param index A counter less than `totalSupply()`255 // @return The token identifier for the `index`th NFT,256 // (sort order not specified)257 //258 // Selector: tokenByIndex(uint256) 4f6ccce7259 function tokenByIndex(uint256 index) external view returns (uint256);260261 // Not implemented262 //263 // Selector: tokenOfOwnerByIndex(address,uint256) 2f745c59264 function tokenOfOwnerByIndex(address owner, uint256 index)265 external266 view267 returns (uint256);268269 // @notice Count RFTs tracked by this contract270 // @return A count of valid RFTs tracked by this contract, where each one of271 // them has an assigned and queryable owner not equal to the zero address272 //273 // Selector: totalSupply() 18160ddd274 function totalSupply() external view returns (uint256);275}1427615// Selector: 7d9262e6277// Selector: 7d9262e616interface Collection is Dummy, ERC165 {278interface Collection is Dummy, ERC165 {160 function setCollectionMintMode(bool mode) external;422 function setCollectionMintMode(bool mode) external;161}423}424425// Selector: d74d154f426interface ERC721UniqueExtensions is Dummy, ERC165 {427 // @notice Transfer ownership of an RFT428 // @dev Throws unless `msg.sender` is the current owner. Throws if `to`429 // is the zero address. Throws if `tokenId` is not a valid RFT.430 // Throws if RFT pieces have multiple owners.431 // @param to The new owner432 // @param tokenId The RFT to transfer433 // @param _value Not used for an RFT434 //435 // Selector: transfer(address,uint256) a9059cbb436 function transfer(address to, uint256 tokenId) external;437438 // @notice Burns a specific ERC721 token.439 // @dev Throws unless `msg.sender` is the current owner or an authorized440 // operator for this RFT. Throws if `from` is not the current owner. Throws441 // if `to` is the zero address. Throws if `tokenId` is not a valid RFT.442 // Throws if RFT pieces have multiple owners.443 // @param from The current owner of the RFT444 // @param tokenId The RFT to transfer445 // @param _value Not used for an RFT446 //447 // Selector: burnFrom(address,uint256) 79cc6790448 function burnFrom(address from, uint256 tokenId) external;449450 // @notice Returns next free RFT ID.451 //452 // Selector: nextTokenId() 75794a3c453 function nextTokenId() external view returns (uint256);454455 // @notice Function to mint multiple tokens.456 // @dev `tokenIds` should be an array of consecutive numbers and first number457 // should be obtained with `nextTokenId` method458 // @param to The new owner459 // @param tokenIds IDs of the minted RFTs460 //461 // Selector: mintBulk(address,uint256[]) 44a9945e462 function mintBulk(address to, uint256[] memory tokenIds)463 external464 returns (bool);465466 // @notice Function to mint multiple tokens with the given tokenUris.467 // @dev `tokenIds` is array of pairs of token ID and token URI. Token IDs should be consecutive468 // numbers and first number should be obtained with `nextTokenId` method469 // @param to The new owner470 // @param tokens array of pairs of token ID and token URI for minted tokens471 //472 // Selector: mintBulkWithTokenURI(address,(uint256,string)[]) 36543006473 function mintBulkWithTokenURI(address to, Tuple0[] memory tokens)474 external475 returns (bool);476}162477163interface UniqueRefungible is Dummy, ERC165, Collection {}478interface UniqueRefungible is479 Dummy,480 ERC165,481 ERC721,482 ERC721Metadata,483 ERC721Enumerable,484 ERC721UniqueExtensions,485 ERC721Mintable,486 ERC721Burnable,487 Collection,488 TokenProperties489{}164490tests/src/eth/reFungible.test.tsdiffbeforeafterbothno changes
tests/src/eth/reFungibleAbi.jsondiffbeforeafterbothno changes
tests/src/eth/reFungibleToken.test.tsdiffbeforeafterboth74});74});757576// FIXME: Need erc721 for ReFubgible.76// FIXME: Need erc721 for ReFubgible.77describe.skip('Check ERC721 token URI for ReFungible', () => {77describe('Check ERC721 token URI for ReFungible', () => {78 itWeb3('Empty tokenURI', async ({web3, api, privateKeyWrapper}) => {78 itWeb3('Empty tokenURI', async ({web3, api, privateKeyWrapper}) => {79 const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);79 const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);80 const helper = evmCollectionHelpers(web3, owner);80 const helper = evmCollectionHelpers(web3, owner);277 {277 {278 const result = await contract.methods.transferFrom(owner, receiver, 49).send({from: spender});278 const result = await contract.methods.transferFrom(owner, receiver, 49).send({from: spender});279 const events = normalizeEvents(result.events);279 const events = normalizeEvents(result.events);280 expect(events).to.be.deep.equal([280 expect(events).to.include.deep.members([281 {281 {282 address,282 address,283 event: 'Transfer',283 event: 'Transfer',329 {329 {330 const result = await contract.methods.transfer(receiver, 50).send({from: owner});330 const result = await contract.methods.transfer(receiver, 50).send({from: owner});331 const events = normalizeEvents(result.events);331 const events = normalizeEvents(result.events);332 expect(events).to.be.deep.equal([332 expect(events).to.include.deep.members([333 {333 {334 address,334 address,335 event: 'Transfer',335 event: 'Transfer',443 ]);443 ]);444 });444 });445446 itWeb3('Receiving Transfer event on burning into full ownership', async ({web3, api, privateKeyWrapper}) => {447 const caller = await createEthAccountWithBalance(api, web3, privateKeyWrapper);448 const receiver = await createEthAccountWithBalance(api, web3, privateKeyWrapper);449 const helper = evmCollectionHelpers(web3, caller);450 const result = await helper.methods.createRefungibleCollection('Mint collection', '6', '6').send();451 const {collectionIdAddress, collectionId} = await getCollectionAddressFromResult(api, result);452 const contract = evmCollection(web3, caller, collectionIdAddress, {type: 'ReFungible'});453454 const tokenId = await contract.methods.nextTokenId().call();455 await contract.methods.mint(caller, tokenId).send();456457 const address = tokenIdToAddress(collectionId, tokenId);458459 const tokenContract = new web3.eth.Contract(reFungibleTokenAbi as any, address, {from: caller, ...GAS_ARGS});460 await tokenContract.methods.repartition(2).send();461 await tokenContract.methods.transfer(receiver, 1).send();462463 const events = await recordEvents(contract, async () => 464 await tokenContract.methods.burnFrom(caller, 1).send());465 expect(events).to.deep.equal([466 {467 address: collectionIdAddress,468 event: 'Transfer',469 args: {470 from: '0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF',471 to: receiver,472 tokenId,473 },474 },475 ]);476 });445});477});446478447describe('Refungible: Fees', () => {479describe('Refungible: Fees', () => {tests/src/eth/util/helpers.tsdiffbeforeafterboth31import collectionHelpersAbi from '../collectionHelpersAbi.json';31import collectionHelpersAbi from '../collectionHelpersAbi.json';32import fungibleAbi from '../fungibleAbi.json';32import fungibleAbi from '../fungibleAbi.json';33import nonFungibleAbi from '../nonFungibleAbi.json';33import nonFungibleAbi from '../nonFungibleAbi.json';34import refungibleAbi from '../refungibleAbi.json';34import refungibleAbi from '../reFungibleAbi.json';35import contractHelpersAbi from './contractHelpersAbi.json';35import contractHelpersAbi from './contractHelpersAbi.json';363637export const GAS_ARGS = {gas: 2500000};37export const GAS_ARGS = {gas: 2500000};