--- a/pallets/nft/src/eth/erc.rs +++ b/pallets/nft/src/eth/erc.rs @@ -8,6 +8,7 @@ }; use frame_support::storage::{StorageMap, StorageDoubleMap}; use pallet_evm::AddressMapping; +use pallet_evm_coder_substrate::dispatch_to_evm; use super::account::CrossAccountId; use sp_std::{vec, vec::Vec}; @@ -299,6 +300,90 @@ .ok_or("item id overflow")? .into()) } + + fn set_variable_metadata( + &mut self, + caller: caller, + token_id: uint256, + data: bytes, + ) -> Result { + let caller = T::CrossAccountId::from_eth(caller); + let token_id = token_id.try_into().map_err(|_| "token id overflow")?; + + >::set_variable_meta_data_internal(&caller, self, token_id, data) + .map_err(dispatch_to_evm::)?; + Ok(()) + } + + fn get_variable_metadata(&self, token_id: uint256) -> Result { + let token_id = token_id.try_into().map_err(|_| "token id overflow")?; + + >::get_variable_metadata(self, token_id).map_err(dispatch_to_evm::) + } + + fn mint_bulk(&mut self, caller: caller, to: address, token_ids: Vec) -> Result { + let caller = T::CrossAccountId::from_eth(caller); + let to = T::CrossAccountId::from_eth(to); + let mut expected_index = ::get(self.id) + .checked_add(1) + .ok_or("item id overflow")?; + + let total_tokens = token_ids.len(); + for id in token_ids.into_iter() { + let id: u32 = id.try_into().map_err(|_| "token id overflow")?; + if id != expected_index { + return Err("item id should be next".into()); + } + expected_index = expected_index.checked_add(1).ok_or("item id overflow")?; + } + + let data = (0..total_tokens) + .map(|_| { + CreateItemData::NFT(CreateNftData { + const_data: vec![].try_into().unwrap(), + variable_data: vec![].try_into().unwrap(), + }) + }) + .collect(); + + >::create_multiple_items_internal(&caller, self, &to, data) + .map_err(dispatch_to_evm::)?; + Ok(true) + } + + #[solidity(rename_selector = "mintBulkWithTokenURI")] + fn mint_bulk_with_token_uri( + &mut self, + caller: caller, + to: address, + tokens: Vec<(uint256, string)>, + ) -> Result { + let caller = T::CrossAccountId::from_eth(caller); + let to = T::CrossAccountId::from_eth(to); + let mut expected_index = ::get(self.id) + .checked_add(1) + .ok_or("item id overflow")?; + + let mut data = Vec::with_capacity(tokens.len()); + for (id, token_uri) in tokens { + let id: u32 = id.try_into().map_err(|_| "token id overflow")?; + if id != expected_index { + panic!("item id should be next ({}) but got {}", expected_index, id); + } + expected_index = expected_index.checked_add(1).ok_or("item id overflow")?; + + data.push(CreateItemData::NFT(CreateNftData { + const_data: Vec::::from(token_uri) + .try_into() + .map_err(|_| "token uri is too long")?, + variable_data: vec![].try_into().unwrap(), + })); + } + + >::create_multiple_items_internal(&caller, self, &to, data) + .map_err(dispatch_to_evm::)?; + Ok(true) + } } #[solidity_interface( --- a/pallets/nft/src/eth/stubs/UniqueNFT.sol +++ b/pallets/nft/src/eth/stubs/UniqueNFT.sol @@ -3,6 +3,12 @@ pragma solidity >=0.8.0 <0.9.0; +// Anonymous struct +struct Tuple0 { + uint256 field_0; + string field_1; +} + // Common stubs holder contract Dummy { uint8 dummy; @@ -239,6 +245,50 @@ dummy; return 0; } + + // Selector: setVariableMetadata(uint256,bytes) d4eac26d + function setVariableMetadata(uint256 tokenId, bytes memory data) public { + require(false, stub_error); + tokenId; + data; + dummy = 0; + } + + // Selector: getVariableMetadata(uint256) e6c5ce6f + function getVariableMetadata(uint256 tokenId) + public + view + returns (bytes memory) + { + require(false, stub_error); + tokenId; + dummy; + return hex""; + } + + // 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; + } + + // 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; + } } contract UniqueNFT is --- a/pallets/nft/src/lib.rs +++ b/pallets/nft/src/lib.rs @@ -1501,6 +1501,14 @@ Ok(()) } + pub fn get_variable_metadata(collection: &CollectionHandle, item_id: TokenId) -> Result, DispatchError> { + Ok(match collection.mode { + CollectionMode::NFT => >::get(collection.id, item_id).ok_or(Error::::TokenNotFound)?.variable_data, + CollectionMode::ReFungible => >::get(collection.id, item_id).ok_or(Error::::TokenNotFound)?.variable_data, + _ => fail!(Error::::UnexpectedCollectionType), + }) + } + pub fn create_multiple_items_internal( sender: &T::CrossAccountId, collection: &CollectionHandle, --- a/tests/src/eth/api/UniqueNFT.sol +++ b/tests/src/eth/api/UniqueNFT.sol @@ -3,6 +3,12 @@ pragma solidity >=0.8.0 <0.9.0; +// Anonymous struct +struct Tuple0 { + uint256 field_0; + string field_1; +} + // Common stubs holder interface Dummy { @@ -119,6 +125,25 @@ function transfer(address to, uint256 tokenId) external; function nextTokenId() external view returns (uint256); + + // Selector: setVariableMetadata(uint256,bytes) d4eac26d + function setVariableMetadata(uint256 tokenId, bytes memory data) external; + + // Selector: getVariableMetadata(uint256) e6c5ce6f + function getVariableMetadata(uint256 tokenId) + external + view + returns (bytes memory); + + // Selector: mintBulk(address,uint256[]) 44a9945e + function mintBulk(address to, uint256[] memory tokenIds) + external + returns (bool); + + // Selector: mintBulkWithTokenURI(address,(uint256,string)[]) 36543006 + function mintBulkWithTokenURI(address to, Tuple0[] memory tokens) + external + returns (bool); } interface UniqueNFT is