From eadc594eaa1a087524ee53f53c1e5861b070bc32 Mon Sep 17 00:00:00 2001 From: Yaroslav Bolyukin Date: Thu, 25 Aug 2022 15:04:10 +0000 Subject: [PATCH] Merge remote-tracking branch 'origin/feature/add_collection_sponsor_substrate' into develop --- --- a/Cargo.lock +++ b/Cargo.lock @@ -5510,7 +5510,7 @@ [[package]] name = "pallet-common" -version = "0.1.7" +version = "0.1.8" dependencies = [ "ethereum", "evm-coder", @@ -5748,7 +5748,7 @@ [[package]] name = "pallet-fungible" -version = "0.1.3" +version = "0.1.4" dependencies = [ "ethereum", "evm-coder", @@ -5990,7 +5990,7 @@ [[package]] name = "pallet-nonfungible" -version = "0.1.4" +version = "0.1.5" dependencies = [ "ethereum", "evm-coder", @@ -6112,7 +6112,7 @@ [[package]] name = "pallet-refungible" -version = "0.2.3" +version = "0.2.4" dependencies = [ "derivative", "ethereum", --- a/crates/evm-coder/CHANGELOG.md +++ b/crates/evm-coder/CHANGELOG.md @@ -1,6 +1,8 @@ # Change Log All notable changes to this project will be documented in this file. + + ## [v0.1.2] 2022-08-19 ### Added --- a/crates/evm-coder/src/solidity.rs +++ b/crates/evm-coder/src/solidity.rs @@ -479,7 +479,7 @@ } } -#[impl_for_tuples(0, 24)] +#[impl_for_tuples(0, 48)] impl SolidityFunctions for Tuple { for_tuples!( where #( Tuple: SolidityFunctions ),* ); --- a/pallets/common/CHANGELOG.md +++ b/pallets/common/CHANGELOG.md @@ -2,6 +2,16 @@ All notable changes to this project will be documented in this file. +## [0.1.8] - 2022-08-24 + +## Added + - Eth methods for collection + + set_collection_sponsor_substrate + + has_collection_pending_sponsor + + remove_collection_sponsor + + get_collection_sponsor +- Add convert function from `uint256` to `CrossAccountId`. + ## [0.1.7] - 2022-08-19 ### Added --- a/pallets/common/Cargo.toml +++ b/pallets/common/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pallet-common" -version = "0.1.7" +version = "0.1.8" license = "GPLv3" edition = "2021" --- a/pallets/common/src/erc.rs +++ b/pallets/common/src/erc.rs @@ -26,13 +26,13 @@ use sp_std::vec::Vec; use up_data_structs::{ AccessMode, CollectionMode, CollectionPermissions, OwnerRestrictedSet, Property, - SponsoringRateLimit, + SponsoringRateLimit, SponsorshipState, }; use alloc::format; use crate::{ Pallet, CollectionHandle, Config, CollectionProperties, - eth::convert_substrate_address_to_cross_account_id, + eth::{convert_cross_account_to_uint256, convert_uint256_to_cross_account}, }; /// Events for ethereum collection helper. @@ -63,7 +63,7 @@ #[solidity_interface(name = Collection)] impl CollectionHandle where - T::AccountId: From<[u8; 32]>, + T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>, { /// Set collection property. /// @@ -128,6 +128,32 @@ save(self) } + /// Set the substrate sponsor of the collection. + /// + /// @dev In order for sponsorship to work, it must be confirmed on behalf of the sponsor. + /// + /// @param sponsor Substrate address of the sponsor from whose account funds will be debited for operations with the contract. + fn set_collection_sponsor_substrate( + &mut self, + caller: caller, + sponsor: uint256, + ) -> Result { + check_is_owner_or_admin(caller, self)?; + + let sponsor = convert_uint256_to_cross_account::(sponsor); + self.set_sponsor(sponsor.as_sub().clone()) + .map_err(dispatch_to_evm::)?; + save(self) + } + + // /// Whether there is a pending sponsor. + fn has_collection_pending_sponsor(&self) -> Result { + Ok(matches!( + self.collection.sponsorship, + SponsorshipState::Unconfirmed(_) + )) + } + /// Collection sponsorship confirmation. /// /// @dev After setting the sponsor for the collection, it must be confirmed with this function. @@ -142,6 +168,32 @@ save(self) } + /// Remove collection sponsor. + fn remove_collection_sponsor(&mut self, caller: caller) -> Result { + check_is_owner_or_admin(caller, self)?; + self.remove_sponsor().map_err(dispatch_to_evm::)?; + save(self) + } + + /// Get current sponsor. + /// + /// @return Tuble with sponsor address and his substrate mirror. If there is no confirmed sponsor error "Contract has no sponsor" throw. + fn get_collection_sponsor(&self) -> Result<(address, uint256)> { + let sponsor = match self.collection.sponsorship.sponsor() { + Some(sponsor) => sponsor, + None => return Ok(Default::default()), + }; + let sponsor = T::CrossAccountId::from_sub(sponsor.clone()); + let result: (address, uint256) = if sponsor.is_canonical_substrate() { + let sponsor = convert_cross_account_to_uint256::(&sponsor); + (Default::default(), sponsor) + } else { + let sponsor = *sponsor.as_eth(); + (sponsor, Default::default()) + }; + Ok(result) + } + /// Set limits for the collection. /// @dev Throws error if limit not found. /// @param limit Name of the limit. Valid names: @@ -235,7 +287,7 @@ new_admin: uint256, ) -> Result { let caller = T::CrossAccountId::from_eth(caller); - let new_admin = convert_substrate_address_to_cross_account_id::(new_admin); + let new_admin = convert_uint256_to_cross_account::(new_admin); >::toggle_admin(self, &caller, &new_admin, true).map_err(dispatch_to_evm::)?; Ok(()) } @@ -248,7 +300,7 @@ admin: uint256, ) -> Result { let caller = T::CrossAccountId::from_eth(caller); - let admin = convert_substrate_address_to_cross_account_id::(admin); + let admin = convert_uint256_to_cross_account::(admin); >::toggle_admin(self, &caller, &admin, false).map_err(dispatch_to_evm::)?; Ok(()) } @@ -422,7 +474,7 @@ /// @param user account to verify /// @return "true" if account is the owner or admin fn is_owner_or_admin_substrate(&self, user: uint256) -> Result { - let user = convert_substrate_address_to_cross_account_id::(user); + let user = convert_uint256_to_cross_account::(user); Ok(self.is_owner_or_admin(&user)) } @@ -455,7 +507,7 @@ /// @param newOwner new owner substrate account fn set_owner_substrate(&mut self, caller: caller, new_owner: uint256) -> Result { let caller = T::CrossAccountId::from_eth(caller); - let new_owner = convert_substrate_address_to_cross_account_id::(new_owner); + let new_owner = convert_uint256_to_cross_account::(new_owner); self.set_owner_internal(caller, new_owner) .map_err(dispatch_to_evm::) } --- a/pallets/common/src/eth.rs +++ b/pallets/common/src/eth.rs @@ -59,15 +59,13 @@ uint256::from_big_endian(slice) } -/// Converts Substrate address to CrossAccountId -pub fn convert_substrate_address_to_cross_account_id( - address: uint256, -) -> T::CrossAccountId +/// Convert `uint256` to `CrossAccountId`. +pub fn convert_uint256_to_cross_account(from: uint256) -> T::CrossAccountId where T::AccountId: From<[u8; 32]>, { - let mut address_arr: [u8; 32] = Default::default(); - address.to_big_endian(&mut address_arr); - let account_id = T::AccountId::from(address_arr); + let mut new_admin_arr = [0_u8; 32]; + from.to_big_endian(&mut new_admin_arr); + let account_id = T::AccountId::from(new_admin_arr); T::CrossAccountId::from_sub(account_id) } --- a/pallets/common/src/lib.rs +++ b/pallets/common/src/lib.rs @@ -231,6 +231,12 @@ Ok(true) } + /// Remove collection sponsor. + pub fn remove_sponsor(&mut self) -> DispatchResult { + self.collection.sponsorship = SponsorshipState::Disabled; + Ok(()) + } + /// Checks that the collection was created with, and must be operated upon through **Unique API**. /// Now check only the `external_collection` flag and if it's **true**, then return [`Error::CollectionIsExternal`] error. pub fn check_is_internal(&self) -> DispatchResult { @@ -732,12 +738,7 @@ /// Get the effective limits for the collection. pub fn effective_collection_limits(collection: CollectionId) -> Option { - let collection = >::get(collection); - if collection.is_none() { - return None; - } - - let collection = collection.unwrap(); + let collection = >::get(collection)?; let limits = collection.limits; let effective_limits = CollectionLimits { account_token_ownership_limit: Some(limits.account_token_ownership_limit()), --- a/pallets/fungible/CHANGELOG.md +++ b/pallets/fungible/CHANGELOG.md @@ -2,6 +2,11 @@ All notable changes to this project will be documented in this file. +## [v0.1.4] - 2022-08-24 + +### Change + - Add bound `AsRef<[u8; 32]>` to `T::CrossAccountId`. + ## [v0.1.3] 2022-08-16 --- a/pallets/fungible/Cargo.toml +++ b/pallets/fungible/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pallet-fungible" -version = "0.1.3" +version = "0.1.4" license = "GPLv3" edition = "2021" --- a/pallets/fungible/src/erc.rs +++ b/pallets/fungible/src/erc.rs @@ -154,14 +154,14 @@ Collection(common_mut, CollectionHandle), ) )] -impl FungibleHandle where T::AccountId: From<[u8; 32]> {} +impl FungibleHandle where T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]> {} generate_stubgen!(gen_impl, UniqueFungibleCall<()>, true); generate_stubgen!(gen_iface, UniqueFungibleCall<()>, false); impl CommonEvmHandler for FungibleHandle where - T::AccountId: From<[u8; 32]>, + T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>, { const CODE: &'static [u8] = include_bytes!("./stubs/UniqueFungible.raw"); --- a/pallets/fungible/src/stubs/UniqueFungible.sol +++ b/pallets/fungible/src/stubs/UniqueFungible.sol @@ -22,7 +22,7 @@ } /// @title A contract that allows you to work with collections. -/// @dev the ERC-165 identifier for this interface is 0xffe4da23 +/// @dev the ERC-165 identifier for this interface is 0xe54be640 contract Collection is Dummy, ERC165 { /// Set collection property. /// @@ -82,6 +82,27 @@ dummy = 0; } + /// Set the substrate sponsor of the collection. + /// + /// @dev In order for sponsorship to work, it must be confirmed on behalf of the sponsor. + /// + /// @param sponsor Substrate address of the sponsor from whose account funds will be debited for operations with the contract. + /// @dev EVM selector for this function is: 0xc74d6751, + /// or in textual repr: setCollectionSponsorSubstrate(uint256) + function setCollectionSponsorSubstrate(uint256 sponsor) public { + require(false, stub_error); + sponsor; + dummy = 0; + } + + /// @dev EVM selector for this function is: 0x058ac185, + /// or in textual repr: hasCollectionPendingSponsor() + function hasCollectionPendingSponsor() public view returns (bool) { + require(false, stub_error); + dummy; + return false; + } + /// Collection sponsorship confirmation. /// /// @dev After setting the sponsor for the collection, it must be confirmed with this function. @@ -92,6 +113,25 @@ dummy = 0; } + /// Remove collection sponsor. + /// @dev EVM selector for this function is: 0x6e0326a3, + /// or in textual repr: removeCollectionSponsor() + function removeCollectionSponsor() public { + require(false, stub_error); + dummy = 0; + } + + /// Get current sponsor. + /// + /// @return Tuble with sponsor address and his substrate mirror. If there is no confirmed sponsor error "Contract has no sponsor" throw. + /// @dev EVM selector for this function is: 0xb66bbc14, + /// or in textual repr: getCollectionSponsor() + function getCollectionSponsor() public view returns (Tuple6 memory) { + require(false, stub_error); + dummy; + return Tuple6(0x0000000000000000000000000000000000000000, 0); + } + /// Set limits for the collection. /// @dev Throws error if limit not found. /// @param limit Name of the limit. Valid names: @@ -310,6 +350,12 @@ } } +/// @dev anonymous struct +struct Tuple6 { + address field_0; + uint256 field_1; +} + /// @dev the ERC-165 identifier for this interface is 0x79cc6790 contract ERC20UniqueExtensions is Dummy, ERC165 { /// @dev EVM selector for this function is: 0x79cc6790, --- a/pallets/nonfungible/CHANGELOG.md +++ b/pallets/nonfungible/CHANGELOG.md @@ -2,6 +2,11 @@ All notable changes to this project will be documented in this file. +## [v0.1.5] - 2022-08-24 + +### Change + - Add bound `AsRef<[u8; 32]>` to `T::CrossAccountId`. + ## [v0.1.4] 2022-08-16 --- a/pallets/nonfungible/Cargo.toml +++ b/pallets/nonfungible/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pallet-nonfungible" -version = "0.1.4" +version = "0.1.5" license = "GPLv3" edition = "2021" --- a/pallets/nonfungible/src/erc.rs +++ b/pallets/nonfungible/src/erc.rs @@ -740,7 +740,7 @@ TokenProperties, ) )] -impl NonfungibleHandle where T::AccountId: From<[u8; 32]> {} +impl NonfungibleHandle where T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]> {} // Not a tests, but code generators generate_stubgen!(gen_impl, UniqueNFTCall<()>, true); @@ -748,7 +748,7 @@ impl CommonEvmHandler for NonfungibleHandle where - T::AccountId: From<[u8; 32]>, + T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>, { const CODE: &'static [u8] = include_bytes!("./stubs/UniqueNFT.raw"); --- a/pallets/nonfungible/src/stubs/UniqueNFT.sol +++ b/pallets/nonfungible/src/stubs/UniqueNFT.sol @@ -99,7 +99,7 @@ } /// @title A contract that allows you to work with collections. -/// @dev the ERC-165 identifier for this interface is 0xffe4da23 +/// @dev the ERC-165 identifier for this interface is 0xe54be640 contract Collection is Dummy, ERC165 { /// Set collection property. /// @@ -159,6 +159,27 @@ dummy = 0; } + /// Set the substrate sponsor of the collection. + /// + /// @dev In order for sponsorship to work, it must be confirmed on behalf of the sponsor. + /// + /// @param sponsor Substrate address of the sponsor from whose account funds will be debited for operations with the contract. + /// @dev EVM selector for this function is: 0xc74d6751, + /// or in textual repr: setCollectionSponsorSubstrate(uint256) + function setCollectionSponsorSubstrate(uint256 sponsor) public { + require(false, stub_error); + sponsor; + dummy = 0; + } + + /// @dev EVM selector for this function is: 0x058ac185, + /// or in textual repr: hasCollectionPendingSponsor() + function hasCollectionPendingSponsor() public view returns (bool) { + require(false, stub_error); + dummy; + return false; + } + /// Collection sponsorship confirmation. /// /// @dev After setting the sponsor for the collection, it must be confirmed with this function. @@ -169,6 +190,25 @@ dummy = 0; } + /// Remove collection sponsor. + /// @dev EVM selector for this function is: 0x6e0326a3, + /// or in textual repr: removeCollectionSponsor() + function removeCollectionSponsor() public { + require(false, stub_error); + dummy = 0; + } + + /// Get current sponsor. + /// + /// @return Tuble with sponsor address and his substrate mirror. If there is no confirmed sponsor error "Contract has no sponsor" throw. + /// @dev EVM selector for this function is: 0xb66bbc14, + /// or in textual repr: getCollectionSponsor() + function getCollectionSponsor() public view returns (Tuple17 memory) { + require(false, stub_error); + dummy; + return Tuple17(0x0000000000000000000000000000000000000000, 0); + } + /// Set limits for the collection. /// @dev Throws error if limit not found. /// @param limit Name of the limit. Valid names: @@ -387,6 +427,12 @@ } } +/// @dev anonymous struct +struct Tuple17 { + address field_0; + uint256 field_1; +} + /// @title ERC721 Token that can be irreversibly burned (destroyed). /// @dev the ERC-165 identifier for this interface is 0x42966c68 contract ERC721Burnable is Dummy, ERC165 { --- a/pallets/refungible/CHANGELOG.md +++ b/pallets/refungible/CHANGELOG.md @@ -2,6 +2,11 @@ All notable changes to this project will be documented in this file. +## [v0.2.4] - 2022-08-24 + +### Change + - Add bound `AsRef<[u8; 32]>` to `T::CrossAccountId`. + ## [v0.2.3] 2022-08-16 --- a/pallets/refungible/Cargo.toml +++ b/pallets/refungible/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pallet-refungible" -version = "0.2.3" +version = "0.2.4" license = "GPLv3" edition = "2021" --- a/pallets/refungible/src/erc.rs +++ b/pallets/refungible/src/erc.rs @@ -789,7 +789,7 @@ TokenProperties, ) )] -impl RefungibleHandle where T::AccountId: From<[u8; 32]> {} +impl RefungibleHandle where T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]> {} // Not a tests, but code generators generate_stubgen!(gen_impl, UniqueRefungibleCall<()>, true); @@ -797,7 +797,7 @@ impl CommonEvmHandler for RefungibleHandle where - T::AccountId: From<[u8; 32]>, + T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>, { const CODE: &'static [u8] = include_bytes!("./stubs/UniqueRefungible.raw"); fn call( --- a/pallets/refungible/src/stubs/UniqueRefungible.sol +++ b/pallets/refungible/src/stubs/UniqueRefungible.sol @@ -99,7 +99,7 @@ } /// @title A contract that allows you to work with collections. -/// @dev the ERC-165 identifier for this interface is 0xffe4da23 +/// @dev the ERC-165 identifier for this interface is 0xe54be640 contract Collection is Dummy, ERC165 { /// Set collection property. /// @@ -159,6 +159,27 @@ dummy = 0; } + /// Set the substrate sponsor of the collection. + /// + /// @dev In order for sponsorship to work, it must be confirmed on behalf of the sponsor. + /// + /// @param sponsor Substrate address of the sponsor from whose account funds will be debited for operations with the contract. + /// @dev EVM selector for this function is: 0xc74d6751, + /// or in textual repr: setCollectionSponsorSubstrate(uint256) + function setCollectionSponsorSubstrate(uint256 sponsor) public { + require(false, stub_error); + sponsor; + dummy = 0; + } + + /// @dev EVM selector for this function is: 0x058ac185, + /// or in textual repr: hasCollectionPendingSponsor() + function hasCollectionPendingSponsor() public view returns (bool) { + require(false, stub_error); + dummy; + return false; + } + /// Collection sponsorship confirmation. /// /// @dev After setting the sponsor for the collection, it must be confirmed with this function. @@ -169,6 +190,25 @@ dummy = 0; } + /// Remove collection sponsor. + /// @dev EVM selector for this function is: 0x6e0326a3, + /// or in textual repr: removeCollectionSponsor() + function removeCollectionSponsor() public { + require(false, stub_error); + dummy = 0; + } + + /// Get current sponsor. + /// + /// @return Tuble with sponsor address and his substrate mirror. If there is no confirmed sponsor error "Contract has no sponsor" throw. + /// @dev EVM selector for this function is: 0xb66bbc14, + /// or in textual repr: getCollectionSponsor() + function getCollectionSponsor() public view returns (Tuple17 memory) { + require(false, stub_error); + dummy; + return Tuple17(0x0000000000000000000000000000000000000000, 0); + } + /// Set limits for the collection. /// @dev Throws error if limit not found. /// @param limit Name of the limit. Valid names: @@ -387,6 +427,12 @@ } } +/// @dev anonymous struct +struct Tuple17 { + address field_0; + uint256 field_1; +} + /// @title ERC721 Token that can be irreversibly burned (destroyed). /// @dev the ERC-165 identifier for this interface is 0x42966c68 contract ERC721Burnable is Dummy, ERC165 { --- a/runtime/common/dispatch.rs +++ b/runtime/common/dispatch.rs @@ -124,7 +124,7 @@ + pallet_fungible::Config + pallet_nonfungible::Config + pallet_refungible::Config, - T::AccountId: From<[u8; 32]>, + T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>, { fn is_reserved(target: &H160) -> bool { map_eth_to_id(target).is_some() --- a/tests/src/eth/api/UniqueFungible.sol +++ b/tests/src/eth/api/UniqueFungible.sol @@ -13,7 +13,7 @@ } /// @title A contract that allows you to work with collections. -/// @dev the ERC-165 identifier for this interface is 0xffe4da23 +/// @dev the ERC-165 identifier for this interface is 0xe54be640 interface Collection is Dummy, ERC165 { /// Set collection property. /// @@ -53,6 +53,19 @@ /// or in textual repr: setCollectionSponsor(address) function setCollectionSponsor(address sponsor) external; + /// Set the substrate sponsor of the collection. + /// + /// @dev In order for sponsorship to work, it must be confirmed on behalf of the sponsor. + /// + /// @param sponsor Substrate address of the sponsor from whose account funds will be debited for operations with the contract. + /// @dev EVM selector for this function is: 0xc74d6751, + /// or in textual repr: setCollectionSponsorSubstrate(uint256) + function setCollectionSponsorSubstrate(uint256 sponsor) external; + + /// @dev EVM selector for this function is: 0x058ac185, + /// or in textual repr: hasCollectionPendingSponsor() + function hasCollectionPendingSponsor() external view returns (bool); + /// Collection sponsorship confirmation. /// /// @dev After setting the sponsor for the collection, it must be confirmed with this function. @@ -60,6 +73,18 @@ /// or in textual repr: confirmCollectionSponsorship() function confirmCollectionSponsorship() external; + /// Remove collection sponsor. + /// @dev EVM selector for this function is: 0x6e0326a3, + /// or in textual repr: removeCollectionSponsor() + function removeCollectionSponsor() external; + + /// Get current sponsor. + /// + /// @return Tuble with sponsor address and his substrate mirror. If there is no confirmed sponsor error "Contract has no sponsor" throw. + /// @dev EVM selector for this function is: 0xb66bbc14, + /// or in textual repr: getCollectionSponsor() + function getCollectionSponsor() external view returns (Tuple6 memory); + /// Set limits for the collection. /// @dev Throws error if limit not found. /// @param limit Name of the limit. Valid names: @@ -200,6 +225,12 @@ function setOwnerSubstrate(uint256 newOwner) external; } +/// @dev anonymous struct +struct Tuple6 { + address field_0; + uint256 field_1; +} + /// @dev the ERC-165 identifier for this interface is 0x79cc6790 interface ERC20UniqueExtensions is Dummy, ERC165 { /// @dev EVM selector for this function is: 0x79cc6790, --- a/tests/src/eth/api/UniqueNFT.sol +++ b/tests/src/eth/api/UniqueNFT.sol @@ -65,7 +65,7 @@ } /// @title A contract that allows you to work with collections. -/// @dev the ERC-165 identifier for this interface is 0xffe4da23 +/// @dev the ERC-165 identifier for this interface is 0xe54be640 interface Collection is Dummy, ERC165 { /// Set collection property. /// @@ -105,6 +105,19 @@ /// or in textual repr: setCollectionSponsor(address) function setCollectionSponsor(address sponsor) external; + /// Set the substrate sponsor of the collection. + /// + /// @dev In order for sponsorship to work, it must be confirmed on behalf of the sponsor. + /// + /// @param sponsor Substrate address of the sponsor from whose account funds will be debited for operations with the contract. + /// @dev EVM selector for this function is: 0xc74d6751, + /// or in textual repr: setCollectionSponsorSubstrate(uint256) + function setCollectionSponsorSubstrate(uint256 sponsor) external; + + /// @dev EVM selector for this function is: 0x058ac185, + /// or in textual repr: hasCollectionPendingSponsor() + function hasCollectionPendingSponsor() external view returns (bool); + /// Collection sponsorship confirmation. /// /// @dev After setting the sponsor for the collection, it must be confirmed with this function. @@ -112,6 +125,18 @@ /// or in textual repr: confirmCollectionSponsorship() function confirmCollectionSponsorship() external; + /// Remove collection sponsor. + /// @dev EVM selector for this function is: 0x6e0326a3, + /// or in textual repr: removeCollectionSponsor() + function removeCollectionSponsor() external; + + /// Get current sponsor. + /// + /// @return Tuble with sponsor address and his substrate mirror. If there is no confirmed sponsor error "Contract has no sponsor" throw. + /// @dev EVM selector for this function is: 0xb66bbc14, + /// or in textual repr: getCollectionSponsor() + function getCollectionSponsor() external view returns (Tuple17 memory); + /// Set limits for the collection. /// @dev Throws error if limit not found. /// @param limit Name of the limit. Valid names: @@ -252,6 +277,12 @@ function setOwnerSubstrate(uint256 newOwner) external; } +/// @dev anonymous struct +struct Tuple17 { + address field_0; + uint256 field_1; +} + /// @title ERC721 Token that can be irreversibly burned (destroyed). /// @dev the ERC-165 identifier for this interface is 0x42966c68 interface ERC721Burnable is Dummy, ERC165 { --- a/tests/src/eth/api/UniqueRefungible.sol +++ b/tests/src/eth/api/UniqueRefungible.sol @@ -65,7 +65,7 @@ } /// @title A contract that allows you to work with collections. -/// @dev the ERC-165 identifier for this interface is 0xffe4da23 +/// @dev the ERC-165 identifier for this interface is 0xe54be640 interface Collection is Dummy, ERC165 { /// Set collection property. /// @@ -105,6 +105,19 @@ /// or in textual repr: setCollectionSponsor(address) function setCollectionSponsor(address sponsor) external; + /// Set the substrate sponsor of the collection. + /// + /// @dev In order for sponsorship to work, it must be confirmed on behalf of the sponsor. + /// + /// @param sponsor Substrate address of the sponsor from whose account funds will be debited for operations with the contract. + /// @dev EVM selector for this function is: 0xc74d6751, + /// or in textual repr: setCollectionSponsorSubstrate(uint256) + function setCollectionSponsorSubstrate(uint256 sponsor) external; + + /// @dev EVM selector for this function is: 0x058ac185, + /// or in textual repr: hasCollectionPendingSponsor() + function hasCollectionPendingSponsor() external view returns (bool); + /// Collection sponsorship confirmation. /// /// @dev After setting the sponsor for the collection, it must be confirmed with this function. @@ -112,6 +125,18 @@ /// or in textual repr: confirmCollectionSponsorship() function confirmCollectionSponsorship() external; + /// Remove collection sponsor. + /// @dev EVM selector for this function is: 0x6e0326a3, + /// or in textual repr: removeCollectionSponsor() + function removeCollectionSponsor() external; + + /// Get current sponsor. + /// + /// @return Tuble with sponsor address and his substrate mirror. If there is no confirmed sponsor error "Contract has no sponsor" throw. + /// @dev EVM selector for this function is: 0xb66bbc14, + /// or in textual repr: getCollectionSponsor() + function getCollectionSponsor() external view returns (Tuple17 memory); + /// Set limits for the collection. /// @dev Throws error if limit not found. /// @param limit Name of the limit. Valid names: @@ -252,6 +277,12 @@ function setOwnerSubstrate(uint256 newOwner) external; } +/// @dev anonymous struct +struct Tuple17 { + address field_0; + uint256 field_1; +} + /// @title ERC721 Token that can be irreversibly burned (destroyed). /// @dev the ERC-165 identifier for this interface is 0x42966c68 interface ERC721Burnable is Dummy, ERC165 { --- a/tests/src/eth/collectionSponsoring.test.ts +++ b/tests/src/eth/collectionSponsoring.test.ts @@ -1,8 +1,10 @@ -import {addToAllowListExpectSuccess, confirmSponsorshipExpectSuccess, createCollectionExpectSuccess, enablePublicMintingExpectSuccess, getDetailedCollectionInfo, setCollectionSponsorExpectSuccess} from '../util/helpers'; -import {itWeb3, createEthAccount, collectionIdToAddress, GAS_ARGS, normalizeEvents, createEthAccountWithBalance, evmCollectionHelpers, getCollectionAddressFromResult, evmCollection, ethBalanceViaSub} from './util/helpers'; +import {addToAllowListExpectSuccess, bigIntToSub, confirmSponsorshipExpectSuccess, createCollectionExpectSuccess, enablePublicMintingExpectSuccess, getDetailedCollectionInfo, setCollectionSponsorExpectSuccess} from '../util/helpers'; +import {itWeb3, createEthAccount, collectionIdToAddress, GAS_ARGS, normalizeEvents, createEthAccountWithBalance, evmCollectionHelpers, getCollectionAddressFromResult, evmCollection, ethBalanceViaSub, subToEth} from './util/helpers'; import nonFungibleAbi from './nonFungibleAbi.json'; import {expect} from 'chai'; import {evmToAddress} from '@polkadot/util-crypto'; +import {submitTransactionAsync} from '../substrate/substrate-api'; +import getBalance from '../substrate/get-balance'; describe('evm collection sponsoring', () => { itWeb3('sponsors mint transactions', async ({web3, privateKeyWrapper}) => { @@ -38,6 +40,47 @@ ]); }); + itWeb3('Set substrate sponsor', async ({api, web3, privateKeyWrapper}) => { + const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper); + const collectionHelpers = evmCollectionHelpers(web3, owner); + let result = await collectionHelpers.methods.createNonfungibleCollection('Sponsor collection', '1', '1').send(); + const {collectionIdAddress, collectionId} = await getCollectionAddressFromResult(api, result); + const sponsor = privateKeyWrapper('//Alice'); + const collectionEvm = evmCollection(web3, owner, collectionIdAddress); + + expect(await collectionEvm.methods.hasCollectionPendingSponsor().call({from: owner})).to.be.false; + result = await collectionEvm.methods.setCollectionSponsorSubstrate(sponsor.addressRaw).send({from: owner}); + expect(await collectionEvm.methods.hasCollectionPendingSponsor().call({from: owner})).to.be.true; + + const confirmTx = await api.tx.unique.confirmSponsorship(collectionId); + await submitTransactionAsync(sponsor, confirmTx); + expect(await collectionEvm.methods.hasCollectionPendingSponsor().call({from: owner})).to.be.false; + + const sponsorTuple = await collectionEvm.methods.getCollectionSponsor().call({from: owner}); + expect(bigIntToSub(api, BigInt(sponsorTuple[1]))).to.be.eq(sponsor.address); + }); + + itWeb3('Remove sponsor', async ({api, web3, privateKeyWrapper}) => { + const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper); + const collectionHelpers = evmCollectionHelpers(web3, owner); + let result = await collectionHelpers.methods.createNonfungibleCollection('Sponsor collection', '1', '1').send(); + const {collectionIdAddress} = await getCollectionAddressFromResult(api, result); + const sponsor = await createEthAccountWithBalance(api, web3, privateKeyWrapper); + const collectionEvm = evmCollection(web3, owner, collectionIdAddress); + + expect(await collectionEvm.methods.hasCollectionPendingSponsor().call({from: owner})).to.be.false; + result = await collectionEvm.methods.setCollectionSponsor(sponsor).send({from: owner}); + expect(await collectionEvm.methods.hasCollectionPendingSponsor().call({from: owner})).to.be.true; + + await collectionEvm.methods.confirmCollectionSponsorship().send({from: sponsor}); + expect(await collectionEvm.methods.hasCollectionPendingSponsor().call({from: owner})).to.be.false; + + await collectionEvm.methods.removeCollectionSponsor().send({from: owner}); + + const sponsorTuple = await collectionEvm.methods.getCollectionSponsor().call({from: owner}); + expect(sponsorTuple.field_0).to.be.eq('0x0000000000000000000000000000000000000000'); + }); + itWeb3('Sponsoring collection from evm address via access list', async ({api, web3, privateKeyWrapper}) => { const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper); const collectionHelpers = evmCollectionHelpers(web3, owner); @@ -107,6 +150,61 @@ } }); + itWeb3('Sponsoring collection from substrate address via access list', async ({api, web3, privateKeyWrapper}) => { + const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper); + const collectionHelpers = evmCollectionHelpers(web3, owner); + const result = await collectionHelpers.methods.createNonfungibleCollection('Sponsor collection', '1', '1').send(); + const {collectionIdAddress, collectionId} = await getCollectionAddressFromResult(api, result); + const sponsor = privateKeyWrapper('//Alice'); + const collectionEvm = evmCollection(web3, owner, collectionIdAddress); + + await collectionEvm.methods.setCollectionSponsorSubstrate(sponsor.addressRaw).send({from: owner}); + + const confirmTx = await api.tx.unique.confirmSponsorship(collectionId); + await submitTransactionAsync(sponsor, confirmTx); + + const user = createEthAccount(web3); + const nextTokenId = await collectionEvm.methods.nextTokenId().call(); + expect(nextTokenId).to.be.equal('1'); + + await collectionEvm.methods.setCollectionAccess(1 /*'AllowList'*/).send({from: owner}); + await collectionEvm.methods.addToCollectionAllowList(user).send({from: owner}); + await collectionEvm.methods.setCollectionMintMode(true).send({from: owner}); + + const ownerBalanceBefore = await ethBalanceViaSub(api, owner); + const sponsorBalanceBefore = (await getBalance(api, [sponsor.address]))[0]; + + { + const nextTokenId = await collectionEvm.methods.nextTokenId().call(); + expect(nextTokenId).to.be.equal('1'); + const result = await collectionEvm.methods.mintWithTokenURI( + user, + nextTokenId, + 'Test URI', + ).send({from: user}); + const events = normalizeEvents(result.events); + + expect(events).to.be.deep.equal([ + { + address: collectionIdAddress, + event: 'Transfer', + args: { + from: '0x0000000000000000000000000000000000000000', + to: user, + tokenId: nextTokenId, + }, + }, + ]); + + const ownerBalanceAfter = await ethBalanceViaSub(api, owner); + const sponsorBalanceAfter = (await getBalance(api, [sponsor.address]))[0]; + + expect(await collectionEvm.methods.tokenURI(nextTokenId).call()).to.be.equal('Test URI'); + expect(ownerBalanceBefore).to.be.eq(ownerBalanceAfter); + expect(sponsorBalanceBefore > sponsorBalanceAfter).to.be.true; + } + }); + itWeb3('Check that transaction via EVM spend money from sponsor address', async ({api, web3, privateKeyWrapper}) => { const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper); const collectionHelpers = evmCollectionHelpers(web3, owner); --- a/tests/src/eth/fungibleAbi.json +++ b/tests/src/eth/fungibleAbi.json @@ -151,6 +151,30 @@ "type": "function" }, { + "inputs": [], + "name": "getCollectionSponsor", + "outputs": [ + { + "components": [ + { "internalType": "address", "name": "field_0", "type": "address" }, + { "internalType": "uint256", "name": "field_1", "type": "uint256" } + ], + "internalType": "struct Tuple6", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "hasCollectionPendingSponsor", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "view", + "type": "function" + }, + { "inputs": [ { "internalType": "address", "name": "user", "type": "address" } ], @@ -194,6 +218,13 @@ "type": "function" }, { + "inputs": [], + "name": "removeCollectionSponsor", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { "inputs": [ { "internalType": "address", "name": "user", "type": "address" } ], @@ -278,6 +309,15 @@ }, { "inputs": [ + { "internalType": "uint256", "name": "sponsor", "type": "uint256" } + ], + "name": "setCollectionSponsorSubstrate", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ { "internalType": "address", "name": "newOwner", "type": "address" } ], "name": "setOwner", --- a/tests/src/eth/nonFungibleAbi.json +++ b/tests/src/eth/nonFungibleAbi.json @@ -200,6 +200,30 @@ "type": "function" }, { + "inputs": [], + "name": "getCollectionSponsor", + "outputs": [ + { + "components": [ + { "internalType": "address", "name": "field_0", "type": "address" }, + { "internalType": "uint256", "name": "field_1", "type": "uint256" } + ], + "internalType": "struct Tuple17", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "hasCollectionPendingSponsor", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "view", + "type": "function" + }, + { "inputs": [ { "internalType": "address", "name": "owner", "type": "address" }, { "internalType": "address", "name": "operator", "type": "address" } @@ -335,6 +359,13 @@ "type": "function" }, { + "inputs": [], + "name": "removeCollectionSponsor", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { "inputs": [ { "internalType": "address", "name": "user", "type": "address" } ], @@ -452,6 +483,15 @@ }, { "inputs": [ + { "internalType": "uint256", "name": "sponsor", "type": "uint256" } + ], + "name": "setCollectionSponsorSubstrate", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ { "internalType": "address", "name": "newOwner", "type": "address" } ], "name": "setOwner", --- a/tests/src/eth/reFungibleAbi.json +++ b/tests/src/eth/reFungibleAbi.json @@ -200,6 +200,30 @@ "type": "function" }, { + "inputs": [], + "name": "getCollectionSponsor", + "outputs": [ + { + "components": [ + { "internalType": "address", "name": "field_0", "type": "address" }, + { "internalType": "uint256", "name": "field_1", "type": "uint256" } + ], + "internalType": "struct Tuple17", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "hasCollectionPendingSponsor", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "view", + "type": "function" + }, + { "inputs": [ { "internalType": "address", "name": "owner", "type": "address" }, { "internalType": "address", "name": "operator", "type": "address" } @@ -335,6 +359,13 @@ "type": "function" }, { + "inputs": [], + "name": "removeCollectionSponsor", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { "inputs": [ { "internalType": "address", "name": "user", "type": "address" } ], @@ -452,6 +483,15 @@ }, { "inputs": [ + { "internalType": "uint256", "name": "sponsor", "type": "uint256" } + ], + "name": "setCollectionSponsorSubstrate", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ { "internalType": "address", "name": "newOwner", "type": "address" } ], "name": "setOwner", --- a/tests/src/util/helpers.ts +++ b/tests/src/util/helpers.ts @@ -99,6 +99,10 @@ } } +export function bigIntToSub(api: ApiPromise, number: bigint) { + return api.registry.createType('AccountId', '0x' + number.toString(16).padStart(64, '0')).toJSON(); +} + export function normalizeAccountId(input: string | AccountId | CrossAccountId | IKeyringPair): CrossAccountId { if (typeof input === 'string') { if (input.length >= 47) { -- gitstuff