--- a/pallets/common/src/erc.rs +++ b/pallets/common/src/erc.rs @@ -431,6 +431,27 @@ }; Ok(mode.into()) } + + /// Changes collection owner + /// + /// @dev Owner can be changed only by current owner + /// @param newOwner new owner + fn change_owner(&mut self, caller: caller, new_owner: address) -> Result { + let caller = T::CrossAccountId::from_eth(caller); + let new_owner = T::CrossAccountId::from_eth(new_owner); + self.change_owner_internal(caller, new_owner) + } + +impl CollectionHandle +where + T::AccountId: From<[u8; 32]>, +{ + + fn change_owner_internal(&mut self, caller: T::CrossAccountId, new_owner: T::CrossAccountId) -> Result { + self.check_is_owner(&caller).map_err(dispatch_to_evm::)?; + self.collection.owner = new_owner.as_sub().clone(); + save(self) + } } fn check_is_owner_or_admin( @@ -440,12 +461,13 @@ let caller = T::CrossAccountId::from_eth(caller); collection .check_is_owner_or_admin(&caller) - .map_err(pallet_evm_coder_substrate::dispatch_to_evm::)?; + .map_err(dispatch_to_evm::)?; Ok(caller) } fn save(collection: &CollectionHandle) -> Result { // TODO possibly delete for the lack of transaction + collection.consume_store_writes(1)?; collection .check_is_internal() .map_err(dispatch_to_evm::)?; --- a/pallets/fungible/src/stubs/UniqueFungible.sol +++ b/pallets/fungible/src/stubs/UniqueFungible.sol @@ -31,7 +31,103 @@ ); } -// Selector: 6cf113cd +// Selector: 79cc6790 +contract ERC20UniqueExtensions is Dummy, ERC165 { + // Selector: burnFrom(address,uint256) 79cc6790 + function burnFrom(address from, uint256 amount) public returns (bool) { + require(false, stub_error); + from; + amount; + dummy = 0; + return false; + } +} + +// 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: ca08c92c contract Collection is Dummy, ERC165 { // Set collection property. // @@ -276,105 +372,21 @@ // // Selector: uniqueCollectionType() d34b55b8 function uniqueCollectionType() public returns (string memory) { - require(false, stub_error); - dummy = 0; - return ""; - } -} - -// Selector: 79cc6790 -contract ERC20UniqueExtensions is Dummy, ERC165 { - // Selector: burnFrom(address,uint256) 79cc6790 - function burnFrom(address from, uint256 amount) public returns (bool) { require(false, stub_error); - from; - amount; dummy = 0; - return false; - } -} - -// 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) { + // Changes collection owner + // + // @dev Owner can be changed only by current owner + // @param newOwner new owner + // + // Selector: changeOwner(address) a6f9dae1 + function changeOwner(address newOwner) public { require(false, stub_error); - spender; - amount; + newOwner; 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; } } --- a/pallets/nonfungible/src/stubs/UniqueNFT.sol +++ b/pallets/nonfungible/src/stubs/UniqueNFT.sol @@ -373,7 +373,49 @@ } } -// Selector: 6cf113cd +// Selector: 780e9d63 +contract ERC721Enumerable is Dummy, ERC165 { + // @notice Enumerate valid NFTs + // @param index A counter less than `totalSupply()` + // @return The token identifier for the `index`th NFT, + // (sort order not specified) + // + // Selector: tokenByIndex(uint256) 4f6ccce7 + function tokenByIndex(uint256 index) public view returns (uint256) { + require(false, stub_error); + index; + dummy; + return 0; + } + + // @dev Not implemented + // + // Selector: tokenOfOwnerByIndex(address,uint256) 2f745c59 + function tokenOfOwnerByIndex(address owner, uint256 index) + public + view + returns (uint256) + { + require(false, stub_error); + owner; + index; + dummy; + return 0; + } + + // @notice Count NFTs tracked by this contract + // @return A count of valid NFTs tracked by this contract, where each one of + // them has an assigned and queryable owner not equal to the zero address + // + // Selector: totalSupply() 18160ddd + function totalSupply() public view returns (uint256) { + require(false, stub_error); + dummy; + return 0; + } +} + +// Selector: ca08c92c contract Collection is Dummy, ERC165 { // Set collection property. // @@ -622,47 +664,17 @@ dummy = 0; return ""; } -} -// Selector: 780e9d63 -contract ERC721Enumerable is Dummy, ERC165 { - // @notice Enumerate valid NFTs - // @param index A counter less than `totalSupply()` - // @return The token identifier for the `index`th NFT, - // (sort order not specified) + // Changes collection owner // - // Selector: tokenByIndex(uint256) 4f6ccce7 - function tokenByIndex(uint256 index) public view returns (uint256) { - require(false, stub_error); - index; - dummy; - return 0; - } - - // @dev Not implemented + // @dev Owner can be changed only by current owner + // @param newOwner new owner // - // Selector: tokenOfOwnerByIndex(address,uint256) 2f745c59 - function tokenOfOwnerByIndex(address owner, uint256 index) - public - view - returns (uint256) - { + // Selector: changeOwner(address) a6f9dae1 + function changeOwner(address newOwner) public { require(false, stub_error); - owner; - index; - dummy; - return 0; - } - - // @notice Count NFTs tracked by this contract - // @return A count of valid NFTs tracked by this contract, where each one of - // them has an assigned and queryable owner not equal to the zero address - // - // Selector: totalSupply() 18160ddd - function totalSupply() public view returns (uint256) { - require(false, stub_error); - dummy; - return 0; + newOwner; + dummy = 0; } } --- a/pallets/refungible/src/stubs/UniqueRefungible.sol +++ b/pallets/refungible/src/stubs/UniqueRefungible.sol @@ -371,7 +371,142 @@ } } -// Selector: 6cf113cd +// Selector: 780e9d63 +contract ERC721Enumerable is Dummy, ERC165 { + // @notice Enumerate valid RFTs + // @param index A counter less than `totalSupply()` + // @return The token identifier for the `index`th NFT, + // (sort order not specified) + // + // Selector: tokenByIndex(uint256) 4f6ccce7 + function tokenByIndex(uint256 index) public view returns (uint256) { + require(false, stub_error); + index; + dummy; + return 0; + } + + // Not implemented + // + // Selector: tokenOfOwnerByIndex(address,uint256) 2f745c59 + function tokenOfOwnerByIndex(address owner, uint256 index) + public + view + returns (uint256) + { + require(false, stub_error); + owner; + index; + dummy; + return 0; + } + + // @notice Count RFTs tracked by this contract + // @return A count of valid RFTs tracked by this contract, where each one of + // them has an assigned and queryable owner not equal to the zero address + // + // Selector: totalSupply() 18160ddd + function totalSupply() public view returns (uint256) { + require(false, stub_error); + dummy; + return 0; + } +} + +// 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: ca08c92c contract Collection is Dummy, ERC165 { // Set collection property. // @@ -620,140 +755,17 @@ dummy = 0; return ""; } -} -// Selector: 780e9d63 -contract ERC721Enumerable is Dummy, ERC165 { - // @notice Enumerate valid RFTs - // @param index A counter less than `totalSupply()` - // @return The token identifier for the `index`th NFT, - // (sort order not specified) + // Changes collection owner // - // Selector: tokenByIndex(uint256) 4f6ccce7 - function tokenByIndex(uint256 index) public view returns (uint256) { - require(false, stub_error); - index; - dummy; - return 0; - } - - // Not implemented + // @dev Owner can be changed only by current owner + // @param newOwner new owner // - // Selector: tokenOfOwnerByIndex(address,uint256) 2f745c59 - function tokenOfOwnerByIndex(address owner, uint256 index) - public - view - returns (uint256) - { + // Selector: changeOwner(address) a6f9dae1 + function changeOwner(address newOwner) public { require(false, stub_error); - owner; - index; - dummy; - return 0; - } - - // @notice Count RFTs tracked by this contract - // @return A count of valid RFTs tracked by this contract, where each one of - // them has an assigned and queryable owner not equal to the zero address - // - // Selector: totalSupply() 18160ddd - function totalSupply() public view returns (uint256) { - require(false, stub_error); - dummy; - return 0; - } -} - -// 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; + newOwner; 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; } } --- a/tests/src/eth/api/UniqueFungible.sol +++ b/tests/src/eth/api/UniqueFungible.sol @@ -22,7 +22,50 @@ ); } -// Selector: 6cf113cd +// Selector: 79cc6790 +interface ERC20UniqueExtensions is Dummy, ERC165 { + // Selector: burnFrom(address,uint256) 79cc6790 + function burnFrom(address from, uint256 amount) external returns (bool); +} + +// 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: ca08c92c interface Collection is Dummy, ERC165 { // Set collection property. // @@ -183,49 +226,14 @@ // // Selector: uniqueCollectionType() d34b55b8 function uniqueCollectionType() external returns (string memory); -} -// Selector: 79cc6790 -interface ERC20UniqueExtensions is Dummy, ERC165 { - // Selector: burnFrom(address,uint256) 79cc6790 - function burnFrom(address from, uint256 amount) external returns (bool); -} - -// 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); + // Changes collection owner + // + // @dev Owner can be changed only by current owner + // @param newOwner new owner + // + // Selector: changeOwner(address) a6f9dae1 + function changeOwner(address newOwner) external; } interface UniqueFungible is --- a/tests/src/eth/api/UniqueNFT.sol +++ b/tests/src/eth/api/UniqueNFT.sol @@ -250,7 +250,33 @@ function finishMinting() external returns (bool); } -// Selector: 6cf113cd +// Selector: 780e9d63 +interface ERC721Enumerable is Dummy, ERC165 { + // @notice Enumerate valid NFTs + // @param index A counter less than `totalSupply()` + // @return The token identifier for the `index`th NFT, + // (sort order not specified) + // + // Selector: tokenByIndex(uint256) 4f6ccce7 + function tokenByIndex(uint256 index) external view returns (uint256); + + // @dev Not implemented + // + // Selector: tokenOfOwnerByIndex(address,uint256) 2f745c59 + function tokenOfOwnerByIndex(address owner, uint256 index) + external + view + returns (uint256); + + // @notice Count NFTs tracked by this contract + // @return A count of valid NFTs tracked by this contract, where each one of + // them has an assigned and queryable owner not equal to the zero address + // + // Selector: totalSupply() 18160ddd + function totalSupply() external view returns (uint256); +} + +// Selector: ca08c92c interface Collection is Dummy, ERC165 { // Set collection property. // @@ -411,32 +437,14 @@ // // Selector: uniqueCollectionType() d34b55b8 function uniqueCollectionType() external returns (string memory); -} -// Selector: 780e9d63 -interface ERC721Enumerable is Dummy, ERC165 { - // @notice Enumerate valid NFTs - // @param index A counter less than `totalSupply()` - // @return The token identifier for the `index`th NFT, - // (sort order not specified) + // Changes collection owner // - // Selector: tokenByIndex(uint256) 4f6ccce7 - function tokenByIndex(uint256 index) external view returns (uint256); - - // @dev Not implemented + // @dev Owner can be changed only by current owner + // @param newOwner new owner // - // Selector: tokenOfOwnerByIndex(address,uint256) 2f745c59 - function tokenOfOwnerByIndex(address owner, uint256 index) - external - view - returns (uint256); - - // @notice Count NFTs tracked by this contract - // @return A count of valid NFTs tracked by this contract, where each one of - // them has an assigned and queryable owner not equal to the zero address - // - // Selector: totalSupply() 18160ddd - function totalSupply() external view returns (uint256); + // Selector: changeOwner(address) a6f9dae1 + function changeOwner(address newOwner) external; } // Selector: d74d154f --- a/tests/src/eth/api/UniqueRefungible.sol +++ b/tests/src/eth/api/UniqueRefungible.sol @@ -248,7 +248,96 @@ function finishMinting() external returns (bool); } -// Selector: 6cf113cd +// Selector: 780e9d63 +interface ERC721Enumerable is Dummy, ERC165 { + // @notice Enumerate valid RFTs + // @param index A counter less than `totalSupply()` + // @return The token identifier for the `index`th NFT, + // (sort order not specified) + // + // Selector: tokenByIndex(uint256) 4f6ccce7 + function tokenByIndex(uint256 index) external view returns (uint256); + + // Not implemented + // + // Selector: tokenOfOwnerByIndex(address,uint256) 2f745c59 + function tokenOfOwnerByIndex(address owner, uint256 index) + external + view + returns (uint256); + + // @notice Count RFTs tracked by this contract + // @return A count of valid RFTs tracked by this contract, where each one of + // them has an assigned and queryable owner not equal to the zero address + // + // Selector: totalSupply() 18160ddd + function totalSupply() external view returns (uint256); +} + +// 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: ca08c92c interface Collection is Dummy, ERC165 { // Set collection property. // @@ -409,95 +498,14 @@ // // Selector: uniqueCollectionType() d34b55b8 function uniqueCollectionType() external returns (string memory); -} - -// Selector: 780e9d63 -interface ERC721Enumerable is Dummy, ERC165 { - // @notice Enumerate valid RFTs - // @param index A counter less than `totalSupply()` - // @return The token identifier for the `index`th NFT, - // (sort order not specified) - // - // Selector: tokenByIndex(uint256) 4f6ccce7 - function tokenByIndex(uint256 index) external view returns (uint256); - - // Not implemented - // - // Selector: tokenOfOwnerByIndex(address,uint256) 2f745c59 - function tokenOfOwnerByIndex(address owner, uint256 index) - external - view - returns (uint256); - - // @notice Count RFTs tracked by this contract - // @return A count of valid RFTs tracked by this contract, where each one of - // them has an assigned and queryable owner not equal to the zero address - // - // Selector: totalSupply() 18160ddd - function totalSupply() external view returns (uint256); -} -// 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 + // Changes collection owner // - // 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 + // @dev Owner can be changed only by current owner + // @param newOwner new owner // - // Selector: tokenContractAddress(uint256) ab76fac6 - function tokenContractAddress(uint256 token) - external - view - returns (address); + // Selector: changeOwner(address) a6f9dae1 + function changeOwner(address newOwner) external; } interface UniqueRefungible is --- a/tests/src/eth/collectionAdmin.test.ts +++ b/tests/src/eth/collectionAdmin.test.ts @@ -15,6 +15,7 @@ import {expect} from 'chai'; import privateKey from '../substrate/privateKey'; +import { UNIQUE } from '../util/helpers'; import { createEthAccount, createEthAccountWithBalance, @@ -22,6 +23,7 @@ evmCollectionHelpers, getCollectionAddressFromResult, itWeb3, + recordEthFee, } from './util/helpers'; describe('Add collection admins', () => { @@ -309,4 +311,51 @@ expect(adminList[0].asSubstrate.toString().toLocaleLowerCase()) .to.be.eq(adminSub.address.toLocaleLowerCase()); }); +}); + +describe('Change owner tests', () => { + itWeb3('Change owner', async ({api, web3, privateKeyWrapper}) => { + const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper); + const newOwner = await createEthAccountWithBalance(api, web3, privateKeyWrapper); + const collectionHelper = evmCollectionHelpers(web3, owner); + const result = await collectionHelper.methods + .createNonfungibleCollection('A', 'B', 'C') + .send(); + const {collectionIdAddress} = await getCollectionAddressFromResult(api, result); + const collectionEvm = evmCollection(web3, owner, collectionIdAddress); + + await collectionEvm.methods.changeOwner(newOwner).send(); + + expect(await collectionEvm.methods.verifyOwnerOrAdmin(owner).call()).to.be.false; + expect(await collectionEvm.methods.verifyOwnerOrAdmin(newOwner).call()).to.be.true; + }); + + itWeb3('change owner call fee', async ({web3, api, privateKeyWrapper}) => { + const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper); + const newOwner = await createEthAccountWithBalance(api, web3, privateKeyWrapper); + const collectionHelper = evmCollectionHelpers(web3, owner); + const result = await collectionHelper.methods + .createNonfungibleCollection('A', 'B', 'C') + .send(); + const {collectionIdAddress} = await getCollectionAddressFromResult(api, result); + const collectionEvm = evmCollection(web3, owner, collectionIdAddress); + + const cost = await recordEthFee(api, owner, () => collectionEvm.methods.changeOwner(newOwner).send()); + expect(cost < BigInt(0.2 * Number(UNIQUE))); + expect(cost > 0); + }); + + itWeb3('(!negative tests!) call changeOwner by not owner', async ({api, web3, privateKeyWrapper}) => { + const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper); + const newOwner = await createEthAccountWithBalance(api, web3, privateKeyWrapper); + const collectionHelper = evmCollectionHelpers(web3, owner); + const result = await collectionHelper.methods + .createNonfungibleCollection('A', 'B', 'C') + .send(); + const {collectionIdAddress} = await getCollectionAddressFromResult(api, result); + const collectionEvm = evmCollection(web3, owner, collectionIdAddress); + + await expect(collectionEvm.methods.changeOwner(newOwner).send({from: newOwner})).to.be.rejected; + expect(await collectionEvm.methods.verifyOwnerOrAdmin(newOwner).call()).to.be.false; + }); }); \ No newline at end of file --- a/tests/src/eth/fungibleAbi.json +++ b/tests/src/eth/fungibleAbi.json @@ -116,6 +116,15 @@ "type": "function" }, { + "inputs": [ + { "internalType": "address", "name": "newOwner", "type": "address" } + ], + "name": "changeOwner", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { "inputs": [{ "internalType": "string", "name": "key", "type": "string" }], "name": "collectionProperty", "outputs": [{ "internalType": "bytes", "name": "", "type": "bytes" }], --- a/tests/src/eth/nonFungibleAbi.json +++ b/tests/src/eth/nonFungibleAbi.json @@ -146,6 +146,15 @@ "type": "function" }, { + "inputs": [ + { "internalType": "address", "name": "newOwner", "type": "address" } + ], + "name": "changeOwner", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { "inputs": [{ "internalType": "string", "name": "key", "type": "string" }], "name": "collectionProperty", "outputs": [{ "internalType": "bytes", "name": "", "type": "bytes" }], --- a/tests/src/eth/reFungibleAbi.json +++ b/tests/src/eth/reFungibleAbi.json @@ -146,6 +146,15 @@ "type": "function" }, { + "inputs": [ + { "internalType": "address", "name": "newOwner", "type": "address" } + ], + "name": "changeOwner", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { "inputs": [{ "internalType": "string", "name": "key", "type": "string" }], "name": "collectionProperty", "outputs": [{ "internalType": "bytes", "name": "", "type": "bytes" }],