From eb176303d30bb9affedc447bf8f57e2da604bbf1 Mon Sep 17 00:00:00 2001 From: Grigoriy Simonov Date: Thu, 11 Aug 2022 09:44:00 +0000 Subject: [PATCH] feat(refungible-pallet): ERC 1633 implementation and `set_parent_nft` method --- --- a/pallets/common/src/erc.rs +++ b/pallets/common/src/erc.rs @@ -25,7 +25,8 @@ use pallet_evm_coder_substrate::dispatch_to_evm; use sp_std::vec::Vec; use up_data_structs::{ - Property, SponsoringRateLimit, OwnerRestrictedSet, AccessMode, CollectionPermissions, + AccessMode, CollectionMode, CollectionPermissions, OwnerRestrictedSet, Property, + SponsoringRateLimit, }; use alloc::format; @@ -408,6 +409,27 @@ save(self) } + + /// Check that account is the owner or admin of the collection + /// + /// @return "true" if account is the owner or admin + fn verify_owner_or_admin(&mut self, caller: caller) -> Result { + Ok(check_is_owner_or_admin(caller, self) + .map(|_| true) + .unwrap_or(false)) + } + + /// Returns collection type + /// + /// @return `Fungible` or `NFT` or `ReFungible` + fn unique_collection_type(&mut self) -> Result { + let mode = match self.collection.mode { + CollectionMode::Fungible(_) => "Fungible", + CollectionMode::NFT => "NFT", + CollectionMode::ReFungible => "ReFungible", + }; + Ok(mode.into()) + } } fn check_is_owner_or_admin( @@ -462,6 +484,11 @@ pub fn suffix() -> up_data_structs::PropertyKey { property_key_from_bytes(b"suffix").expect(EXPECT_CONVERT_ERROR) } + + /// Key "parentNft". + pub fn parent_nft() -> up_data_structs::PropertyKey { + property_key_from_bytes(b"parentNft").expect(EXPECT_CONVERT_ERROR) + } } /// Values. --- a/pallets/common/src/lib.rs +++ b/pallets/common/src/lib.rs @@ -1112,6 +1112,26 @@ sender: &T::CrossAccountId, property_permission: PropertyKeyPermission, ) -> DispatchResult { + Self::set_scoped_property_permission( + collection, + sender, + PropertyScope::None, + property_permission, + ) + } + + /// Set collection property permission with scope. + /// + /// * `collection` - Collection handler. + /// * `sender` - The owner or administrator of the collection. + /// * `scope` - Property scope. + /// * `property_permission` - Property permission. + pub fn set_scoped_property_permission( + collection: &CollectionHandle, + sender: &T::CrossAccountId, + scope: PropertyScope, + property_permission: PropertyKeyPermission, + ) -> DispatchResult { collection.check_is_owner_or_admin(sender)?; let all_permissions = CollectionPropertyPermissions::::get(collection.id); @@ -1125,7 +1145,11 @@ CollectionPropertyPermissions::::try_mutate(collection.id, |permissions| { let property_permission = property_permission.clone(); - permissions.try_set(property_permission.key, property_permission.permission) + permissions.try_scoped_set( + scope, + property_permission.key, + property_permission.permission, + ) }) .map_err(>::from)?; @@ -1148,8 +1172,29 @@ sender: &T::CrossAccountId, property_permissions: Vec, ) -> DispatchResult { + Self::set_scoped_token_property_permissions( + collection, + sender, + PropertyScope::None, + property_permissions, + ) + } + + /// Set token property permission with scope. + /// + /// * `collection` - Collection handler. + /// * `sender` - The owner or administrator of the collection. + /// * `scope` - Property scope. + /// * `property_permissions` - Property permissions. + #[transactional] + pub fn set_scoped_token_property_permissions( + collection: &CollectionHandle, + sender: &T::CrossAccountId, + scope: PropertyScope, + property_permissions: Vec, + ) -> DispatchResult { for prop_pemission in property_permissions { - Self::set_property_permission(collection, sender, prop_pemission)?; + Self::set_scoped_property_permission(collection, sender, scope, prop_pemission)?; } Ok(()) @@ -1429,6 +1474,9 @@ .saturating_mul(max_selfs.max(1) as u64) .saturating_add(Self::burn_recursively_breadth_raw(max_breadth)) } + + /// The price of retrieving token owner + fn token_owner() -> Weight; } /// Weight info extension trait for refungible pallet. @@ -1567,7 +1615,7 @@ /// /// * `sender` - Must be either the owner of the token or its admin. /// * `token_id` - The token for which the properties are being set. - /// * `properties` - Properties to be set. + /// * `property_permissions` - Property permissions to be set. /// * `budget` - Budget for setting properties. fn set_token_property_permissions( &self, --- a/pallets/fungible/src/common.rs +++ b/pallets/fungible/src/common.rs @@ -103,6 +103,10 @@ // Fungible tokens can't have children 0 } + + fn token_owner() -> Weight { + 0 + } } /// Implementation of `CommonCollectionOperations` for `FungibleHandle`. It wraps FungibleHandle Pallete --- a/pallets/fungible/src/stubs/UniqueFungible.sol +++ b/pallets/fungible/src/stubs/UniqueFungible.sol @@ -43,7 +43,91 @@ } } -// Selector: 7d9262e6 +// Selector: 942e8b22 +contract ERC20 is Dummy, ERC165, ERC20Events { + // Selector: name() 06fdde03 + function name() public view returns (string memory) { + require(false, stub_error); + dummy; + return ""; + } + + // Selector: symbol() 95d89b41 + function symbol() public view returns (string memory) { + require(false, stub_error); + dummy; + return ""; + } + + // Selector: totalSupply() 18160ddd + function totalSupply() public view returns (uint256) { + require(false, stub_error); + dummy; + return 0; + } + + // Selector: decimals() 313ce567 + function decimals() public view returns (uint8) { + require(false, stub_error); + dummy; + return 0; + } + + // Selector: balanceOf(address) 70a08231 + function balanceOf(address owner) public view returns (uint256) { + require(false, stub_error); + owner; + dummy; + return 0; + } + + // Selector: transfer(address,uint256) a9059cbb + function transfer(address to, uint256 amount) public returns (bool) { + require(false, stub_error); + to; + amount; + dummy = 0; + return false; + } + + // Selector: transferFrom(address,address,uint256) 23b872dd + function transferFrom( + address from, + address to, + uint256 amount + ) public returns (bool) { + require(false, stub_error); + from; + to; + amount; + dummy = 0; + return false; + } + + // Selector: approve(address,uint256) 095ea7b3 + function approve(address spender, uint256 amount) public returns (bool) { + require(false, stub_error); + spender; + amount; + dummy = 0; + return false; + } + + // Selector: allowance(address,address) dd62ed3e + function allowance(address owner, address spender) + public + view + returns (uint256) + { + require(false, stub_error); + owner; + spender; + dummy; + return 0; + } +} + +// Selector: aa7d570d contract Collection is Dummy, ERC165 { // Set collection property. // @@ -267,90 +351,24 @@ require(false, stub_error); mode; dummy = 0; - } -} - -// Selector: 942e8b22 -contract ERC20 is Dummy, ERC165, ERC20Events { - // Selector: name() 06fdde03 - function name() public view returns (string memory) { - require(false, stub_error); - dummy; - return ""; } - // Selector: symbol() 95d89b41 - function symbol() public view returns (string memory) { - require(false, stub_error); - dummy; - return ""; - } - - // Selector: totalSupply() 18160ddd - function totalSupply() public view returns (uint256) { - require(false, stub_error); - dummy; - return 0; - } - - // Selector: decimals() 313ce567 - function decimals() public view returns (uint8) { - require(false, stub_error); - dummy; - return 0; - } - - // Selector: balanceOf(address) 70a08231 - function balanceOf(address owner) public view returns (uint256) { - require(false, stub_error); - owner; - dummy; - return 0; - } - - // Selector: transfer(address,uint256) a9059cbb - function transfer(address to, uint256 amount) public returns (bool) { - require(false, stub_error); - to; - amount; - dummy = 0; - return false; - } - - // Selector: transferFrom(address,address,uint256) 23b872dd - function transferFrom( - address from, - address to, - uint256 amount - ) public returns (bool) { + // Check that account is the owner or admin of the collection + // + // @return "true" if account is the owner or admin + // + // Selector: verifyOwnerOrAdmin() 04a46053 + function verifyOwnerOrAdmin() public returns (bool) { require(false, stub_error); - from; - to; - amount; dummy = 0; return false; } - // Selector: approve(address,uint256) 095ea7b3 - function approve(address spender, uint256 amount) public returns (bool) { + // Selector: uniqueCollectionType() d34b55b8 + function uniqueCollectionType() public returns (string memory) { require(false, stub_error); - spender; - amount; dummy = 0; - return false; - } - - // Selector: allowance(address,address) dd62ed3e - function allowance(address owner, address spender) - public - view - returns (uint256) - { - require(false, stub_error); - owner; - spender; - dummy; - return 0; + return ""; } } --- a/pallets/nonfungible/src/benchmarking.rs +++ b/pallets/nonfungible/src/benchmarking.rs @@ -17,11 +17,14 @@ use super::*; use crate::{Pallet, Config, NonfungibleHandle}; -use sp_std::prelude::*; -use pallet_common::benchmarking::{create_collection_raw, property_key, property_value}; use frame_benchmarking::{benchmarks, account}; +use pallet_common::{ + bench_init, + benchmarking::{create_collection_raw, property_key, property_value}, + CommonCollectionOperations, +}; +use sp_std::prelude::*; use up_data_structs::{CollectionMode, MAX_ITEMS_PER_BATCH, MAX_PROPERTIES_PER_ITEM, budget::Unlimited}; -use pallet_common::bench_init; const SEED: u32 = 1; @@ -208,4 +211,13 @@ >::set_token_properties(&collection, &owner, item, props.into_iter(), false, &Unlimited)?; let to_delete = (0..b).map(|k| property_key(k as usize)).collect::>(); }: {>::delete_token_properties(&collection, &owner, item, to_delete.into_iter(), &Unlimited)?} + + token_owner { + bench_init!{ + owner: sub; collection: collection(owner); + owner: cross_from_sub; + }; + let item = create_max_item(&collection, &owner, owner.clone())?; + + }: {collection.token_owner(item)} } --- a/pallets/nonfungible/src/common.rs +++ b/pallets/nonfungible/src/common.rs @@ -118,6 +118,10 @@ >::burn_recursively_breadth_plus_self_plus_self_per_each_raw(amount) .saturating_sub(Self::burn_recursively_self_raw().saturating_mul(amount as u64 + 1)) } + + fn token_owner() -> Weight { + 0 //>::token_owner() + } } fn map_create_data( --- a/pallets/nonfungible/src/lib.rs +++ b/pallets/nonfungible/src/lib.rs @@ -784,6 +784,23 @@ >::set_token_property_permissions(collection, sender, property_permissions) } + /// Set property permissions for the token with scope. + /// + /// Sender should be the owner or admin of token's collection. + pub fn set_scoped_token_property_permissions( + collection: &CollectionHandle, + sender: &T::CrossAccountId, + scope: PropertyScope, + property_permissions: Vec, + ) -> DispatchResult { + >::set_scoped_token_property_permissions( + collection, + sender, + scope, + property_permissions, + ) + } + /// Set property permissions for the collection. /// /// Sender should be the owner or admin of the collection. --- a/pallets/nonfungible/src/stubs/UniqueNFT.sol +++ b/pallets/nonfungible/src/stubs/UniqueNFT.sol @@ -415,7 +415,7 @@ } } -// Selector: 7d9262e6 +// Selector: aa7d570d contract Collection is Dummy, ERC165 { // Set collection property. // @@ -640,6 +640,24 @@ mode; dummy = 0; } + + // Check that account is the owner or admin of the collection + // + // @return "true" if account is the owner or admin + // + // Selector: verifyOwnerOrAdmin() 04a46053 + function verifyOwnerOrAdmin() public returns (bool) { + require(false, stub_error); + dummy = 0; + return false; + } + + // Selector: uniqueCollectionType() d34b55b8 + function uniqueCollectionType() public returns (string memory) { + require(false, stub_error); + dummy = 0; + return ""; + } } // Selector: d74d154f --- a/pallets/nonfungible/src/weights.rs +++ b/pallets/nonfungible/src/weights.rs @@ -3,7 +3,7 @@ //! Autogenerated weights for pallet_nonfungible //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-07-20, STEPS: `50`, REPEAT: 80, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2022-08-01, STEPS: `50`, REPEAT: 80, LOW RANGE: `[]`, HIGH RANGE: `[]` //! EXECUTION: None, WASM-EXECUTION: Compiled, CHAIN: None, DB CACHE: 1024 // Executed Command: @@ -46,6 +46,7 @@ fn set_token_property_permissions(b: u32, ) -> Weight; fn set_token_properties(b: u32, ) -> Weight; fn delete_token_properties(b: u32, ) -> Weight; + fn token_owner() -> Weight; } /// Weights for pallet_nonfungible using the Substrate node and recommended hardware. @@ -56,7 +57,7 @@ // Storage: Nonfungible TokenData (r:0 w:1) // Storage: Nonfungible Owned (r:0 w:1) fn create_item() -> Weight { - (20_328_000 as Weight) + (20_909_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(4 as Weight)) } @@ -65,9 +66,9 @@ // Storage: Nonfungible TokenData (r:0 w:4) // Storage: Nonfungible Owned (r:0 w:4) fn create_multiple_items(b: u32, ) -> Weight { - (10_134_000 as Weight) - // Standard Error: 3_000 - .saturating_add((4_927_000 as Weight).saturating_mul(b as Weight)) + (12_601_000 as Weight) + // Standard Error: 1_000 + .saturating_add((4_920_000 as Weight).saturating_mul(b as Weight)) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) .saturating_add(T::DbWeight::get().writes((2 as Weight).saturating_mul(b as Weight))) @@ -77,9 +78,9 @@ // Storage: Nonfungible TokenData (r:0 w:4) // Storage: Nonfungible Owned (r:0 w:4) fn create_multiple_items_ex(b: u32, ) -> Weight { - (5_710_000 as Weight) - // Standard Error: 4_000 - .saturating_add((7_578_000 as Weight).saturating_mul(b as Weight)) + (0 as Weight) + // Standard Error: 3_000 + .saturating_add((7_734_000 as Weight).saturating_mul(b as Weight)) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().reads((1 as Weight).saturating_mul(b as Weight))) .saturating_add(T::DbWeight::get().writes(1 as Weight)) @@ -93,7 +94,7 @@ // Storage: Nonfungible Owned (r:0 w:1) // Storage: Nonfungible TokenProperties (r:0 w:1) fn burn_item() -> Weight { - (28_433_000 as Weight) + (29_746_000 as Weight) .saturating_add(T::DbWeight::get().reads(5 as Weight)) .saturating_add(T::DbWeight::get().writes(5 as Weight)) } @@ -105,7 +106,7 @@ // Storage: Nonfungible Owned (r:0 w:1) // Storage: Nonfungible TokenProperties (r:0 w:1) fn burn_recursively_self_raw() -> Weight { - (34_435_000 as Weight) + (36_077_000 as Weight) .saturating_add(T::DbWeight::get().reads(5 as Weight)) .saturating_add(T::DbWeight::get().writes(5 as Weight)) } @@ -119,8 +120,8 @@ // Storage: Common CollectionById (r:1 w:0) fn burn_recursively_breadth_plus_self_plus_self_per_each_raw(b: u32, ) -> Weight { (0 as Weight) - // Standard Error: 1_539_000 - .saturating_add((304_456_000 as Weight).saturating_mul(b as Weight)) + // Standard Error: 1_605_000 + .saturating_add((312_391_000 as Weight).saturating_mul(b as Weight)) .saturating_add(T::DbWeight::get().reads(7 as Weight)) .saturating_add(T::DbWeight::get().reads((4 as Weight).saturating_mul(b as Weight))) .saturating_add(T::DbWeight::get().writes(6 as Weight)) @@ -131,14 +132,14 @@ // Storage: Nonfungible Allowance (r:1 w:0) // Storage: Nonfungible Owned (r:0 w:2) fn transfer() -> Weight { - (24_376_000 as Weight) + (25_248_000 as Weight) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(5 as Weight)) } // Storage: Nonfungible TokenData (r:1 w:0) // Storage: Nonfungible Allowance (r:1 w:1) fn approve() -> Weight { - (15_890_000 as Weight) + (16_321_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -147,7 +148,7 @@ // Storage: Nonfungible AccountBalance (r:2 w:2) // Storage: Nonfungible Owned (r:0 w:2) fn transfer_from() -> Weight { - (28_634_000 as Weight) + (29_325_000 as Weight) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(6 as Weight)) } @@ -159,15 +160,15 @@ // Storage: Nonfungible Owned (r:0 w:1) // Storage: Nonfungible TokenProperties (r:0 w:1) fn burn_from() -> Weight { - (32_201_000 as Weight) + (33_323_000 as Weight) .saturating_add(T::DbWeight::get().reads(5 as Weight)) .saturating_add(T::DbWeight::get().writes(6 as Weight)) } // Storage: Common CollectionPropertyPermissions (r:1 w:1) fn set_token_property_permissions(b: u32, ) -> Weight { (0 as Weight) - // Standard Error: 57_000 - .saturating_add((15_232_000 as Weight).saturating_mul(b as Weight)) + // Standard Error: 62_000 + .saturating_add((16_222_000 as Weight).saturating_mul(b as Weight)) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -175,8 +176,8 @@ // Storage: Nonfungible TokenProperties (r:1 w:1) fn set_token_properties(b: u32, ) -> Weight { (0 as Weight) - // Standard Error: 1_648_000 - .saturating_add((288_654_000 as Weight).saturating_mul(b as Weight)) + // Standard Error: 1_750_000 + .saturating_add((304_476_000 as Weight).saturating_mul(b as Weight)) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -184,11 +185,16 @@ // Storage: Nonfungible TokenProperties (r:1 w:1) fn delete_token_properties(b: u32, ) -> Weight { (0 as Weight) - // Standard Error: 1_632_000 - .saturating_add((289_190_000 as Weight).saturating_mul(b as Weight)) + // Standard Error: 1_638_000 + .saturating_add((294_096_000 as Weight).saturating_mul(b as Weight)) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } + // Storage: Nonfungible TokenData (r:1 w:0) + fn token_owner() -> Weight { + (2_986_000 as Weight) + .saturating_add(T::DbWeight::get().reads(1 as Weight)) + } } // For backwards compatibility and tests @@ -198,7 +204,7 @@ // Storage: Nonfungible TokenData (r:0 w:1) // Storage: Nonfungible Owned (r:0 w:1) fn create_item() -> Weight { - (20_328_000 as Weight) + (20_909_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(4 as Weight)) } @@ -207,9 +213,9 @@ // Storage: Nonfungible TokenData (r:0 w:4) // Storage: Nonfungible Owned (r:0 w:4) fn create_multiple_items(b: u32, ) -> Weight { - (10_134_000 as Weight) - // Standard Error: 3_000 - .saturating_add((4_927_000 as Weight).saturating_mul(b as Weight)) + (12_601_000 as Weight) + // Standard Error: 1_000 + .saturating_add((4_920_000 as Weight).saturating_mul(b as Weight)) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) .saturating_add(RocksDbWeight::get().writes((2 as Weight).saturating_mul(b as Weight))) @@ -219,9 +225,9 @@ // Storage: Nonfungible TokenData (r:0 w:4) // Storage: Nonfungible Owned (r:0 w:4) fn create_multiple_items_ex(b: u32, ) -> Weight { - (5_710_000 as Weight) - // Standard Error: 4_000 - .saturating_add((7_578_000 as Weight).saturating_mul(b as Weight)) + (0 as Weight) + // Standard Error: 3_000 + .saturating_add((7_734_000 as Weight).saturating_mul(b as Weight)) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().reads((1 as Weight).saturating_mul(b as Weight))) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) @@ -235,7 +241,7 @@ // Storage: Nonfungible Owned (r:0 w:1) // Storage: Nonfungible TokenProperties (r:0 w:1) fn burn_item() -> Weight { - (28_433_000 as Weight) + (29_746_000 as Weight) .saturating_add(RocksDbWeight::get().reads(5 as Weight)) .saturating_add(RocksDbWeight::get().writes(5 as Weight)) } @@ -247,7 +253,7 @@ // Storage: Nonfungible Owned (r:0 w:1) // Storage: Nonfungible TokenProperties (r:0 w:1) fn burn_recursively_self_raw() -> Weight { - (34_435_000 as Weight) + (36_077_000 as Weight) .saturating_add(RocksDbWeight::get().reads(5 as Weight)) .saturating_add(RocksDbWeight::get().writes(5 as Weight)) } @@ -261,8 +267,8 @@ // Storage: Common CollectionById (r:1 w:0) fn burn_recursively_breadth_plus_self_plus_self_per_each_raw(b: u32, ) -> Weight { (0 as Weight) - // Standard Error: 1_539_000 - .saturating_add((304_456_000 as Weight).saturating_mul(b as Weight)) + // Standard Error: 1_605_000 + .saturating_add((312_391_000 as Weight).saturating_mul(b as Weight)) .saturating_add(RocksDbWeight::get().reads(7 as Weight)) .saturating_add(RocksDbWeight::get().reads((4 as Weight).saturating_mul(b as Weight))) .saturating_add(RocksDbWeight::get().writes(6 as Weight)) @@ -273,14 +279,14 @@ // Storage: Nonfungible Allowance (r:1 w:0) // Storage: Nonfungible Owned (r:0 w:2) fn transfer() -> Weight { - (24_376_000 as Weight) + (25_248_000 as Weight) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(5 as Weight)) } // Storage: Nonfungible TokenData (r:1 w:0) // Storage: Nonfungible Allowance (r:1 w:1) fn approve() -> Weight { - (15_890_000 as Weight) + (16_321_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -289,7 +295,7 @@ // Storage: Nonfungible AccountBalance (r:2 w:2) // Storage: Nonfungible Owned (r:0 w:2) fn transfer_from() -> Weight { - (28_634_000 as Weight) + (29_325_000 as Weight) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(6 as Weight)) } @@ -301,15 +307,15 @@ // Storage: Nonfungible Owned (r:0 w:1) // Storage: Nonfungible TokenProperties (r:0 w:1) fn burn_from() -> Weight { - (32_201_000 as Weight) + (33_323_000 as Weight) .saturating_add(RocksDbWeight::get().reads(5 as Weight)) .saturating_add(RocksDbWeight::get().writes(6 as Weight)) } // Storage: Common CollectionPropertyPermissions (r:1 w:1) fn set_token_property_permissions(b: u32, ) -> Weight { (0 as Weight) - // Standard Error: 57_000 - .saturating_add((15_232_000 as Weight).saturating_mul(b as Weight)) + // Standard Error: 62_000 + .saturating_add((16_222_000 as Weight).saturating_mul(b as Weight)) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -317,8 +323,8 @@ // Storage: Nonfungible TokenProperties (r:1 w:1) fn set_token_properties(b: u32, ) -> Weight { (0 as Weight) - // Standard Error: 1_648_000 - .saturating_add((288_654_000 as Weight).saturating_mul(b as Weight)) + // Standard Error: 1_750_000 + .saturating_add((304_476_000 as Weight).saturating_mul(b as Weight)) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -326,9 +332,14 @@ // Storage: Nonfungible TokenProperties (r:1 w:1) fn delete_token_properties(b: u32, ) -> Weight { (0 as Weight) - // Standard Error: 1_632_000 - .saturating_add((289_190_000 as Weight).saturating_mul(b as Weight)) + // Standard Error: 1_638_000 + .saturating_add((294_096_000 as Weight).saturating_mul(b as Weight)) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } + // Storage: Nonfungible TokenData (r:1 w:0) + fn token_owner() -> Weight { + (2_986_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(1 as Weight)) + } } --- a/pallets/refungible/src/benchmarking.rs +++ b/pallets/refungible/src/benchmarking.rs @@ -17,16 +17,19 @@ use super::*; use crate::{Pallet, Config, RefungibleHandle}; -use sp_std::prelude::*; -use pallet_common::benchmarking::{create_collection_raw, property_key, property_value, create_data}; +use core::convert::TryInto; +use core::iter::IntoIterator; use frame_benchmarking::{benchmarks, account}; +use pallet_common::{ + bench_init, + benchmarking::{create_collection_raw, property_key, property_value, create_data}, +}; +use sp_core::H160; +use sp_std::prelude::*; use up_data_structs::{ CollectionMode, MAX_ITEMS_PER_BATCH, MAX_PROPERTIES_PER_ITEM, CUSTOM_DATA_LIMIT, budget::Unlimited, }; -use pallet_common::bench_init; -use core::convert::TryInto; -use core::iter::IntoIterator; const SEED: u32 = 1; @@ -44,6 +47,7 @@ properties: Default::default(), } } + fn create_max_item( collection: &RefungibleHandle, sender: &T::CrossAccountId, @@ -59,11 +63,12 @@ ) -> Result, DispatchError> { create_collection_raw( owner, - CollectionMode::NFT, + CollectionMode::ReFungible, >::init_collection, RefungibleHandle::cast, ) } + benchmarks! { create_item { bench_init!{ @@ -277,4 +282,21 @@ }; let item = create_max_item(&collection, &sender, [(owner.clone(), 100)])?; }: {>::repartition(&collection, &owner, item, 200)?} + + set_parent_nft_unchecked { + bench_init!{ + owner: sub; collection: collection(owner); + sender: cross_from_sub(owner); owner: cross_sub; + }; + let item = create_max_item(&collection, &sender, [(owner.clone(), 100)])?; + + }: {>::set_parent_nft_unchecked(&collection, item, owner, T::CrossAccountId::from_eth(H160::default()))?} + + token_owner { + bench_init!{ + owner: sub; collection: collection(owner); + sender: cross_from_sub(owner); owner: cross_sub; + }; + let item = create_max_item(&collection, &sender, [(owner.clone(), 100)])?; + }: {>::token_owner(collection.id, item)} } --- a/pallets/refungible/src/common.rs +++ b/pallets/refungible/src/common.rs @@ -148,6 +148,10 @@ // Refungible token can't have children 0 } + + fn token_owner() -> Weight { + 0 //>::token_owner() + } } fn map_create_data( --- a/pallets/refungible/src/erc.rs +++ b/pallets/refungible/src/erc.rs @@ -41,8 +41,8 @@ use sp_core::H160; use sp_std::{collections::btree_map::BTreeMap, vec::Vec, vec}; use up_data_structs::{ - CollectionId, CollectionPropertiesVec, Property, PropertyKey, PropertyKeyPermission, - PropertyPermission, TokenId, + CollectionId, CollectionPropertiesVec, mapping::TokenAddressMapping, Property, PropertyKey, + PropertyKeyPermission, PropertyPermission, TokenId, }; use crate::{ @@ -413,7 +413,7 @@ } /// Returns amount of pieces of `token` that `owner` have -fn balance( +pub fn balance( collection: &RefungibleHandle, token: TokenId, owner: &T::CrossAccountId, @@ -424,7 +424,7 @@ } /// Throws if `owner_balance` is lower than total amount of `token` pieces -fn ensure_single_owner( +pub fn ensure_single_owner( collection: &RefungibleHandle, token: TokenId, owner_balance: u128, @@ -788,6 +788,16 @@ .map_err(dispatch_to_evm::)?; Ok(true) } + + /// Returns EVM address for refungible token + /// + /// @param token ID of the token + fn token_contract_address(&self, token: uint256) -> Result
{ + Ok(T::EvmTokenAddressMapping::token_to_address( + self.id, + token.try_into().map_err(|_| "token id overflow")?, + )) + } } #[solidity_interface( --- a/pallets/refungible/src/erc_token.rs +++ b/pallets/refungible/src/erc_token.rs @@ -20,29 +20,89 @@ //! Method implementations are mostly doing parameter conversion and calling Nonfungible Pallet methods. extern crate alloc; + +#[cfg(not(feature = "std"))] +use alloc::format; + use core::{ char::{REPLACEMENT_CHARACTER, decode_utf16}, convert::TryInto, ops::Deref, }; -use evm_coder::{ToLog, execution::*, generate_stubgen, solidity_interface, types::*, weight}; +use evm_coder::{ToLog, execution::*, generate_stubgen, solidity, solidity_interface, types::*, weight}; use pallet_common::{ CommonWeightInfo, - erc::{CommonEvmHandler, PrecompileResult}, + erc::{CommonEvmHandler, PrecompileResult, static_property::key}, + eth::map_eth_to_id, }; use pallet_evm::{account::CrossAccountId, PrecompileHandle}; use pallet_evm_coder_substrate::{call, dispatch_to_evm, WithRecorder}; use pallet_structure::{SelfWeightOf as StructureWeight, weights::WeightInfo as _}; +use sp_core::H160; use sp_std::vec::Vec; -use up_data_structs::TokenId; +use up_data_structs::{mapping::TokenAddressMapping, PropertyScope, TokenId}; use crate::{ Allowance, Balance, common::CommonWeights, Config, Pallet, RefungibleHandle, SelfWeightOf, - weights::WeightInfo, TotalSupply, + TokenProperties, TotalSupply, weights::WeightInfo, }; pub struct RefungibleTokenHandle(pub RefungibleHandle, pub TokenId); +#[solidity_interface(name = "ERC1633")] +impl RefungibleTokenHandle { + fn parent_token(&self) -> Result
{ + self.consume_store_reads(2)?; + let props = >::get((self.id, self.1)); + let key = key::parent_nft(); + + let key_scoped = PropertyScope::Eth + .apply(key) + .expect("property key shouldn't exceed length limit"); + let value = props.get(&key_scoped).ok_or("key not found")?; + Ok(H160::from_slice(value.as_slice())) + } + + fn parent_token_id(&self) -> Result { + self.consume_store_reads(2)?; + let props = >::get((self.id, self.1)); + let key = key::parent_nft(); + + let key_scoped = PropertyScope::Eth + .apply(key) + .expect("property key shouldn't exceed length limit"); + let value = props.get(&key_scoped).ok_or("key not found")?; + let nft_token_address = H160::from_slice(value.as_slice()); + let nft_token_account = T::CrossAccountId::from_eth(nft_token_address); + let (_, token_id) = T::CrossTokenAddressMapping::address_to_token(&nft_token_account) + .ok_or("parent NFT should contain NFT token address")?; + + Ok(token_id.into()) + } +} + +#[solidity_interface(name = "ERC1633UniqueExtensions")] +impl RefungibleTokenHandle { + #[solidity(rename_selector = "setParentNFT")] + #[weight(>::token_owner() + >::set_parent_nft_unchecked())] + fn set_parent_nft( + &mut self, + caller: caller, + collection: address, + nft_id: uint256, + ) -> Result { + self.consume_store_reads(1)?; + let caller = T::CrossAccountId::from_eth(caller); + let nft_collection = map_eth_to_id(&collection).ok_or("collection not found")?; + let nft_token = nft_id.try_into()?; + + >::set_parent_nft(&self.0, self.1, caller, nft_collection, nft_token) + .map_err(dispatch_to_evm::)?; + + Ok(true) + } +} + #[derive(ToLog)] pub enum ERC20Events { /// @dev This event is emitted when the amount of tokens (value) is sent @@ -239,7 +299,10 @@ } } -#[solidity_interface(name = "UniqueRefungibleToken", is(ERC20, ERC20UniqueExtensions,))] +#[solidity_interface( + name = "UniqueRefungibleToken", + is(ERC20, ERC20UniqueExtensions, ERC1633, ERC1633UniqueExtensions) +)] impl RefungibleTokenHandle where T::AccountId: From<[u8; 32]> {} generate_stubgen!(gen_impl, UniqueRefungibleTokenCall<()>, true); --- a/pallets/refungible/src/lib.rs +++ b/pallets/refungible/src/lib.rs @@ -99,8 +99,12 @@ use pallet_evm::{account::CrossAccountId, Pallet as PalletEvm}; use pallet_evm_coder_substrate::WithRecorder; use pallet_common::{ - CommonCollectionOperations, Error as CommonError, Event as CommonEvent, - eth::collection_id_to_address, Pallet as PalletCommon, + CollectionHandle, CommonCollectionOperations, + dispatch::CollectionDispatch, + erc::static_property::{key, property_value_from_bytes}, + Error as CommonError, + eth::collection_id_to_address, + Event as CommonEvent, Pallet as PalletCommon, }; use pallet_structure::Pallet as PalletStructure; use scale_info::TypeInfo; @@ -108,10 +112,10 @@ use sp_runtime::{ArithmeticError, DispatchError, DispatchResult, TransactionOutcome}; use sp_std::{vec::Vec, vec, collections::btree_map::BTreeMap}; use up_data_structs::{ - AccessMode, budget::Budget, CollectionId, CreateCollectionData, CustomDataLimit, - mapping::TokenAddressMapping, MAX_REFUNGIBLE_PIECES, MAX_ITEMS_PER_BATCH, TokenId, Property, + AccessMode, budget::Budget, CollectionId, CollectionMode, CollectionPropertiesVec, CreateCollectionData, CustomDataLimit, + mapping::TokenAddressMapping, MAX_ITEMS_PER_BATCH, MAX_REFUNGIBLE_PIECES, Property, PropertyKey, PropertyKeyPermission, PropertyPermission, PropertyScope, PropertyValue, - TrySetProperty, CollectionPropertiesVec, + TokenId, TrySetProperty, }; use frame_support::BoundedBTreeMap; use derivative::Derivative; @@ -1341,6 +1345,20 @@ >::set_token_property_permissions(collection, sender, property_permissions) } + pub fn set_scoped_token_property_permissions( + collection: &RefungibleHandle, + sender: &T::CrossAccountId, + scope: PropertyScope, + property_permissions: Vec, + ) -> DispatchResult { + >::set_scoped_token_property_permissions( + collection, + sender, + scope, + property_permissions, + ) + } + /// Returns 10 token in no particular order. /// /// There is no direct way to get token holders in ascending order, @@ -1362,4 +1380,68 @@ Some(res) } } + + /// Sets the NFT token as a parent for the RFT token + /// + /// Throws if `sender` is not the owner of the NFT token. + /// Throws if `sender` is not the owner of all of the RFT token pieces. + pub fn set_parent_nft( + collection: &RefungibleHandle, + rft_token_id: TokenId, + sender: T::CrossAccountId, + nft_collection: CollectionId, + nft_token: TokenId, + ) -> DispatchResult { + let handle = >::try_get(nft_collection)?; + if handle.mode != CollectionMode::NFT { + return Err("Only NFT token could be parent to RFT".into()); + } + let dispatch = T::CollectionDispatch::dispatch(handle); + let dispatch = dispatch.as_dyn(); + + let owner = dispatch.token_owner(nft_token).ok_or("owner not found")?; + if owner != sender { + return Err("Only owned token could be set as parent".into()); + } + + let nft_token_address = + T::CrossTokenAddressMapping::token_to_address(nft_collection, nft_token); + + Self::set_parent_nft_unchecked(collection, rft_token_id, sender, nft_token_address) + } + + /// Sets the NFT token as a parent for the RFT token + /// + /// `sender` should be the owner of the NFT token. + /// Throws if `sender` is not the owner of all of the RFT token pieces. + pub fn set_parent_nft_unchecked( + collection: &RefungibleHandle, + rft_token_id: TokenId, + sender: T::CrossAccountId, + nft_token_address: T::CrossAccountId, + ) -> DispatchResult { + let owner_balance = >::get((collection.id, rft_token_id, &sender)); + let total_supply = >::get((collection.id, rft_token_id)); + if total_supply != owner_balance { + return Err("token has multiple owners".into()); + } + + let parent_nft_property_key = key::parent_nft(); + + let parent_nft_property_value = + property_value_from_bytes(&nft_token_address.as_eth().to_fixed_bytes()) + .expect("address should fit in value length limit"); + + >::set_scoped_token_property( + collection.id, + rft_token_id, + PropertyScope::Eth, + Property { + key: parent_nft_property_key, + value: parent_nft_property_value, + }, + )?; + + Ok(()) + } } --- a/pallets/refungible/src/stubs/UniqueRefungible.sol +++ b/pallets/refungible/src/stubs/UniqueRefungible.sol @@ -413,7 +413,100 @@ } } -// Selector: 7d9262e6 +// Selector: 7c3bef89 +contract ERC721UniqueExtensions is Dummy, ERC165 { + // @notice Transfer ownership of an RFT + // @dev Throws unless `msg.sender` is the current owner. Throws if `to` + // is the zero address. Throws if `tokenId` is not a valid RFT. + // Throws if RFT pieces have multiple owners. + // @param to The new owner + // @param tokenId The RFT to transfer + // @param _value Not used for an RFT + // + // Selector: transfer(address,uint256) a9059cbb + function transfer(address to, uint256 tokenId) public { + require(false, stub_error); + to; + tokenId; + dummy = 0; + } + + // @notice Burns a specific ERC721 token. + // @dev Throws unless `msg.sender` is the current owner or an authorized + // operator for this RFT. Throws if `from` is not the current owner. Throws + // if `to` is the zero address. Throws if `tokenId` is not a valid RFT. + // Throws if RFT pieces have multiple owners. + // @param from The current owner of the RFT + // @param tokenId The RFT to transfer + // @param _value Not used for an RFT + // + // Selector: burnFrom(address,uint256) 79cc6790 + function burnFrom(address from, uint256 tokenId) public { + require(false, stub_error); + from; + tokenId; + dummy = 0; + } + + // @notice Returns next free RFT ID. + // + // Selector: nextTokenId() 75794a3c + function nextTokenId() public view returns (uint256) { + require(false, stub_error); + dummy; + return 0; + } + + // @notice Function to mint multiple tokens. + // @dev `tokenIds` should be an array of consecutive numbers and first number + // should be obtained with `nextTokenId` method + // @param to The new owner + // @param tokenIds IDs of the minted RFTs + // + // Selector: mintBulk(address,uint256[]) 44a9945e + function mintBulk(address to, uint256[] memory tokenIds) + public + returns (bool) + { + require(false, stub_error); + to; + tokenIds; + dummy = 0; + return false; + } + + // @notice Function to mint multiple tokens with the given tokenUris. + // @dev `tokenIds` is array of pairs of token ID and token URI. Token IDs should be consecutive + // numbers and first number should be obtained with `nextTokenId` method + // @param to The new owner + // @param tokens array of pairs of token ID and token URI for minted tokens + // + // Selector: mintBulkWithTokenURI(address,(uint256,string)[]) 36543006 + function mintBulkWithTokenURI(address to, Tuple0[] memory tokens) + public + returns (bool) + { + require(false, stub_error); + to; + tokens; + dummy = 0; + return false; + } + + // Returns EVM address for refungible token + // + // @param token ID of the token + // + // Selector: tokenContractAddress(uint256) ab76fac6 + function tokenContractAddress(uint256 token) public view returns (address) { + require(false, stub_error); + token; + dummy; + return 0x0000000000000000000000000000000000000000; + } +} + +// Selector: aa7d570d contract Collection is Dummy, ERC165 { // Set collection property. // @@ -636,88 +729,29 @@ function setCollectionMintMode(bool mode) public { require(false, stub_error); mode; - dummy = 0; - } -} - -// Selector: d74d154f -contract ERC721UniqueExtensions is Dummy, ERC165 { - // @notice Transfer ownership of an RFT - // @dev Throws unless `msg.sender` is the current owner. Throws if `to` - // is the zero address. Throws if `tokenId` is not a valid RFT. - // Throws if RFT pieces have multiple owners. - // @param to The new owner - // @param tokenId The RFT to transfer - // @param _value Not used for an RFT - // - // Selector: transfer(address,uint256) a9059cbb - function transfer(address to, uint256 tokenId) public { - require(false, stub_error); - to; - tokenId; - dummy = 0; - } - - // @notice Burns a specific ERC721 token. - // @dev Throws unless `msg.sender` is the current owner or an authorized - // operator for this RFT. Throws if `from` is not the current owner. Throws - // if `to` is the zero address. Throws if `tokenId` is not a valid RFT. - // Throws if RFT pieces have multiple owners. - // @param from The current owner of the RFT - // @param tokenId The RFT to transfer - // @param _value Not used for an RFT - // - // Selector: burnFrom(address,uint256) 79cc6790 - function burnFrom(address from, uint256 tokenId) public { - require(false, stub_error); - from; - tokenId; dummy = 0; } - // @notice Returns next free RFT ID. + // Check that account is the owner or admin of the collection // - // Selector: nextTokenId() 75794a3c - function nextTokenId() public view returns (uint256) { - require(false, stub_error); - dummy; - return 0; - } - - // @notice Function to mint multiple tokens. - // @dev `tokenIds` should be an array of consecutive numbers and first number - // should be obtained with `nextTokenId` method - // @param to The new owner - // @param tokenIds IDs of the minted RFTs + // @return "true" if account is the owner or admin // - // Selector: mintBulk(address,uint256[]) 44a9945e - function mintBulk(address to, uint256[] memory tokenIds) - public - returns (bool) - { + // Selector: verifyOwnerOrAdmin() 04a46053 + function verifyOwnerOrAdmin() public returns (bool) { require(false, stub_error); - to; - tokenIds; dummy = 0; return false; } - // @notice Function to mint multiple tokens with the given tokenUris. - // @dev `tokenIds` is array of pairs of token ID and token URI. Token IDs should be consecutive - // numbers and first number should be obtained with `nextTokenId` method - // @param to The new owner - // @param tokens array of pairs of token ID and token URI for minted tokens + // Returns collection type // - // Selector: mintBulkWithTokenURI(address,(uint256,string)[]) 36543006 - function mintBulkWithTokenURI(address to, Tuple0[] memory tokens) - public - returns (bool) - { + // @return `Fungible` or `NFT` or `ReFungible` + // + // Selector: uniqueCollectionType() d34b55b8 + function uniqueCollectionType() public returns (string memory) { require(false, stub_error); - to; - tokens; dummy = 0; - return false; + return ""; } } --- a/pallets/refungible/src/stubs/UniqueRefungibleToken.sol +++ b/pallets/refungible/src/stubs/UniqueRefungibleToken.sol @@ -31,6 +31,38 @@ ); } +// Selector: 042f1106 +contract ERC1633UniqueExtensions is Dummy, ERC165 { + // Selector: setParentNFT(address,uint256) 042f1106 + function setParentNFT(address collection, uint256 nftId) + public + returns (bool) + { + require(false, stub_error); + collection; + nftId; + dummy = 0; + return false; + } +} + +// Selector: 5755c3f2 +contract ERC1633 is Dummy, ERC165 { + // Selector: parentToken() 80a54001 + function parentToken() public view returns (address) { + require(false, stub_error); + dummy; + return 0x0000000000000000000000000000000000000000; + } + + // Selector: parentTokenId() d7f083f3 + function parentTokenId() public view returns (uint256) { + require(false, stub_error); + dummy; + return 0; + } +} + // Selector: 942e8b22 contract ERC20 is Dummy, ERC165, ERC20Events { // @return the name of the token. @@ -178,4 +210,11 @@ } } -contract UniqueRefungibleToken is Dummy, ERC165, ERC20, ERC20UniqueExtensions {} +contract UniqueRefungibleToken is + Dummy, + ERC165, + ERC20, + ERC20UniqueExtensions, + ERC1633, + ERC1633UniqueExtensions +{} --- a/pallets/refungible/src/weights.rs +++ b/pallets/refungible/src/weights.rs @@ -3,7 +3,7 @@ //! Autogenerated weights for pallet_refungible //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-07-20, STEPS: `50`, REPEAT: 80, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2022-08-01, STEPS: `50`, REPEAT: 80, LOW RANGE: `[]`, HIGH RANGE: `[]` //! EXECUTION: None, WASM-EXECUTION: Compiled, CHAIN: None, DB CACHE: 1024 // Executed Command: @@ -53,6 +53,8 @@ fn set_token_properties(b: u32, ) -> Weight; fn delete_token_properties(b: u32, ) -> Weight; fn repartition_item() -> Weight; + fn set_parent_nft_unchecked() -> Weight; + fn token_owner() -> Weight; } /// Weights for pallet_refungible using the Substrate node and recommended hardware. @@ -65,7 +67,7 @@ // Storage: Refungible TokenData (r:0 w:1) // Storage: Refungible Owned (r:0 w:1) fn create_item() -> Weight { - (21_310_000 as Weight) + (25_197_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(6 as Weight)) } @@ -76,9 +78,9 @@ // Storage: Refungible TokenData (r:0 w:4) // Storage: Refungible Owned (r:0 w:4) fn create_multiple_items(b: u32, ) -> Weight { - (9_552_000 as Weight) + (10_852_000 as Weight) // Standard Error: 2_000 - .saturating_add((7_056_000 as Weight).saturating_mul(b as Weight)) + .saturating_add((8_087_000 as Weight).saturating_mul(b as Weight)) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) .saturating_add(T::DbWeight::get().writes((4 as Weight).saturating_mul(b as Weight))) @@ -90,9 +92,9 @@ // Storage: Refungible TokenData (r:0 w:4) // Storage: Refungible Owned (r:0 w:4) fn create_multiple_items_ex_multiple_items(b: u32, ) -> Weight { - (4_857_000 as Weight) + (9_978_000 as Weight) // Standard Error: 2_000 - .saturating_add((9_838_000 as Weight).saturating_mul(b as Weight)) + .saturating_add((10_848_000 as Weight).saturating_mul(b as Weight)) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().reads((1 as Weight).saturating_mul(b as Weight))) .saturating_add(T::DbWeight::get().writes(1 as Weight)) @@ -105,9 +107,9 @@ // Storage: Refungible Balance (r:0 w:4) // Storage: Refungible Owned (r:0 w:4) fn create_multiple_items_ex_multiple_owners(b: u32, ) -> Weight { - (11_335_000 as Weight) + (15_419_000 as Weight) // Standard Error: 2_000 - .saturating_add((6_784_000 as Weight).saturating_mul(b as Weight)) + .saturating_add((7_813_000 as Weight).saturating_mul(b as Weight)) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().reads((1 as Weight).saturating_mul(b as Weight))) .saturating_add(T::DbWeight::get().writes(3 as Weight)) @@ -118,7 +120,7 @@ // Storage: Refungible AccountBalance (r:1 w:1) // Storage: Refungible Owned (r:0 w:1) fn burn_item_partial() -> Weight { - (21_239_000 as Weight) + (25_578_000 as Weight) .saturating_add(T::DbWeight::get().reads(3 as Weight)) .saturating_add(T::DbWeight::get().writes(4 as Weight)) } @@ -130,13 +132,13 @@ // Storage: Refungible Owned (r:0 w:1) // Storage: Refungible TokenProperties (r:0 w:1) fn burn_item_fully() -> Weight { - (29_426_000 as Weight) + (33_593_000 as Weight) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(7 as Weight)) } // Storage: Refungible Balance (r:2 w:2) fn transfer_normal() -> Weight { - (17_743_000 as Weight) + (21_049_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } @@ -144,7 +146,7 @@ // Storage: Refungible AccountBalance (r:1 w:1) // Storage: Refungible Owned (r:0 w:1) fn transfer_creating() -> Weight { - (20_699_000 as Weight) + (24_646_000 as Weight) .saturating_add(T::DbWeight::get().reads(3 as Weight)) .saturating_add(T::DbWeight::get().writes(4 as Weight)) } @@ -152,7 +154,7 @@ // Storage: Refungible AccountBalance (r:1 w:1) // Storage: Refungible Owned (r:0 w:1) fn transfer_removing() -> Weight { - (22_833_000 as Weight) + (26_570_000 as Weight) .saturating_add(T::DbWeight::get().reads(3 as Weight)) .saturating_add(T::DbWeight::get().writes(4 as Weight)) } @@ -160,21 +162,21 @@ // Storage: Refungible AccountBalance (r:2 w:2) // Storage: Refungible Owned (r:0 w:2) fn transfer_creating_removing() -> Weight { - (24_936_000 as Weight) + (28_906_000 as Weight) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(6 as Weight)) } // Storage: Refungible Balance (r:1 w:0) // Storage: Refungible Allowance (r:0 w:1) fn approve() -> Weight { - (13_446_000 as Weight) + (16_451_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Refungible Allowance (r:1 w:1) // Storage: Refungible Balance (r:2 w:2) fn transfer_from_normal() -> Weight { - (24_777_000 as Weight) + (29_545_000 as Weight) .saturating_add(T::DbWeight::get().reads(3 as Weight)) .saturating_add(T::DbWeight::get().writes(3 as Weight)) } @@ -183,7 +185,7 @@ // Storage: Refungible AccountBalance (r:1 w:1) // Storage: Refungible Owned (r:0 w:1) fn transfer_from_creating() -> Weight { - (28_483_000 as Weight) + (33_392_000 as Weight) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(5 as Weight)) } @@ -192,7 +194,7 @@ // Storage: Refungible AccountBalance (r:1 w:1) // Storage: Refungible Owned (r:0 w:1) fn transfer_from_removing() -> Weight { - (29_896_000 as Weight) + (35_446_000 as Weight) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(5 as Weight)) } @@ -201,7 +203,7 @@ // Storage: Refungible AccountBalance (r:2 w:2) // Storage: Refungible Owned (r:0 w:2) fn transfer_from_creating_removing() -> Weight { - (32_070_000 as Weight) + (37_762_000 as Weight) .saturating_add(T::DbWeight::get().reads(5 as Weight)) .saturating_add(T::DbWeight::get().writes(7 as Weight)) } @@ -214,15 +216,15 @@ // Storage: Refungible Owned (r:0 w:1) // Storage: Refungible TokenProperties (r:0 w:1) fn burn_from() -> Weight { - (36_789_000 as Weight) + (42_620_000 as Weight) .saturating_add(T::DbWeight::get().reads(5 as Weight)) .saturating_add(T::DbWeight::get().writes(8 as Weight)) } // Storage: Common CollectionPropertyPermissions (r:1 w:1) fn set_token_property_permissions(b: u32, ) -> Weight { (0 as Weight) - // Standard Error: 62_000 - .saturating_add((15_803_000 as Weight).saturating_mul(b as Weight)) + // Standard Error: 65_000 + .saturating_add((16_513_000 as Weight).saturating_mul(b as Weight)) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -230,8 +232,8 @@ // Storage: Refungible TokenProperties (r:1 w:1) fn set_token_properties(b: u32, ) -> Weight { (0 as Weight) - // Standard Error: 1_668_000 - .saturating_add((302_308_000 as Weight).saturating_mul(b as Weight)) + // Standard Error: 1_583_000 + .saturating_add((291_392_000 as Weight).saturating_mul(b as Weight)) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -239,18 +241,31 @@ // Storage: Refungible TokenProperties (r:1 w:1) fn delete_token_properties(b: u32, ) -> Weight { (0 as Weight) - // Standard Error: 1_619_000 - .saturating_add((294_574_000 as Weight).saturating_mul(b as Weight)) + // Standard Error: 1_699_000 + .saturating_add((293_270_000 as Weight).saturating_mul(b as Weight)) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Refungible TotalSupply (r:1 w:1) // Storage: Refungible Balance (r:1 w:1) fn repartition_item() -> Weight { - (8_325_000 as Weight) + (19_206_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } + // Storage: Refungible Balance (r:1 w:0) + // Storage: Refungible TotalSupply (r:1 w:0) + // Storage: Refungible TokenProperties (r:1 w:1) + fn set_parent_nft_unchecked() -> Weight { + (10_189_000 as Weight) + .saturating_add(T::DbWeight::get().reads(3 as Weight)) + .saturating_add(T::DbWeight::get().writes(1 as Weight)) + } + // Storage: Refungible Balance (r:2 w:0) + fn token_owner() -> Weight { + (8_205_000 as Weight) + .saturating_add(T::DbWeight::get().reads(2 as Weight)) + } } // For backwards compatibility and tests @@ -262,7 +277,7 @@ // Storage: Refungible TokenData (r:0 w:1) // Storage: Refungible Owned (r:0 w:1) fn create_item() -> Weight { - (21_310_000 as Weight) + (25_197_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(6 as Weight)) } @@ -273,9 +288,9 @@ // Storage: Refungible TokenData (r:0 w:4) // Storage: Refungible Owned (r:0 w:4) fn create_multiple_items(b: u32, ) -> Weight { - (9_552_000 as Weight) + (10_852_000 as Weight) // Standard Error: 2_000 - .saturating_add((7_056_000 as Weight).saturating_mul(b as Weight)) + .saturating_add((8_087_000 as Weight).saturating_mul(b as Weight)) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) .saturating_add(RocksDbWeight::get().writes((4 as Weight).saturating_mul(b as Weight))) @@ -287,9 +302,9 @@ // Storage: Refungible TokenData (r:0 w:4) // Storage: Refungible Owned (r:0 w:4) fn create_multiple_items_ex_multiple_items(b: u32, ) -> Weight { - (4_857_000 as Weight) + (9_978_000 as Weight) // Standard Error: 2_000 - .saturating_add((9_838_000 as Weight).saturating_mul(b as Weight)) + .saturating_add((10_848_000 as Weight).saturating_mul(b as Weight)) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().reads((1 as Weight).saturating_mul(b as Weight))) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) @@ -302,9 +317,9 @@ // Storage: Refungible Balance (r:0 w:4) // Storage: Refungible Owned (r:0 w:4) fn create_multiple_items_ex_multiple_owners(b: u32, ) -> Weight { - (11_335_000 as Weight) + (15_419_000 as Weight) // Standard Error: 2_000 - .saturating_add((6_784_000 as Weight).saturating_mul(b as Weight)) + .saturating_add((7_813_000 as Weight).saturating_mul(b as Weight)) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().reads((1 as Weight).saturating_mul(b as Weight))) .saturating_add(RocksDbWeight::get().writes(3 as Weight)) @@ -315,7 +330,7 @@ // Storage: Refungible AccountBalance (r:1 w:1) // Storage: Refungible Owned (r:0 w:1) fn burn_item_partial() -> Weight { - (21_239_000 as Weight) + (25_578_000 as Weight) .saturating_add(RocksDbWeight::get().reads(3 as Weight)) .saturating_add(RocksDbWeight::get().writes(4 as Weight)) } @@ -327,13 +342,13 @@ // Storage: Refungible Owned (r:0 w:1) // Storage: Refungible TokenProperties (r:0 w:1) fn burn_item_fully() -> Weight { - (29_426_000 as Weight) + (33_593_000 as Weight) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(7 as Weight)) } // Storage: Refungible Balance (r:2 w:2) fn transfer_normal() -> Weight { - (17_743_000 as Weight) + (21_049_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } @@ -341,7 +356,7 @@ // Storage: Refungible AccountBalance (r:1 w:1) // Storage: Refungible Owned (r:0 w:1) fn transfer_creating() -> Weight { - (20_699_000 as Weight) + (24_646_000 as Weight) .saturating_add(RocksDbWeight::get().reads(3 as Weight)) .saturating_add(RocksDbWeight::get().writes(4 as Weight)) } @@ -349,7 +364,7 @@ // Storage: Refungible AccountBalance (r:1 w:1) // Storage: Refungible Owned (r:0 w:1) fn transfer_removing() -> Weight { - (22_833_000 as Weight) + (26_570_000 as Weight) .saturating_add(RocksDbWeight::get().reads(3 as Weight)) .saturating_add(RocksDbWeight::get().writes(4 as Weight)) } @@ -357,21 +372,21 @@ // Storage: Refungible AccountBalance (r:2 w:2) // Storage: Refungible Owned (r:0 w:2) fn transfer_creating_removing() -> Weight { - (24_936_000 as Weight) + (28_906_000 as Weight) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(6 as Weight)) } // Storage: Refungible Balance (r:1 w:0) // Storage: Refungible Allowance (r:0 w:1) fn approve() -> Weight { - (13_446_000 as Weight) + (16_451_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Refungible Allowance (r:1 w:1) // Storage: Refungible Balance (r:2 w:2) fn transfer_from_normal() -> Weight { - (24_777_000 as Weight) + (29_545_000 as Weight) .saturating_add(RocksDbWeight::get().reads(3 as Weight)) .saturating_add(RocksDbWeight::get().writes(3 as Weight)) } @@ -380,7 +395,7 @@ // Storage: Refungible AccountBalance (r:1 w:1) // Storage: Refungible Owned (r:0 w:1) fn transfer_from_creating() -> Weight { - (28_483_000 as Weight) + (33_392_000 as Weight) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(5 as Weight)) } @@ -389,7 +404,7 @@ // Storage: Refungible AccountBalance (r:1 w:1) // Storage: Refungible Owned (r:0 w:1) fn transfer_from_removing() -> Weight { - (29_896_000 as Weight) + (35_446_000 as Weight) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(5 as Weight)) } @@ -398,7 +413,7 @@ // Storage: Refungible AccountBalance (r:2 w:2) // Storage: Refungible Owned (r:0 w:2) fn transfer_from_creating_removing() -> Weight { - (32_070_000 as Weight) + (37_762_000 as Weight) .saturating_add(RocksDbWeight::get().reads(5 as Weight)) .saturating_add(RocksDbWeight::get().writes(7 as Weight)) } @@ -411,15 +426,15 @@ // Storage: Refungible Owned (r:0 w:1) // Storage: Refungible TokenProperties (r:0 w:1) fn burn_from() -> Weight { - (36_789_000 as Weight) + (42_620_000 as Weight) .saturating_add(RocksDbWeight::get().reads(5 as Weight)) .saturating_add(RocksDbWeight::get().writes(8 as Weight)) } // Storage: Common CollectionPropertyPermissions (r:1 w:1) fn set_token_property_permissions(b: u32, ) -> Weight { (0 as Weight) - // Standard Error: 62_000 - .saturating_add((15_803_000 as Weight).saturating_mul(b as Weight)) + // Standard Error: 65_000 + .saturating_add((16_513_000 as Weight).saturating_mul(b as Weight)) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -427,8 +442,8 @@ // Storage: Refungible TokenProperties (r:1 w:1) fn set_token_properties(b: u32, ) -> Weight { (0 as Weight) - // Standard Error: 1_668_000 - .saturating_add((302_308_000 as Weight).saturating_mul(b as Weight)) + // Standard Error: 1_583_000 + .saturating_add((291_392_000 as Weight).saturating_mul(b as Weight)) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -436,16 +451,29 @@ // Storage: Refungible TokenProperties (r:1 w:1) fn delete_token_properties(b: u32, ) -> Weight { (0 as Weight) - // Standard Error: 1_619_000 - .saturating_add((294_574_000 as Weight).saturating_mul(b as Weight)) + // Standard Error: 1_699_000 + .saturating_add((293_270_000 as Weight).saturating_mul(b as Weight)) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Refungible TotalSupply (r:1 w:1) // Storage: Refungible Balance (r:1 w:1) fn repartition_item() -> Weight { - (8_325_000 as Weight) + (19_206_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } + // Storage: Refungible Balance (r:1 w:0) + // Storage: Refungible TotalSupply (r:1 w:0) + // Storage: Refungible TokenProperties (r:1 w:1) + fn set_parent_nft_unchecked() -> Weight { + (10_189_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(3 as Weight)) + .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + } + // Storage: Refungible Balance (r:2 w:0) + fn token_owner() -> Weight { + (8_205_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(2 as Weight)) + } } --- a/pallets/unique/src/eth/mod.rs +++ b/pallets/unique/src/eth/mod.rs @@ -17,26 +17,29 @@ //! Implementation of CollectionHelpers contract. use core::marker::PhantomData; -use evm_coder::{execution::*, generate_stubgen, solidity_interface, solidity, weight, types::*}; use ethereum as _; -use pallet_evm_coder_substrate::{SubstrateRecorder, WithRecorder}; -use pallet_evm::{OnMethodCall, PrecompileResult, account::CrossAccountId, PrecompileHandle}; -use up_data_structs::{ - CollectionName, CollectionDescription, CollectionTokenPrefix, CreateCollectionData, - CollectionMode, PropertyValue, -}; +use evm_coder::{execution::*, generate_stubgen, solidity_interface, solidity, weight, types::*}; use frame_support::traits::Get; use pallet_common::{ - CollectionById, + CollectionById, CollectionHandle, + dispatch::CollectionDispatch, erc::{ + CollectionHelpersEvents, static_property::{key, value as property_value}, - CollectionHelpersEvents, }, - dispatch::CollectionDispatch, + Pallet as PalletCommon, }; -use crate::{SelfWeightOf, Config, weights::WeightInfo}; +use pallet_evm_coder_substrate::{SubstrateRecorder, WithRecorder}; +use pallet_evm::{account::CrossAccountId, OnMethodCall, PrecompileHandle, PrecompileResult}; +use pallet_evm_coder_substrate::dispatch_to_evm; +use up_data_structs::{ + CollectionName, CollectionDescription, CollectionTokenPrefix, CreateCollectionData, + CollectionMode, PropertyKeyPermission, PropertyPermission, PropertyScope, PropertyValue, +}; + +use crate::{Config, SelfWeightOf, weights::WeightInfo}; -use sp_std::vec::Vec; +use sp_std::{vec, vec::Vec}; use alloc::format; /// See [`CollectionHelpersCall`] @@ -151,6 +154,54 @@ Ok(data) } +fn parent_nft_property_permissions() -> PropertyKeyPermission { + PropertyKeyPermission { + key: key::parent_nft(), + permission: PropertyPermission { + mutable: false, + collection_admin: false, + token_owner: true, + }, + } +} + +fn create_refungible_collection_internal< + T: Config + pallet_nonfungible::Config + pallet_refungible::Config, +>( + caller: caller, + name: string, + description: string, + token_prefix: string, + base_uri: string, + add_properties: bool, +) -> Result
{ + let (caller, name, description, token_prefix, base_uri_value) = + convert_data::(caller, name, description, token_prefix, base_uri)?; + let data = make_data::( + name, + CollectionMode::ReFungible, + description, + token_prefix, + base_uri_value, + add_properties, + )?; + + let collection_id = T::CollectionDispatch::create(caller.clone(), data) + .map_err(pallet_evm_coder_substrate::dispatch_to_evm::)?; + + let handle = >::try_get(collection_id).map_err(dispatch_to_evm::)?; + >::set_scoped_token_property_permissions( + &handle, + &caller, + PropertyScope::Eth, + vec![parent_nft_property_permissions()], + ) + .map_err(dispatch_to_evm::)?; + + let address = pallet_common::eth::collection_id_to_address(collection_id); + Ok(address) +} + /// @title Contract, which allows users to operate with collections #[solidity_interface(name = "CollectionHelpers", events(CollectionHelpersEvents))] impl EvmCollectionHelpers @@ -216,27 +267,20 @@ #[weight(>::create_collection())] fn create_refungible_collection( - &self, + &mut self, caller: caller, name: string, description: string, token_prefix: string, ) -> Result
{ - let (caller, name, description, token_prefix, _base_uri) = - convert_data::(caller, name, description, token_prefix, "".into())?; - let data = make_data::( + create_refungible_collection_internal::( + caller, name, - CollectionMode::ReFungible, description, token_prefix, Default::default(), false, - )?; - let collection_id = T::CollectionDispatch::create(caller, data) - .map_err(pallet_evm_coder_substrate::dispatch_to_evm::)?; - - let address = pallet_common::eth::collection_id_to_address(collection_id); - Ok(address) + ) } #[weight(>::create_collection())] @@ -249,21 +293,14 @@ token_prefix: string, base_uri: string, ) -> Result
{ - let (caller, name, description, token_prefix, base_uri_value) = - convert_data::(caller, name, description, token_prefix, base_uri)?; - let data = make_data::( + create_refungible_collection_internal::( + caller, name, - CollectionMode::NFT, description, token_prefix, - base_uri_value, + base_uri, true, - )?; - let collection_id = T::CollectionDispatch::create(caller, data) - .map_err(pallet_evm_coder_substrate::dispatch_to_evm::)?; - - let address = pallet_common::eth::collection_id_to_address(collection_id); - Ok(address) + ) } /// Check if a collection exists --- a/pallets/unique/src/eth/stubs/CollectionHelpers.sol +++ b/pallets/unique/src/eth/stubs/CollectionHelpers.sol @@ -72,12 +72,12 @@ string memory name, string memory description, string memory tokenPrefix - ) public view returns (address) { + ) public returns (address) { require(false, stub_error); name; description; tokenPrefix; - dummy; + dummy = 0; return 0x0000000000000000000000000000000000000000; } @@ -96,7 +96,7 @@ dummy = 0; return 0x0000000000000000000000000000000000000000; } - + // Check if a collection exists // @param collection_address Address of the collection in question // @return bool Does the collection exist? --- a/primitives/data-structs/src/lib.rs +++ b/primitives/data-structs/src/lib.rs @@ -1050,6 +1050,7 @@ pub enum PropertyScope { None, Rmrk, + Eth, } impl PropertyScope { @@ -1058,6 +1059,7 @@ let scope_str: &[u8] = match self { Self::None => return Ok(key), Self::Rmrk => b"rmrk", + Self::Eth => b"eth", }; [scope_str, b":", key.as_slice()] --- a/runtime/common/src/weights.rs +++ b/runtime/common/src/weights.rs @@ -99,6 +99,10 @@ fn burn_recursively_breadth_raw(amount: u32) -> Weight { max_weight_of!(burn_recursively_breadth_raw(amount)) } + + fn token_owner() -> Weight { + max_weight_of!(token_owner()) + } } impl RefungibleExtensionsWeightInfo for CommonWeights --- a/tests/src/eth/api/CollectionHelpers.sol +++ b/tests/src/eth/api/CollectionHelpers.sol @@ -48,7 +48,7 @@ string memory name, string memory description, string memory tokenPrefix - ) external view returns (address); + ) external returns (address); // Selector: createERC721MetadataCompatibleRFTCollection(string,string,string,string) a5596388 function createERC721MetadataCompatibleRFTCollection( @@ -57,7 +57,7 @@ string memory tokenPrefix, string memory baseUri ) external returns (address); - + // Check if a collection exists // @param collection_address Address of the collection in question // @return bool Does the collection exist? --- a/tests/src/eth/api/UniqueFungible.sol +++ b/tests/src/eth/api/UniqueFungible.sol @@ -28,7 +28,44 @@ function burnFrom(address from, uint256 amount) external returns (bool); } -// Selector: 7d9262e6 +// Selector: 942e8b22 +interface ERC20 is Dummy, ERC165, ERC20Events { + // Selector: name() 06fdde03 + function name() external view returns (string memory); + + // Selector: symbol() 95d89b41 + function symbol() external view returns (string memory); + + // Selector: totalSupply() 18160ddd + function totalSupply() external view returns (uint256); + + // Selector: decimals() 313ce567 + function decimals() external view returns (uint8); + + // Selector: balanceOf(address) 70a08231 + function balanceOf(address owner) external view returns (uint256); + + // Selector: transfer(address,uint256) a9059cbb + function transfer(address to, uint256 amount) external returns (bool); + + // Selector: transferFrom(address,address,uint256) 23b872dd + function transferFrom( + address from, + address to, + uint256 amount + ) external returns (bool); + + // Selector: approve(address,uint256) 095ea7b3 + function approve(address spender, uint256 amount) external returns (bool); + + // Selector: allowance(address,address) dd62ed3e + function allowance(address owner, address spender) + external + view + returns (uint256); +} + +// Selector: aa7d570d interface Collection is Dummy, ERC165 { // Set collection property. // @@ -174,43 +211,16 @@ // // Selector: setCollectionMintMode(bool) 00018e84 function setCollectionMintMode(bool mode) external; -} -// Selector: 942e8b22 -interface ERC20 is Dummy, ERC165, ERC20Events { - // Selector: name() 06fdde03 - function name() external view returns (string memory); - - // Selector: symbol() 95d89b41 - function symbol() external view returns (string memory); - - // Selector: totalSupply() 18160ddd - function totalSupply() external view returns (uint256); - - // Selector: decimals() 313ce567 - function decimals() external view returns (uint8); - - // Selector: balanceOf(address) 70a08231 - function balanceOf(address owner) external view returns (uint256); - - // Selector: transfer(address,uint256) a9059cbb - function transfer(address to, uint256 amount) external returns (bool); - - // Selector: transferFrom(address,address,uint256) 23b872dd - function transferFrom( - address from, - address to, - uint256 amount - ) external returns (bool); + // Check that account is the owner or admin of the collection + // + // @return "true" if account is the owner or admin + // + // Selector: verifyOwnerOrAdmin() 04a46053 + function verifyOwnerOrAdmin() external returns (bool); - // Selector: approve(address,uint256) 095ea7b3 - function approve(address spender, uint256 amount) external returns (bool); - - // Selector: allowance(address,address) dd62ed3e - function allowance(address owner, address spender) - external - view - returns (uint256); + // Selector: uniqueCollectionType() d34b55b8 + function uniqueCollectionType() external returns (string memory); } interface UniqueFungible is --- a/tests/src/eth/api/UniqueNFT.sol +++ b/tests/src/eth/api/UniqueNFT.sol @@ -276,7 +276,7 @@ function totalSupply() external view returns (uint256); } -// Selector: 7d9262e6 +// Selector: aa7d570d interface Collection is Dummy, ERC165 { // Set collection property. // @@ -422,6 +422,16 @@ // // Selector: setCollectionMintMode(bool) 00018e84 function setCollectionMintMode(bool mode) external; + + // Check that account is the owner or admin of the collection + // + // @return "true" if account is the owner or admin + // + // Selector: verifyOwnerOrAdmin() 04a46053 + function verifyOwnerOrAdmin() external returns (bool); + + // Selector: uniqueCollectionType() d34b55b8 + function uniqueCollectionType() external returns (string memory); } // Selector: d74d154f --- a/tests/src/eth/api/UniqueRefungible.sol +++ b/tests/src/eth/api/UniqueRefungible.sol @@ -274,7 +274,70 @@ function totalSupply() external view returns (uint256); } -// Selector: 7d9262e6 +// Selector: 7c3bef89 +interface ERC721UniqueExtensions is Dummy, ERC165 { + // @notice Transfer ownership of an RFT + // @dev Throws unless `msg.sender` is the current owner. Throws if `to` + // is the zero address. Throws if `tokenId` is not a valid RFT. + // Throws if RFT pieces have multiple owners. + // @param to The new owner + // @param tokenId The RFT to transfer + // @param _value Not used for an RFT + // + // Selector: transfer(address,uint256) a9059cbb + function transfer(address to, uint256 tokenId) external; + + // @notice Burns a specific ERC721 token. + // @dev Throws unless `msg.sender` is the current owner or an authorized + // operator for this RFT. Throws if `from` is not the current owner. Throws + // if `to` is the zero address. Throws if `tokenId` is not a valid RFT. + // Throws if RFT pieces have multiple owners. + // @param from The current owner of the RFT + // @param tokenId The RFT to transfer + // @param _value Not used for an RFT + // + // Selector: burnFrom(address,uint256) 79cc6790 + function burnFrom(address from, uint256 tokenId) external; + + // @notice Returns next free RFT ID. + // + // Selector: nextTokenId() 75794a3c + function nextTokenId() external view returns (uint256); + + // @notice Function to mint multiple tokens. + // @dev `tokenIds` should be an array of consecutive numbers and first number + // should be obtained with `nextTokenId` method + // @param to The new owner + // @param tokenIds IDs of the minted RFTs + // + // Selector: mintBulk(address,uint256[]) 44a9945e + function mintBulk(address to, uint256[] memory tokenIds) + external + returns (bool); + + // @notice Function to mint multiple tokens with the given tokenUris. + // @dev `tokenIds` is array of pairs of token ID and token URI. Token IDs should be consecutive + // numbers and first number should be obtained with `nextTokenId` method + // @param to The new owner + // @param tokens array of pairs of token ID and token URI for minted tokens + // + // Selector: mintBulkWithTokenURI(address,(uint256,string)[]) 36543006 + function mintBulkWithTokenURI(address to, Tuple0[] memory tokens) + external + returns (bool); + + // Returns EVM address for refungible token + // + // @param token ID of the token + // + // Selector: tokenContractAddress(uint256) ab76fac6 + function tokenContractAddress(uint256 token) + external + view + returns (address); +} + +// Selector: aa7d570d interface Collection is Dummy, ERC165 { // Set collection property. // @@ -420,59 +483,20 @@ // // Selector: setCollectionMintMode(bool) 00018e84 function setCollectionMintMode(bool mode) external; -} -// Selector: d74d154f -interface ERC721UniqueExtensions is Dummy, ERC165 { - // @notice Transfer ownership of an RFT - // @dev Throws unless `msg.sender` is the current owner. Throws if `to` - // is the zero address. Throws if `tokenId` is not a valid RFT. - // Throws if RFT pieces have multiple owners. - // @param to The new owner - // @param tokenId The RFT to transfer - // @param _value Not used for an RFT + // Check that account is the owner or admin of the collection // - // Selector: transfer(address,uint256) a9059cbb - function transfer(address to, uint256 tokenId) external; - - // @notice Burns a specific ERC721 token. - // @dev Throws unless `msg.sender` is the current owner or an authorized - // operator for this RFT. Throws if `from` is not the current owner. Throws - // if `to` is the zero address. Throws if `tokenId` is not a valid RFT. - // Throws if RFT pieces have multiple owners. - // @param from The current owner of the RFT - // @param tokenId The RFT to transfer - // @param _value Not used for an RFT + // @return "true" if account is the owner or admin // - // Selector: burnFrom(address,uint256) 79cc6790 - function burnFrom(address from, uint256 tokenId) external; + // Selector: verifyOwnerOrAdmin() 04a46053 + function verifyOwnerOrAdmin() external returns (bool); - // @notice Returns next free RFT ID. + // Returns collection type // - // Selector: nextTokenId() 75794a3c - function nextTokenId() external view returns (uint256); - - // @notice Function to mint multiple tokens. - // @dev `tokenIds` should be an array of consecutive numbers and first number - // should be obtained with `nextTokenId` method - // @param to The new owner - // @param tokenIds IDs of the minted RFTs + // @return `Fungible` or `NFT` or `ReFungible` // - // Selector: mintBulk(address,uint256[]) 44a9945e - function mintBulk(address to, uint256[] memory tokenIds) - external - returns (bool); - - // @notice Function to mint multiple tokens with the given tokenUris. - // @dev `tokenIds` is array of pairs of token ID and token URI. Token IDs should be consecutive - // numbers and first number should be obtained with `nextTokenId` method - // @param to The new owner - // @param tokens array of pairs of token ID and token URI for minted tokens - // - // Selector: mintBulkWithTokenURI(address,(uint256,string)[]) 36543006 - function mintBulkWithTokenURI(address to, Tuple0[] memory tokens) - external - returns (bool); + // Selector: uniqueCollectionType() d34b55b8 + function uniqueCollectionType() external returns (string memory); } interface UniqueRefungible is --- a/tests/src/eth/api/UniqueRefungibleToken.sol +++ b/tests/src/eth/api/UniqueRefungibleToken.sol @@ -22,6 +22,23 @@ ); } +// Selector: 042f1106 +interface ERC1633UniqueExtensions is Dummy, ERC165 { + // Selector: setParentNFT(address,uint256) 042f1106 + function setParentNFT(address collection, uint256 nftId) + external + returns (bool); +} + +// Selector: 5755c3f2 +interface ERC1633 is Dummy, ERC165 { + // Selector: parentToken() 80a54001 + function parentToken() external view returns (address); + + // Selector: parentTokenId() d7f083f3 + function parentTokenId() external view returns (uint256); +} + // Selector: 942e8b22 interface ERC20 is Dummy, ERC165, ERC20Events { // @return the name of the token. @@ -115,5 +132,7 @@ Dummy, ERC165, ERC20, - ERC20UniqueExtensions + ERC20UniqueExtensions, + ERC1633, + ERC1633UniqueExtensions {} --- a/tests/src/eth/reFungibleAbi.json +++ b/tests/src/eth/reFungibleAbi.json @@ -482,6 +482,15 @@ }, { "inputs": [ + { "internalType": "uint256", "name": "token", "type": "uint256" } + ], + "name": "tokenContractAddress", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ { "internalType": "address", "name": "owner", "type": "address" }, { "internalType": "uint256", "name": "index", "type": "uint256" } ], @@ -526,5 +535,19 @@ "outputs": [], "stateMutability": "nonpayable", "type": "function" + }, + { + "inputs": [], + "name": "uniqueCollectionType", + "outputs": [{ "internalType": "string", "name": "", "type": "string" }], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "verifyOwnerOrAdmin", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "nonpayable", + "type": "function" } ] --- a/tests/src/eth/reFungibleToken.test.ts +++ b/tests/src/eth/reFungibleToken.test.ts @@ -15,7 +15,7 @@ // along with Unique Network. If not, see . import {approve, createCollection, createRefungibleToken, transfer, transferFrom, UNIQUE} from '../util/helpers'; -import {collectionIdToAddress, createEthAccount, createEthAccountWithBalance, evmCollection, evmCollectionHelpers, GAS_ARGS, getCollectionAddressFromResult, itWeb3, normalizeEvents, recordEthFee, recordEvents, subToEth, tokenIdToAddress, transferBalanceToEth} from './util/helpers'; +import {collectionIdFromAddress, collectionIdToAddress, createEthAccount, createEthAccountWithBalance, createNonfungibleCollection, createRefungibleCollection, evmCollection, evmCollectionHelpers, GAS_ARGS, getCollectionAddressFromResult, itWeb3, normalizeEvents, recordEthFee, recordEvents, subToEth, tokenIdToAddress, transferBalanceToEth, uniqueNFT, uniqueRefungible, uniqueRefungibleToken} from './util/helpers'; import reFungibleTokenAbi from './reFungibleTokenAbi.json'; import chai from 'chai'; @@ -630,3 +630,31 @@ ]); }); }); + +describe('ERC 1633 implementation', () => { + itWeb3('Parent NFT token address and id', async ({api, web3, privateKeyWrapper}) => { + const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper); + + const {collectionIdAddress: nftCollectionAddress} = await createNonfungibleCollection(api, web3, owner); + const nftContract = uniqueNFT(web3, nftCollectionAddress, owner); + const nftTokenId = await nftContract.methods.nextTokenId().call(); + await nftContract.methods.mint(owner, nftTokenId).send(); + const nftCollectionId = collectionIdFromAddress(nftCollectionAddress); + + const {collectionIdAddress, collectionId} = await createRefungibleCollection(api, web3, owner); + const refungibleContract = uniqueRefungible(web3, collectionIdAddress, owner); + const refungibleTokenId = await refungibleContract.methods.nextTokenId().call(); + await refungibleContract.methods.mint(owner, refungibleTokenId).send(); + + const rftTokenAddress = tokenIdToAddress(collectionId, refungibleTokenId); + const refungibleTokenContract = uniqueRefungibleToken(web3, rftTokenAddress, owner); + await refungibleTokenContract.methods.setParentNFT(nftCollectionAddress, nftTokenId).send(); + + const tokenAddress = await refungibleTokenContract.methods.parentToken().call(); + const tokenId = await refungibleTokenContract.methods.parentTokenId().call(); + const nftTokenAddress = tokenIdToAddress(nftCollectionId, nftTokenId); + expect(tokenAddress).to.be.equal(nftTokenAddress); + expect(tokenId).to.be.equal(nftTokenId); + }); +}); + --- a/tests/src/eth/reFungibleTokenAbi.json +++ b/tests/src/eth/reFungibleTokenAbi.json @@ -103,6 +103,20 @@ "type": "function" }, { + "inputs": [], + "name": "parentToken", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "parentTokenId", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { "inputs": [ { "internalType": "uint256", "name": "amount", "type": "uint256" } ], @@ -113,6 +127,16 @@ }, { "inputs": [ + { "internalType": "address", "name": "collection", "type": "address" }, + { "internalType": "uint256", "name": "nftId", "type": "uint256" } + ], + "name": "setParentNFT", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ { "internalType": "bytes4", "name": "interfaceID", "type": "bytes4" } ], "name": "supportsInterface", -- gitstuff