git.delta.rocks / unique-network / refs/commits / 3203150b5c74

difftreelog

Merge remote-tracking branch 'origin/feature/erc-721-for-refungible' into develop

Yaroslav Bolyukin2022-08-04parents: #f5cb854 #708f63e.patch.diff
in: master

13 files changed

modifiedCargo.lockdiffbeforeafterboth
63116311
6312[[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",
modifiedMakefilediffbeforeafterboth
15NONFUNGIBLE_EVM_STUBS=./pallets/nonfungible/src/stubs15NONFUNGIBLE_EVM_STUBS=./pallets/nonfungible/src/stubs
16NONFUNGIBLE_EVM_ABI=./tests/src/eth/nonFungibleAbi.json16NONFUNGIBLE_EVM_ABI=./tests/src/eth/nonFungibleAbi.json
1717
18REFUNGIBLE_EVM_STUBS=./pallets/refungible/src/stubs
18RENFUNGIBLE_EVM_ABI=./tests/src/eth/reFungibleAbi.json19REFUNGIBLE_EVM_ABI=./tests/src/eth/reFungibleAbi.json
19RENFUNGIBLE_TOKEN_EVM_ABI=./tests/src/eth/reFungibleTokenAbi.json20REFUNGIBLE_TOKEN_EVM_ABI=./tests/src/eth/reFungibleTokenAbi.json
2021
21CONTRACT_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.json
36UniqueNFT.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.sh
38 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.sh
39 40
40UniqueRefungible.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.sh
42 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.sh
61 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.sh
62 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.sh
6364
64UniqueRefungibleToken: UniqueRefungibleToken.sol65UniqueRefungible: UniqueRefungible.sol
65 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.sh
66 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.sh
6768
68UniqueRefungible: UniqueRefungible.sol69UniqueRefungibleToken: UniqueRefungibleToken.sol
69 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.sh
70 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.sh
7172
72ContractHelpers: ContractHelpers.sol73ContractHelpers: ContractHelpers.sol
73 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.sh
modifiedpallets/refungible/CHANGELOG.mddiffbeforeafterboth
22
3All notable changes to this project will be documented in this file.3All notable changes to this project will be documented in this file.
44
5## [v0.2.1] - 2022-07-27
6
7### New features
8
9Implementation of ERC-721 EVM API ([#452](https://github.com/UniqueNetwork/unique-chain/pull/452))
10
5## [v0.2.0] - 2022-08-0111## [v0.2.0] - 2022-08-01
12
6### Deprecated13### Deprecated
14
15`const_data` field is removed
16
7- `ItemData`17- `ItemData`
8- `TokenData`18- `TokenData`
919
15test(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))
1626
17## [v0.1.1] - 2022-07-1427## [v0.1.1] - 2022-07-14
28
18### Added29### Added features
30
19- Support for properties for RFT collections and tokens.31- Support for properties for RFT collections and tokens.
2032
21### Other changes33### Other changes
2234
23- 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.
2436
25This 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.
2638
modifiedpallets/refungible/Cargo.tomldiffbeforeafterboth
1[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"
66
modifiedpallets/refungible/src/erc.rsdiffbeforeafterboth
14// You should have received a copy of the GNU General Public License14// You should have received a copy of the GNU General Public License
15// 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/>.
16
17//! # Refungible Pallet EVM API for tokens
18//!
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.
1621
17extern crate alloc;22extern crate alloc;
23
24use 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};
2447
25use crate::{Config, RefungibleHandle};48use crate::{
49 AccountBalance, Balance, Config, CreateItemData, Pallet, RefungibleHandle, SelfWeightOf,
50 TokenProperties, TokensMinted, TotalSupply, weights::WeightInfo,
51};
52
53pub const ADDRESS_FOR_PARTIALLY_OWNED_TOKENS: H160 = H160::repeat_byte(0xff);
54
55/// @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 }
89
90 /// @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")?;
108
109 let nesting_budget = self
110 .recorder
111 .weight_calls_budget(<StructureWeight<T>>::find_parent());
112
113 <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 }
122
123 /// @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")?;
133
134 let nesting_budget = self
135 .recorder
136 .weight_calls_budget(<StructureWeight<T>>::find_parent());
137
138 <Pallet<T>>::delete_token_property(self, &caller, TokenId(token_id), key, &nesting_budget)
139 .map_err(dispatch_to_evm::<T>)
140 }
141
142 /// @notice Get token property value.
143 /// @dev Throws error if key not found
144 /// @param tokenId ID of the token.
145 /// @param key Property key.
146 /// @return Property value bytes
147 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")?;
152
153 let props = <TokenProperties<T>>::get((self.id, token_id));
154 let prop = props.get(&key).ok_or("key not found")?;
155
156 Ok(prop.to_vec())
157 }
158}
159
160#[derive(ToLog)]
161pub enum ERC721Events {
162 /// @dev This event emits when NFTs are created (`from` == 0) and destroyed
163 /// (`to` == 0). Exception: during contract creation, any number of RFTs
164 /// 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 supported
174 Approval {
175 #[indexed]
176 owner: address,
177 #[indexed]
178 approved: address,
179 #[indexed]
180 token_id: uint256,
181 },
182 /// @dev Not supported
183 #[allow(dead_code)]
184 ApprovalForAll {
185 #[indexed]
186 owner: address,
187 #[indexed]
188 operator: address,
189 approved: bool,
190 },
191}
192
193#[derive(ToLog)]
194pub enum ERC721MintableEvents {
195 /// @dev Not supported
196 #[allow(dead_code)]
197 MintingFinished {},
198}
199
200#[solidity_interface(name = "ERC721Metadata")]
201impl<T: Config> RefungibleHandle<T> {
202 /// @notice A descriptive name for a collection of RFTs in this contract
203 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 }
208
209 /// @notice An abbreviated name for RFTs in this contract
210 fn symbol(&self) -> Result<string> {
211 Ok(string::from_utf8_lossy(&self.token_prefix).into())
212 }
213
214 /// @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 suffix
220 /// otherwise, return concatenation of `baseURI` and stringified token id (decimal stringifying, without paddings).
221 ///
222 /// @return token's const_metadata
223 #[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")?;
226
227 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 }
234
235 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 e
243 ))
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 }
250
251 return Ok(base_uri + token_id.to_string().as_str());
252 }
253 }
254
255 Ok("".into())
256 }
257}
258
259/// @title ERC-721 Non-Fungible Token Standard, optional enumeration extension
260/// @dev See https://eips.ethereum.org/EIPS/eip-721
261#[solidity_interface(name = "ERC721Enumerable")]
262impl<T: Config> RefungibleHandle<T> {
263 /// @notice Enumerate valid RFTs
264 /// @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 }
270
271 /// Not implemented
272 fn token_of_owner_by_index(&self, _owner: address, _index: uint256) -> Result<uint256> {
273 // TODO: Not implemetable
274 Err("not implemented".into())
275 }
276
277 /// @notice Count RFTs tracked by this contract
278 /// @return A count of valid RFTs tracked by this contract, where each one of
279 /// them has an assigned and queryable owner not equal to the zero address
280 fn total_supply(&self) -> Result<uint256> {
281 self.consume_store_reads(1)?;
282 Ok(<Pallet<T>>::total_supply(self).into())
283 }
284}
285
286/// @title ERC-721 Non-Fungible Token Standard
287/// @dev See https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md
288#[solidity_interface(name = "ERC721", events(ERC721Events))]
289impl<T: Config> RefungibleHandle<T> {
290 /// @notice Count all RFTs assigned to an owner
291 /// @dev RFTs assigned to the zero address are considered invalid, and this
292 /// function throws for queries about the zero address.
293 /// @param owner An address for whom to query the balance
294 /// @return The number of RFTs owned by `owner`, possibly zero
295 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 }
301
302 /// @notice Find the owner of an RFT
303 /// @dev RFTs assigned to zero address are considered invalid, and queries
304 /// about them do throw.
305 /// Returns special 0xffffffffffffffffffffffffffffffffffffffff address for
306 /// the tokens that are partially owned.
307 /// @param tokenId The identifier for an RFT
308 /// @return The address of the owner of the RFT
309 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(owner
314 .map(|address| *address.as_eth())
315 .unwrap_or_else(|| ADDRESS_FOR_PARTIALLY_OWNED_TOKENS))
316 }
317
318 /// @dev Not implemented
319 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 implemetable
328 Err("not implemented".into())
329 }
330
331 /// @dev Not implemented
332 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 implemetable
340 Err("not implemented".into())
341 }
342
343 /// @notice Transfer ownership of an RFT -- THE CALLER IS RESPONSIBLE
344 /// TO CONFIRM THAT `to` IS CAPABLE OF RECEIVING NFTS OR ELSE
345 /// THEY MAY BE PERMANENTLY LOST
346 /// @dev Throws unless `msg.sender` is the current owner or an authorized
347 /// operator for this RFT. Throws if `from` is not the current owner. Throws
348 /// 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 NFT
351 /// @param to The new owner
352 /// @param tokenId The NFT to transfer
353 /// @param _value Not used for an NFT
354 #[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 = self
368 .recorder
369 .weight_calls_budget(<StructureWeight<T>>::find_parent());
370
371 let balance = balance(&self, token, &from)?;
372 ensure_single_owner(&self, token, balance)?;
373
374 <Pallet<T>>::transfer_from(self, &caller, &from, &to, token, balance, &budget)
375 .map_err(dispatch_to_evm::<T>)?;
376
377 Ok(())
378 }
379
380 /// @dev Not implemented
381 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 }
390
391 /// @dev Not implemented
392 fn set_approval_for_all(
393 &mut self,
394 _caller: caller,
395 _operator: address,
396 _approved: bool,
397 ) -> Result<void> {
398 // TODO: Not implemetable
399 Err("not implemented".into())
400 }
401
402 /// @dev Not implemented
403 fn get_approved(&self, _token_id: uint256) -> Result<address> {
404 // TODO: Not implemetable
405 Err("not implemented".into())
406 }
407
408 /// @dev Not implemented
409 fn is_approved_for_all(&self, _owner: address, _operator: address) -> Result<address> {
410 // TODO: Not implemetable
411 Err("not implemented".into())
412 }
413}
414
415/// Returns amount of pieces of `token` that `owner` have
416fn 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}
425
426/// Throws if `owner_balance` is lower than total amount of `token` pieces
427fn 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}
439
440/// @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 authorized
445 /// operator of the current owner.
446 /// @param tokenId The RFT to approve
447 #[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()?;
451
452 let balance = balance(&self, token, &caller)?;
453 ensure_single_owner(&self, token, balance)?;
454
455 <Pallet<T>>::burn(self, &caller, token, balance).map_err(dispatch_to_evm::<T>)?;
456 Ok(())
457 }
458}
459
460/// @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 }
466
467 /// @notice Function to mint token.
468 /// @dev `tokenId` should be obtained with `nextTokenId` method,
469 /// unlike standard, you can't specify it manually
470 /// @param to The new owner
471 /// @param tokenId ID of the minted RFT
472 #[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 = self
478 .recorder
479 .weight_calls_budget(<StructureWeight<T>>::find_parent());
480
481 if <TokensMinted<T>>::get(self.id)
482 .checked_add(1)
483 .ok_or("item id overflow")?
484 != token_id
485 {
486 return Err("item id should be next".into());
487 }
488
489 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>)?;
504
505 Ok(true)
506 }
507
508 /// @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 manually
511 /// @param to The new owner
512 /// @param tokenId ID of the minted RFT
513 /// @param tokenUri Token URI that would be stored in the RFT properties
514 #[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 }
528
529 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 = self
533 .recorder
534 .weight_calls_budget(<StructureWeight<T>>::find_parent());
535
536 if <TokensMinted<T>>::get(self.id)
537 .checked_add(1)
538 .ok_or("item id overflow")?
539 != token_id
540 {
541 return Err("item id should be next".into());
542 }
543
544 let mut properties = CollectionPropertiesVec::default();
545 properties
546 .try_push(Property {
547 key,
548 value: token_uri
549 .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)))?;
554
555 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 }
572
573 /// @dev Not implemented
574 fn finish_minting(&mut self, _caller: caller) -> Result<bool> {
575 Err("not implementable".into())
576 }
577}
578
579fn 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 }
590
591 Err("Property tokenURI not found".into())
592}
593
594fn 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_METADATA
600 } else {
601 false
602 }
603}
604
605fn 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_permissions
612 .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}
620
621/// @title Unique extensions for ERC721.
622#[solidity_interface(name = "ERC721UniqueExtensions")]
623impl<T: Config> RefungibleHandle<T> {
624 /// @notice Transfer ownership of an RFT
625 /// @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 owner
629 /// @param tokenId The RFT to transfer
630 /// @param _value Not used for an RFT
631 #[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 = self
643 .recorder
644 .weight_calls_budget(<StructureWeight<T>>::find_parent());
645
646 let balance = balance(&self, token, &caller)?;
647 ensure_single_owner(&self, token, balance)?;
648
649 <Pallet<T>>::transfer(self, &caller, &to, token, balance, &budget)
650 .map_err(dispatch_to_evm::<T>)?;
651 Ok(())
652 }
653
654 /// @notice Burns a specific ERC721 token.
655 /// @dev Throws unless `msg.sender` is the current owner or an authorized
656 /// operator for this RFT. Throws if `from` is not the current owner. Throws
657 /// 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 RFT
660 /// @param tokenId The RFT to transfer
661 /// @param _value Not used for an RFT
662 #[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 = self
674 .recorder
675 .weight_calls_budget(<StructureWeight<T>>::find_parent());
676
677 let balance = balance(&self, token, &caller)?;
678 ensure_single_owner(&self, token, balance)?;
679
680 <Pallet<T>>::burn_from(self, &caller, &from, token, balance, &budget)
681 .map_err(dispatch_to_evm::<T>)?;
682 Ok(())
683 }
684
685 /// @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 }
693
694 /// @notice Function to mint multiple tokens.
695 /// @dev `tokenIds` should be an array of consecutive numbers and first number
696 /// should be obtained with `nextTokenId` method
697 /// @param to The new owner
698 /// @param tokenIds IDs of the minted RFTs
699 #[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 = self
707 .recorder
708 .weight_calls_budget(<StructureWeight<T>>::find_parent());
709
710 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();
730
731 <Pallet<T>>::create_multiple_items(self, &caller, data, &budget)
732 .map_err(dispatch_to_evm::<T>)?;
733 Ok(true)
734 }
735
736 /// @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 consecutive
738 /// numbers and first number should be obtained with `nextTokenId` method
739 /// @param to The new owner
740 /// @param tokens array of pairs of token ID and token URI for minted tokens
741 #[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 = self
756 .recorder
757 .weight_calls_budget(<StructureWeight<T>>::find_parent());
758
759 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")?;
771
772 let mut properties = CollectionPropertiesVec::default();
773 properties
774 .try_push(Property {
775 key: key.clone(),
776 value: token_uri
777 .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)))?;
782
783 let create_item_data = CreateItemData::<T> {
784 users: users.clone(),
785 properties,
786 };
787 data.push(create_item_data);
788 }
789
790 <Pallet<T>>::create_multiple_items(self, &caller, data, &budget)
791 .map_err(dispatch_to_evm::<T>)?;
792 Ok(true)
793 }
794}
26795
27#[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]> {}
modifiedpallets/refungible/src/lib.rsdiffbeforeafterboth
88#![cfg_attr(not(feature = "std"), no_std)]88#![cfg_attr(not(feature = "std"), no_std)]
8989
90use crate::erc_token::ERC20Events;90use crate::erc_token::ERC20Events;
91use crate::erc::ERC721Events;
9192
92use 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;
122
123pub 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;
121126
122/// Token data, stored independently from other data used to describe it127/// Token data, stored independently from other data used to describe it
396401
397 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 event416
417 <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 }
413427
449 <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);
503
504 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)?;
697730
698 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_from
699 .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 None
715 };749 };
716750
717 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 )?;
752786
753 if let Some(balance_to) = balance_to {787 if let Some(updated_balance_to) = updated_balance_to {
754 // from != to788 // from != to
755 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 ));
825
826 let total_supply = <TotalSupply<T>>::get((collection.id, token));
827
828 if amount == total_supply {
829 // if token was fully owned by `from` and will be fully owned by `to` after transfer
830 <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 event
840 // 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 transfer
844 <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 transfer
854 <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 }
864
790 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;
997
998 let receivers = token
999 .users
1000 .into_iter()
1001 .filter(|(_, amount)| *amount > 0)
1002 .collect::<Vec<_>>();
1003
1004 if let [(user, _)] = receivers.as_slice() {
1005 // if there is exactly one receiver
1006 <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 receiver
1016 <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 }
9221025
923 for (user, amount) in token.users.into_iter() {1026 for (user, amount) in receivers.into_iter() {
924 if amount == 0 {
925 continue;
926 }
927
928 <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)
modifiedpallets/refungible/src/stubs/UniqueRefungible.rawdiffbeforeafterboth

binary blob — no preview

modifiedpallets/refungible/src/stubs/UniqueRefungible.soldiffbeforeafterboth
33
4pragma solidity >=0.8.0 <0.9.0;4pragma solidity >=0.8.0 <0.9.0;
5
6// Anonymous struct
7struct Tuple0 {
8 uint256 field_0;
9 string field_1;
10}
511
6// Common stubs holder12// Common stubs holder
7contract Dummy {13contract Dummy {
21 }27 }
22}28}
29
30// Inline
31contract ERC721Events {
32 event Transfer(
33 address indexed from,
34 address indexed to,
35 uint256 indexed tokenId
36 );
37 event Approval(
38 address indexed owner,
39 address indexed approved,
40 uint256 indexed tokenId
41 );
42 event ApprovalForAll(
43 address indexed owner,
44 address indexed operator,
45 bool approved
46 );
47}
48
49// Inline
50contract ERC721MintableEvents {
51 event MintingFinished();
52}
53
54// Selector: 41369377
55contract 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) 222d97fa
64 function setTokenPropertyPermission(
65 string memory key,
66 bool isMutable,
67 bool collectionAdmin,
68 bool tokenOwner
69 ) public {
70 require(false, stub_error);
71 key;
72 isMutable;
73 collectionAdmin;
74 tokenOwner;
75 dummy = 0;
76 }
77
78 // @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) 1752d67b
85 function setProperty(
86 uint256 tokenId,
87 string memory key,
88 bytes memory value
89 ) public {
90 require(false, stub_error);
91 tokenId;
92 key;
93 value;
94 dummy = 0;
95 }
96
97 // @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) 066111d1
103 function deleteProperty(uint256 tokenId, string memory key) public {
104 require(false, stub_error);
105 tokenId;
106 key;
107 dummy = 0;
108 }
109
110 // @notice Get token property value.
111 // @dev Throws error if key not found
112 // @param tokenId ID of the token.
113 // @param key Property key.
114 // @return Property value bytes
115 //
116 // Selector: property(uint256,string) 7228c327
117 function property(uint256 tokenId, string memory key)
118 public
119 view
120 returns (bytes memory)
121 {
122 require(false, stub_error);
123 tokenId;
124 key;
125 dummy;
126 return hex"";
127 }
128}
129
130// Selector: 42966c68
131contract ERC721Burnable is Dummy, ERC165 {
132 // @notice Burns a specific ERC721 token.
133 // @dev Throws unless `msg.sender` is the current RFT owner, or an authorized
134 // operator of the current owner.
135 // @param tokenId The RFT to approve
136 //
137 // Selector: burn(uint256) 42966c68
138 function burn(uint256 tokenId) public {
139 require(false, stub_error);
140 tokenId;
141 dummy = 0;
142 }
143}
144
145// Selector: 58800161
146contract ERC721 is Dummy, ERC165, ERC721Events {
147 // @notice Count all RFTs assigned to an owner
148 // @dev RFTs assigned to the zero address are considered invalid, and this
149 // function throws for queries about the zero address.
150 // @param owner An address for whom to query the balance
151 // @return The number of RFTs owned by `owner`, possibly zero
152 //
153 // Selector: balanceOf(address) 70a08231
154 function balanceOf(address owner) public view returns (uint256) {
155 require(false, stub_error);
156 owner;
157 dummy;
158 return 0;
159 }
160
161 // @notice Find the owner of an RFT
162 // @dev RFTs assigned to zero address are considered invalid, and queries
163 // about them do throw.
164 // Returns special 0xffffffffffffffffffffffffffffffffffffffff address for
165 // the tokens that are partially owned.
166 // @param tokenId The identifier for an RFT
167 // @return The address of the owner of the RFT
168 //
169 // Selector: ownerOf(uint256) 6352211e
170 function ownerOf(uint256 tokenId) public view returns (address) {
171 require(false, stub_error);
172 tokenId;
173 dummy;
174 return 0x0000000000000000000000000000000000000000;
175 }
176
177 // @dev Not implemented
178 //
179 // Selector: safeTransferFromWithData(address,address,uint256,bytes) 60a11672
180 function safeTransferFromWithData(
181 address from,
182 address to,
183 uint256 tokenId,
184 bytes memory data
185 ) public {
186 require(false, stub_error);
187 from;
188 to;
189 tokenId;
190 data;
191 dummy = 0;
192 }
193
194 // @dev Not implemented
195 //
196 // Selector: safeTransferFrom(address,address,uint256) 42842e0e
197 function safeTransferFrom(
198 address from,
199 address to,
200 uint256 tokenId
201 ) public {
202 require(false, stub_error);
203 from;
204 to;
205 tokenId;
206 dummy = 0;
207 }
208
209 // @notice Transfer ownership of an RFT -- THE CALLER IS RESPONSIBLE
210 // TO CONFIRM THAT `to` IS CAPABLE OF RECEIVING NFTS OR ELSE
211 // THEY MAY BE PERMANENTLY LOST
212 // @dev Throws unless `msg.sender` is the current owner or an authorized
213 // operator for this RFT. Throws if `from` is not the current owner. Throws
214 // 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 NFT
217 // @param to The new owner
218 // @param tokenId The NFT to transfer
219 // @param _value Not used for an NFT
220 //
221 // Selector: transferFrom(address,address,uint256) 23b872dd
222 function transferFrom(
223 address from,
224 address to,
225 uint256 tokenId
226 ) public {
227 require(false, stub_error);
228 from;
229 to;
230 tokenId;
231 dummy = 0;
232 }
233
234 // @dev Not implemented
235 //
236 // Selector: approve(address,uint256) 095ea7b3
237 function approve(address approved, uint256 tokenId) public {
238 require(false, stub_error);
239 approved;
240 tokenId;
241 dummy = 0;
242 }
243
244 // @dev Not implemented
245 //
246 // Selector: setApprovalForAll(address,bool) a22cb465
247 function setApprovalForAll(address operator, bool approved) public {
248 require(false, stub_error);
249 operator;
250 approved;
251 dummy = 0;
252 }
253
254 // @dev Not implemented
255 //
256 // Selector: getApproved(uint256) 081812fc
257 function getApproved(uint256 tokenId) public view returns (address) {
258 require(false, stub_error);
259 tokenId;
260 dummy;
261 return 0x0000000000000000000000000000000000000000;
262 }
263
264 // @dev Not implemented
265 //
266 // Selector: isApprovedForAll(address,address) e985e9c5
267 function isApprovedForAll(address owner, address operator)
268 public
269 view
270 returns (address)
271 {
272 require(false, stub_error);
273 owner;
274 operator;
275 dummy;
276 return 0x0000000000000000000000000000000000000000;
277 }
278}
279
280// Selector: 5b5e139f
281contract ERC721Metadata is Dummy, ERC165 {
282 // @notice A descriptive name for a collection of RFTs in this contract
283 //
284 // Selector: name() 06fdde03
285 function name() public view returns (string memory) {
286 require(false, stub_error);
287 dummy;
288 return "";
289 }
290
291 // @notice An abbreviated name for RFTs in this contract
292 //
293 // Selector: symbol() 95d89b41
294 function symbol() public view returns (string memory) {
295 require(false, stub_error);
296 dummy;
297 return "";
298 }
299
300 // @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 suffix
306 // otherwise, return concatenation of `baseURI` and stringified token id (decimal stringifying, without paddings).
307 //
308 // @return token's const_metadata
309 //
310 // Selector: tokenURI(uint256) c87b56dd
311 function tokenURI(uint256 tokenId) public view returns (string memory) {
312 require(false, stub_error);
313 tokenId;
314 dummy;
315 return "";
316 }
317}
318
319// Selector: 68ccfe89
320contract ERC721Mintable is Dummy, ERC165, ERC721MintableEvents {
321 // Selector: mintingFinished() 05d2035b
322 function mintingFinished() public view returns (bool) {
323 require(false, stub_error);
324 dummy;
325 return false;
326 }
327
328 // @notice Function to mint token.
329 // @dev `tokenId` should be obtained with `nextTokenId` method,
330 // unlike standard, you can't specify it manually
331 // @param to The new owner
332 // @param tokenId ID of the minted RFT
333 //
334 // Selector: mint(address,uint256) 40c10f19
335 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 }
342
343 // @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 manually
346 // @param to The new owner
347 // @param tokenId ID of the minted RFT
348 // @param tokenUri Token URI that would be stored in the RFT properties
349 //
350 // Selector: mintWithTokenURI(address,uint256,string) 50bb4e7f
351 function mintWithTokenURI(
352 address to,
353 uint256 tokenId,
354 string memory tokenUri
355 ) public returns (bool) {
356 require(false, stub_error);
357 to;
358 tokenId;
359 tokenUri;
360 dummy = 0;
361 return false;
362 }
363
364 // @dev Not implemented
365 //
366 // Selector: finishMinting() 7d64bcb4
367 function finishMinting() public returns (bool) {
368 require(false, stub_error);
369 dummy = 0;
370 return false;
371 }
372}
373
374// Selector: 780e9d63
375contract ERC721Enumerable is Dummy, ERC165 {
376 // @notice Enumerate valid RFTs
377 // @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) 4f6ccce7
382 function tokenByIndex(uint256 index) public view returns (uint256) {
383 require(false, stub_error);
384 index;
385 dummy;
386 return 0;
387 }
388
389 // Not implemented
390 //
391 // Selector: tokenOfOwnerByIndex(address,uint256) 2f745c59
392 function tokenOfOwnerByIndex(address owner, uint256 index)
393 public
394 view
395 returns (uint256)
396 {
397 require(false, stub_error);
398 owner;
399 index;
400 dummy;
401 return 0;
402 }
403
404 // @notice Count RFTs tracked by this contract
405 // @return A count of valid RFTs tracked by this contract, where each one of
406 // them has an assigned and queryable owner not equal to the zero address
407 //
408 // Selector: totalSupply() 18160ddd
409 function totalSupply() public view returns (uint256) {
410 require(false, stub_error);
411 dummy;
412 return 0;
413 }
414}
23415
24// Selector: 7d9262e6416// Selector: 7d9262e6
25contract Collection is Dummy, ERC165 {417contract Collection is Dummy, ERC165 {
248 }640 }
249}641}
642
643// Selector: d74d154f
644contract ERC721UniqueExtensions is Dummy, ERC165 {
645 // @notice Transfer ownership of an RFT
646 // @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 owner
650 // @param tokenId The RFT to transfer
651 // @param _value Not used for an RFT
652 //
653 // Selector: transfer(address,uint256) a9059cbb
654 function transfer(address to, uint256 tokenId) public {
655 require(false, stub_error);
656 to;
657 tokenId;
658 dummy = 0;
659 }
660
661 // @notice Burns a specific ERC721 token.
662 // @dev Throws unless `msg.sender` is the current owner or an authorized
663 // operator for this RFT. Throws if `from` is not the current owner. Throws
664 // 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 RFT
667 // @param tokenId The RFT to transfer
668 // @param _value Not used for an RFT
669 //
670 // Selector: burnFrom(address,uint256) 79cc6790
671 function burnFrom(address from, uint256 tokenId) public {
672 require(false, stub_error);
673 from;
674 tokenId;
675 dummy = 0;
676 }
677
678 // @notice Returns next free RFT ID.
679 //
680 // Selector: nextTokenId() 75794a3c
681 function nextTokenId() public view returns (uint256) {
682 require(false, stub_error);
683 dummy;
684 return 0;
685 }
686
687 // @notice Function to mint multiple tokens.
688 // @dev `tokenIds` should be an array of consecutive numbers and first number
689 // should be obtained with `nextTokenId` method
690 // @param to The new owner
691 // @param tokenIds IDs of the minted RFTs
692 //
693 // Selector: mintBulk(address,uint256[]) 44a9945e
694 function mintBulk(address to, uint256[] memory tokenIds)
695 public
696 returns (bool)
697 {
698 require(false, stub_error);
699 to;
700 tokenIds;
701 dummy = 0;
702 return false;
703 }
704
705 // @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 consecutive
707 // numbers and first number should be obtained with `nextTokenId` method
708 // @param to The new owner
709 // @param tokens array of pairs of token ID and token URI for minted tokens
710 //
711 // Selector: mintBulkWithTokenURI(address,(uint256,string)[]) 36543006
712 function mintBulkWithTokenURI(address to, Tuple0[] memory tokens)
713 public
714 returns (bool)
715 {
716 require(false, stub_error);
717 to;
718 tokens;
719 dummy = 0;
720 return false;
721 }
722}
250723
251contract UniqueRefungible is Dummy, ERC165, Collection {}724contract UniqueRefungible is
725 Dummy,
726 ERC165,
727 ERC721,
728 ERC721Metadata,
729 ERC721Enumerable,
730 ERC721UniqueExtensions,
731 ERC721Mintable,
732 ERC721Burnable,
733 Collection,
734 TokenProperties
735{}
252736
modifiedtests/src/eth/api/UniqueRefungible.soldiffbeforeafterboth
33
4pragma solidity >=0.8.0 <0.9.0;4pragma solidity >=0.8.0 <0.9.0;
5
6// Anonymous struct
7struct Tuple0 {
8 uint256 field_0;
9 string field_1;
10}
511
6// Common stubs holder12// Common stubs holder
7interface Dummy {13interface Dummy {
12 function supportsInterface(bytes4 interfaceID) external view returns (bool);18 function supportsInterface(bytes4 interfaceID) external view returns (bool);
13}19}
20
21// Inline
22interface ERC721Events {
23 event Transfer(
24 address indexed from,
25 address indexed to,
26 uint256 indexed tokenId
27 );
28 event Approval(
29 address indexed owner,
30 address indexed approved,
31 uint256 indexed tokenId
32 );
33 event ApprovalForAll(
34 address indexed owner,
35 address indexed operator,
36 bool approved
37 );
38}
39
40// Inline
41interface ERC721MintableEvents {
42 event MintingFinished();
43}
44
45// Selector: 41369377
46interface 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) 222d97fa
55 function setTokenPropertyPermission(
56 string memory key,
57 bool isMutable,
58 bool collectionAdmin,
59 bool tokenOwner
60 ) external;
61
62 // @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) 1752d67b
69 function setProperty(
70 uint256 tokenId,
71 string memory key,
72 bytes memory value
73 ) external;
74
75 // @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) 066111d1
81 function deleteProperty(uint256 tokenId, string memory key) external;
82
83 // @notice Get token property value.
84 // @dev Throws error if key not found
85 // @param tokenId ID of the token.
86 // @param key Property key.
87 // @return Property value bytes
88 //
89 // Selector: property(uint256,string) 7228c327
90 function property(uint256 tokenId, string memory key)
91 external
92 view
93 returns (bytes memory);
94}
95
96// Selector: 42966c68
97interface ERC721Burnable is Dummy, ERC165 {
98 // @notice Burns a specific ERC721 token.
99 // @dev Throws unless `msg.sender` is the current RFT owner, or an authorized
100 // operator of the current owner.
101 // @param tokenId The RFT to approve
102 //
103 // Selector: burn(uint256) 42966c68
104 function burn(uint256 tokenId) external;
105}
106
107// Selector: 58800161
108interface ERC721 is Dummy, ERC165, ERC721Events {
109 // @notice Count all RFTs assigned to an owner
110 // @dev RFTs assigned to the zero address are considered invalid, and this
111 // function throws for queries about the zero address.
112 // @param owner An address for whom to query the balance
113 // @return The number of RFTs owned by `owner`, possibly zero
114 //
115 // Selector: balanceOf(address) 70a08231
116 function balanceOf(address owner) external view returns (uint256);
117
118 // @notice Find the owner of an RFT
119 // @dev RFTs assigned to zero address are considered invalid, and queries
120 // about them do throw.
121 // Returns special 0xffffffffffffffffffffffffffffffffffffffff address for
122 // the tokens that are partially owned.
123 // @param tokenId The identifier for an RFT
124 // @return The address of the owner of the RFT
125 //
126 // Selector: ownerOf(uint256) 6352211e
127 function ownerOf(uint256 tokenId) external view returns (address);
128
129 // @dev Not implemented
130 //
131 // Selector: safeTransferFromWithData(address,address,uint256,bytes) 60a11672
132 function safeTransferFromWithData(
133 address from,
134 address to,
135 uint256 tokenId,
136 bytes memory data
137 ) external;
138
139 // @dev Not implemented
140 //
141 // Selector: safeTransferFrom(address,address,uint256) 42842e0e
142 function safeTransferFrom(
143 address from,
144 address to,
145 uint256 tokenId
146 ) external;
147
148 // @notice Transfer ownership of an RFT -- THE CALLER IS RESPONSIBLE
149 // TO CONFIRM THAT `to` IS CAPABLE OF RECEIVING NFTS OR ELSE
150 // THEY MAY BE PERMANENTLY LOST
151 // @dev Throws unless `msg.sender` is the current owner or an authorized
152 // operator for this RFT. Throws if `from` is not the current owner. Throws
153 // 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 NFT
156 // @param to The new owner
157 // @param tokenId The NFT to transfer
158 // @param _value Not used for an NFT
159 //
160 // Selector: transferFrom(address,address,uint256) 23b872dd
161 function transferFrom(
162 address from,
163 address to,
164 uint256 tokenId
165 ) external;
166
167 // @dev Not implemented
168 //
169 // Selector: approve(address,uint256) 095ea7b3
170 function approve(address approved, uint256 tokenId) external;
171
172 // @dev Not implemented
173 //
174 // Selector: setApprovalForAll(address,bool) a22cb465
175 function setApprovalForAll(address operator, bool approved) external;
176
177 // @dev Not implemented
178 //
179 // Selector: getApproved(uint256) 081812fc
180 function getApproved(uint256 tokenId) external view returns (address);
181
182 // @dev Not implemented
183 //
184 // Selector: isApprovedForAll(address,address) e985e9c5
185 function isApprovedForAll(address owner, address operator)
186 external
187 view
188 returns (address);
189}
190
191// Selector: 5b5e139f
192interface ERC721Metadata is Dummy, ERC165 {
193 // @notice A descriptive name for a collection of RFTs in this contract
194 //
195 // Selector: name() 06fdde03
196 function name() external view returns (string memory);
197
198 // @notice An abbreviated name for RFTs in this contract
199 //
200 // Selector: symbol() 95d89b41
201 function symbol() external view returns (string memory);
202
203 // @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 suffix
209 // otherwise, return concatenation of `baseURI` and stringified token id (decimal stringifying, without paddings).
210 //
211 // @return token's const_metadata
212 //
213 // Selector: tokenURI(uint256) c87b56dd
214 function tokenURI(uint256 tokenId) external view returns (string memory);
215}
216
217// Selector: 68ccfe89
218interface ERC721Mintable is Dummy, ERC165, ERC721MintableEvents {
219 // Selector: mintingFinished() 05d2035b
220 function mintingFinished() external view returns (bool);
221
222 // @notice Function to mint token.
223 // @dev `tokenId` should be obtained with `nextTokenId` method,
224 // unlike standard, you can't specify it manually
225 // @param to The new owner
226 // @param tokenId ID of the minted RFT
227 //
228 // Selector: mint(address,uint256) 40c10f19
229 function mint(address to, uint256 tokenId) external returns (bool);
230
231 // @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 manually
234 // @param to The new owner
235 // @param tokenId ID of the minted RFT
236 // @param tokenUri Token URI that would be stored in the RFT properties
237 //
238 // Selector: mintWithTokenURI(address,uint256,string) 50bb4e7f
239 function mintWithTokenURI(
240 address to,
241 uint256 tokenId,
242 string memory tokenUri
243 ) external returns (bool);
244
245 // @dev Not implemented
246 //
247 // Selector: finishMinting() 7d64bcb4
248 function finishMinting() external returns (bool);
249}
250
251// Selector: 780e9d63
252interface ERC721Enumerable is Dummy, ERC165 {
253 // @notice Enumerate valid RFTs
254 // @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) 4f6ccce7
259 function tokenByIndex(uint256 index) external view returns (uint256);
260
261 // Not implemented
262 //
263 // Selector: tokenOfOwnerByIndex(address,uint256) 2f745c59
264 function tokenOfOwnerByIndex(address owner, uint256 index)
265 external
266 view
267 returns (uint256);
268
269 // @notice Count RFTs tracked by this contract
270 // @return A count of valid RFTs tracked by this contract, where each one of
271 // them has an assigned and queryable owner not equal to the zero address
272 //
273 // Selector: totalSupply() 18160ddd
274 function totalSupply() external view returns (uint256);
275}
14276
15// Selector: 7d9262e6277// Selector: 7d9262e6
16interface Collection is Dummy, ERC165 {278interface Collection is Dummy, ERC165 {
160 function setCollectionMintMode(bool mode) external;422 function setCollectionMintMode(bool mode) external;
161}423}
424
425// Selector: d74d154f
426interface ERC721UniqueExtensions is Dummy, ERC165 {
427 // @notice Transfer ownership of an RFT
428 // @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 owner
432 // @param tokenId The RFT to transfer
433 // @param _value Not used for an RFT
434 //
435 // Selector: transfer(address,uint256) a9059cbb
436 function transfer(address to, uint256 tokenId) external;
437
438 // @notice Burns a specific ERC721 token.
439 // @dev Throws unless `msg.sender` is the current owner or an authorized
440 // operator for this RFT. Throws if `from` is not the current owner. Throws
441 // 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 RFT
444 // @param tokenId The RFT to transfer
445 // @param _value Not used for an RFT
446 //
447 // Selector: burnFrom(address,uint256) 79cc6790
448 function burnFrom(address from, uint256 tokenId) external;
449
450 // @notice Returns next free RFT ID.
451 //
452 // Selector: nextTokenId() 75794a3c
453 function nextTokenId() external view returns (uint256);
454
455 // @notice Function to mint multiple tokens.
456 // @dev `tokenIds` should be an array of consecutive numbers and first number
457 // should be obtained with `nextTokenId` method
458 // @param to The new owner
459 // @param tokenIds IDs of the minted RFTs
460 //
461 // Selector: mintBulk(address,uint256[]) 44a9945e
462 function mintBulk(address to, uint256[] memory tokenIds)
463 external
464 returns (bool);
465
466 // @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 consecutive
468 // numbers and first number should be obtained with `nextTokenId` method
469 // @param to The new owner
470 // @param tokens array of pairs of token ID and token URI for minted tokens
471 //
472 // Selector: mintBulkWithTokenURI(address,(uint256,string)[]) 36543006
473 function mintBulkWithTokenURI(address to, Tuple0[] memory tokens)
474 external
475 returns (bool);
476}
162477
163interface UniqueRefungible is Dummy, ERC165, Collection {}478interface UniqueRefungible is
479 Dummy,
480 ERC165,
481 ERC721,
482 ERC721Metadata,
483 ERC721Enumerable,
484 ERC721UniqueExtensions,
485 ERC721Mintable,
486 ERC721Burnable,
487 Collection,
488 TokenProperties
489{}
164490
addedtests/src/eth/reFungible.test.tsdiffbeforeafterboth

no changes

addedtests/src/eth/reFungibleAbi.jsondiffbeforeafterboth

no changes

modifiedtests/src/eth/reFungibleToken.test.tsdiffbeforeafterboth
74});74});
7575
76// 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 });
445
446 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'});
453
454 const tokenId = await contract.methods.nextTokenId().call();
455 await contract.methods.mint(caller, tokenId).send();
456
457 const address = tokenIdToAddress(collectionId, tokenId);
458
459 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();
462
463 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});
446478
447describe('Refungible: Fees', () => {479describe('Refungible: Fees', () => {
modifiedtests/src/eth/util/helpers.tsdiffbeforeafterboth
31import 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';
3636
37export const GAS_ARGS = {gas: 2500000};37export const GAS_ARGS = {gas: 2500000};