--- /dev/null +++ b/pallets/nft/src/eth/erc_impl.rs @@ -0,0 +1,283 @@ +use core::char::{decode_utf16, REPLACEMENT_CHARACTER}; +use evm_coder::{ + abi::{AbiWriter, StringError}, + types::*, +}; +use core::convert::TryInto; +use alloc::format; +use crate::{Allowances, Module, Balance, CollectionHandle, CollectionMode, Config, NftItemList}; +use frame_support::storage::StorageDoubleMap; +use pallet_evm::AddressMapping; +use super::erc::*; +use super::account::CrossAccountId; + +type Result = core::result::Result; + +impl InlineNameSymbol for CollectionHandle { + type Error = StringError; + + fn name(&self) -> Result { + Ok(decode_utf16(self.name.iter().copied()) + .map(|r| r.unwrap_or(REPLACEMENT_CHARACTER)) + .collect::()) + } + + fn symbol(&self) -> Result { + Ok(string::from_utf8_lossy(&self.token_prefix).into()) + } +} + +impl InlineTotalSupply for CollectionHandle { + type Error = StringError; + + fn total_supply(&self) -> Result { + // TODO: we do not track total amount of all tokens + Ok(0.into()) + } +} + +impl ERC721Metadata for CollectionHandle { + type Error = StringError; + + fn token_uri(&self, token_id: uint256) -> Result { + // TODO: We should standartize url prefix, maybe via offchain schema? + Ok(format!("unique.network/{}/{}", self.id, token_id)) + } + + fn call_inline_name_symbol(&mut self, c: Msg) -> Result { + ::call(self, c) + } +} + +impl ERC721Enumerable for CollectionHandle { + type Error = StringError; + + fn token_by_index(&self, index: uint256) -> Result { + Ok(index) + } + + fn token_of_owner_by_index(&self, owner: address, index: uint256) -> Result { + // TODO: Not implemetable + Err("not implemented".into()) + } + + fn call_inline_total_supply(&mut self, c: Msg) -> Result { + ::call(self, c) + } +} + +impl ERC721 for CollectionHandle { + type Error = StringError; + + fn balance_of(&self, owner: address) -> Result { + let owner = T::EvmAddressMapping::into_account_id(owner); + let balance = >::get(self.id, owner); + Ok(balance.into()) + } + fn owner_of(&self, token_id: uint256) -> Result
{ + let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?; + let token = >::get(self.id, token_id).ok_or("unknown token")?; + Ok(token.owner.as_eth().clone()) + } + fn safe_transfer_from_with_data( + &mut self, + _from: address, + _to: address, + _token_id: uint256, + _data: bytes, + _value: value, + ) -> Result { + // TODO: Not implemetable + Err("not implemented".into()) + } + fn safe_transfer_from( + &mut self, + _from: address, + _to: address, + _token_id: uint256, + _value: value, + ) -> Result { + // TODO: Not implemetable + Err("not implemented".into()) + } + + fn transfer_from( + &mut self, + caller: caller, + from: address, + to: address, + token_id: uint256, + _value: value, + ) -> Result { + let caller = T::CrossAccountId::from_eth(caller); + let from = T::CrossAccountId::from_eth(from); + let to = T::CrossAccountId::from_eth(to); + let token_id = token_id.try_into().map_err(|_| "token_id overflow")?; + + >::transfer_from_internal(&caller, &from, &to, &self, token_id, 1) + .map_err(|_| "transferFrom error")?; + Ok(()) + } + + fn approve( + &mut self, + caller: caller, + approved: address, + token_id: uint256, + _value: value, + ) -> Result { + let caller = T::CrossAccountId::from_eth(caller); + let approved = T::CrossAccountId::from_eth(approved); + let token_id = token_id.try_into().map_err(|_| "token_id overflow")?; + + >::approve_internal(&caller, &approved, &self, token_id, 1) + .map_err(|_| "approve internal")?; + Ok(()) + } + + fn set_approval_for_all( + &mut self, + _caller: caller, + _operator: address, + _approved: bool, + ) -> Result { + // TODO: Not implemetable + Err("not implemented".into()) + } + + fn get_approved(&self, _token_id: uint256) -> Result
{ + // TODO: Not implemetable + Err("not implemented".into()) + } + + fn is_approved_for_all(&self, _owner: address, _operator: address) -> Result
{ + // TODO: Not implemetable + Err("not implemented".into()) + } + + fn call_erc165(&mut self, c: Msg) -> Result { + let ERC165Call::SupportsInterface { interface_id } = c.call; + Ok(evm_coder::abi_encode!(bool( + &ERC721Call::supports_interface(interface_id) + ))) + } +} + +impl ERC721UniqueExtensions for CollectionHandle { + type Error = StringError; + fn transfer( + &mut self, + caller: caller, + to: address, + token_id: uint256, + value: value, + ) -> Result { + let caller = T::CrossAccountId::from_eth(caller); + let to = T::CrossAccountId::from_eth(to); + let token_id = token_id.try_into().map_err(|_| "amount overflow")?; + + >::transfer_internal(&caller, &to, &self, token_id, 1) + .map_err(|_| "transfer error")?; + Ok(()) + } +} + +impl UniqueNFT for CollectionHandle { + type Error = StringError; + fn call_erc165(&mut self, c: Msg) -> Result { + let ERC165Call::SupportsInterface { interface_id } = c.call; + Ok(evm_coder::abi_encode!(bool( + &UniqueNFTCall::supports_interface(interface_id) + ))) + } + fn call_erc721(&mut self, c: Msg) -> Result { + ::call(self, c) + } + fn call_erc721_metadata(&mut self, c: Msg) -> Result { + ::call(self, c) + } + fn call_erc721_enumerable(&mut self, c: Msg) -> Result { + ::call(self, c) + } + fn call_erc721_unique_extensions( + &mut self, + c: Msg, + ) -> Result { + ::call(self, c) + } +} + +impl ERC20 for CollectionHandle { + type Error = StringError; + fn decimals(&self) -> Result { + Ok(if let CollectionMode::Fungible(decimals) = &self.mode { + *decimals + } else { + unreachable!() + }) + } + fn balance_of(&self, owner: address) -> Result { + let owner = T::EvmAddressMapping::into_account_id(owner); + let balance = >::get(self.id, owner); + Ok(balance.into()) + } + fn transfer(&mut self, caller: caller, to: address, amount: uint256) -> Result { + let caller = T::CrossAccountId::from_eth(caller); + let to = T::CrossAccountId::from_eth(to); + let amount = amount.try_into().map_err(|_| "amount overflow")?; + + >::transfer_internal(&caller, &to, &self, 1, amount) + .map_err(|_| "transfer error")?; + Ok(true) + } + fn transfer_from( + &mut self, + caller: caller, + from: address, + to: address, + amount: uint256, + ) -> Result { + let caller = T::CrossAccountId::from_eth(caller); + let from = T::CrossAccountId::from_eth(from); + let to = T::CrossAccountId::from_eth(to); + let amount = amount.try_into().map_err(|_| "amount overflow")?; + + >::transfer_from_internal(&caller, &from, &to, &self, 1, amount) + .map_err(|_| "transferFrom error")?; + Ok(true) + } + fn approve(&mut self, caller: caller, spender: address, amount: uint256) -> Result { + let caller = T::CrossAccountId::from_eth(caller); + let spender = T::CrossAccountId::from_eth(spender); + let amount = amount.try_into().map_err(|_| "amount overflow")?; + + >::approve_internal(&caller, &spender, &self, 1, amount) + .map_err(|_| "approve internal")?; + Ok(true) + } + fn allowance(&self, owner: address, spender: address) -> Result { + let owner = T::CrossAccountId::from_eth(owner); + let spender = T::CrossAccountId::from_eth(spender); + + Ok(>::get(self.id, (1, owner.as_sub(), spender.as_sub())).into()) + } + fn call_inline_name_symbol(&mut self, c: Msg) -> Result { + ::call(self, c) + } + fn call_inline_total_supply(&mut self, c: Msg) -> Result { + ::call(self, c) + } +} + +impl UniqueFungible for CollectionHandle { + type Error = StringError; + fn call_erc165(&mut self, c: Msg) -> Result { + let ERC165Call::SupportsInterface { interface_id } = c.call; + Ok(evm_coder::abi_encode!(bool( + &UniqueNFTCall::supports_interface(interface_id) + ))) + } + fn call_erc20(&mut self, c: Msg) -> Result { + ::call(self, c) + } +} --- a/pallets/nft/src/eth/mod.rs +++ b/pallets/nft/src/eth/mod.rs @@ -36,209 +36,63 @@ H160(out) } -fn result_to_output(result: Result>, logs: Vec) -> PrecompileOutput { - sp_io::storage::start_transaction(); - match result { - Ok(result) => { - sp_io::storage::commit_transaction(); - // TODO: weight - PrecompileOutput(ExitReason::Succeed(ExitSucceed::Returned), result.finish(), 0, logs) - } - Err(Some(s)) => { - sp_io::storage::rollback_transaction(); - // Error(string) - let mut out = AbiWriter::new_call(0x08c379a0); - out.string(&s); - PrecompileOutput(ExitReason::Revert(ExitRevert::Reverted), out.finish(), 0, Vec::new()) - } - Err(None) => { - sp_io::storage::rollback_transaction(); - PrecompileOutput(ExitReason::Revert(ExitRevert::Reverted), Vec::new(), 0, Vec::new()) - } - } -} - -fn call_internal(sender: H160, collection: &CollectionHandle, method_id: u32, mut input: AbiReader) -> Result> { - let erc20 = matches!(collection.mode, CollectionMode::Fungible(_)); - let erc721 = matches!(collection.mode, CollectionMode::NFT); - - Ok(match method_id { - // function name() external view returns (string memory) - fn_selector!(name()) => { - let name = collection.name.iter() - .map(|&e| e.try_into().ok() as Option) - .collect::>>() - .ok_or(Some("non-ascii name"))?; - - crate::abi_encode!(memory(&name)) - } - // function symbol() external view returns (string memory) - fn_selector!(symbol()) => { - let name = collection.token_prefix.iter() - .map(|&e| e.is_ascii_uppercase().then(|| e)) - .collect::>>() - .ok_or(Some("non-uppercase prefix"))?; - - crate::abi_encode!(memory(&name)) - } - // function decimals() external view returns (uint8 decimals) - fn_selector!(decimals()) if erc20 => { - if let CollectionMode::Fungible(decimals) = &collection.mode { - crate::abi_encode!(uint8(*decimals)) - } else { - unreachable!() +fn call_internal( + collection: &mut CollectionHandle, + caller: caller, + method_id: u32, + mut input: AbiReader, + value: U256, +) -> Result, evm_coder::abi::StringError> { + match collection.mode.clone() { + CollectionMode::Fungible(_) => { + #[cfg(feature = "std")] + { + println!("Parse fungible call {:x}", method_id); } - } - // function totalSupply() external view returns (uint256) - fn_selector!(totalSupply()) if erc20 || erc721 => { - // TODO: can't be implemented, as we don't track total amount of fungibles - crate::abi_encode!(uint256(0)) - } - // function balanceOf(address account) external view returns (uint256) - fn_selector!(balanceOf(address)) if erc20 || erc721 => { - crate::abi_decode!(input, account: address); - let account = T::EvmAddressMapping::into_account_id(account); - let balance = >::get(collection.id, account); - crate::abi_encode!(uint256(balance)) + let call = match UniqueFungibleCall::parse(method_id, &mut input)? { + Some(v) => v, + None => { + #[cfg(feature = "std")] + { + println!("Method not found"); + } + return Ok(None); + } + }; + #[cfg(feature = "std")] + { + dbg!(&call); + } + Ok(Some( as UniqueFungible>::call( + collection, + Msg { + call, + caller, + value, + }, + )?)) } - // function ownerOf(uint256 tokenId) external view returns (address) - fn_selector!(ownerOf(uint256)) if erc721 => { - crate::abi_decode!(input, token_id: uint256); - let token_id: u32 = token_id.try_into().map_err(|_| "bad token id")?; - - let token = >::get(collection.id, token_id).ok_or("unknown token")?; - - crate::abi_encode!(address(token.owner.as_eth().clone())) + CollectionMode::NFT => { + let call = match UniqueNFTCall::parse(method_id, &mut input)? { + Some(v) => v, + None => return Ok(None), + }; + Ok(Some( as UniqueNFT>::call( + collection, + Msg { + call, + caller, + value, + }, + )?)) } - // function transfer(address recipient, uint256 amount) external returns (bool) { - fn_selector!(transfer(address, uint256)) if erc20 => { - crate::abi_decode!(input, recipient: address, amount: uint256); - let sender = T::CrossAccountId::from_eth(sender); - let recipient = T::CrossAccountId::from_eth(recipient); - - >::transfer_internal( - &sender, - &recipient, - &collection, - 1, - amount, - ).map_err(|_| "transfer error")?; - - crate::abi_encode!(bool(true)) + _ => { + return Err(StringError::from( + "erc calls only supported to fungible and nft collections for now", + ) + .into()) } - - fn_selector!(transfer(address, uint256)) if erc721 => { - crate::abi_decode!(input, recipient: address, token_id: uint256); - let sender = T::CrossAccountId::from_eth(sender); - let recipient = T::CrossAccountId::from_eth(recipient); - let token_id: u32 = token_id.try_into().map_err(|_| "bad token id")?; - - >::transfer_internal( - &sender, - &recipient, - &collection, - token_id, - 1, - ).map_err(|_| "transfer error")?; - - crate::abi_encode!(bool(true)) - } - - // function allowance(address owner, address spender) external view returns (uint256) - fn_selector!(allowance(address, address)) if erc20 => { - crate::abi_decode!(input, owner: address, spender: address); - let owner = T::EvmAddressMapping::into_account_id(owner); - let spender = T::EvmAddressMapping::into_account_id(spender); - let allowance = >::get(collection.id, (1, &owner, &spender)); - crate::abi_encode!(uint256(allowance)) - } - // function approve(address spender, uint256 amount) external returns (bool) - // FIXME: All current implementations resets amount to specified value, ours - adds it - // FIXME: Our implementation doesn't handle resets (approve with zero amount) - fn_selector!(approve(address, uint256)) if erc20 => { - crate::abi_decode!(input, spender: address, amount: uint256); - let sender = T::CrossAccountId::from_eth(sender); - let spender = T::CrossAccountId::from_eth(spender); - - >::approve_internal( - &sender, - &spender, - &collection, - 1, - amount, - ).map_err(|_| "approve error")?; - - crate::abi_encode!(bool(true)) - } - // function approve(address approved, uint256 tokenId) external payable - fn_selector!(approve(address, uint256)) if erc721 => { - crate::abi_decode!(input, approved: address, token_id: uint256); - let sender = T::CrossAccountId::from_eth(sender); - let approved = T::CrossAccountId::from_eth(approved); - let token_id = token_id.try_into().map_err(|_| "bad token id")?; - - >::approve_internal( - &sender, - &approved, - &collection, - token_id, - 1, - ).map_err(|_| "approve error")?; - crate::abi_encode!() - } - // function transferFrom(address sender, address recipient, uint256 amount) external returns (bool) - fn_selector!(transferFrom(address, address, uint256)) if erc20 => { - crate::abi_decode!(input, from: address, recipient: address, amount: uint256); - let sender = T::CrossAccountId::from_eth(sender); - let from = T::CrossAccountId::from_eth(from); - let recipient = T::CrossAccountId::from_eth(recipient); - - >::transfer_from_internal( - &sender, - &from, - &recipient, - &collection, - 1, - amount, - ).map_err(|_| "transfer_from error")?; - - crate::abi_encode!(bool(true)) - } - // function transferFrom(address from, address to, uint256 tokenId) external payable - fn_selector!(transferFrom(address, address, uint256)) if erc721 => { - crate::abi_decode!(input, from: address, recipient: address, token_id: uint256); - let sender = T::CrossAccountId::from_eth(sender); - let from = T::CrossAccountId::from_eth(from); - let recipient = T::CrossAccountId::from_eth(recipient); - let token_id = token_id.try_into().map_err(|_| "bad token id")?; - - >::transfer_from_internal( - &sender, - &from, - &recipient, - &collection, - token_id, - 1, - ).map_err(|_| "transfer_from error")?; - - crate::abi_encode!() - } - // function supportsInterface(bytes4 interfaceID) public pure returns (bool) - fn_selector!(supportsInterface(bytes4)) => { - crate::abi_decode!(input, interface_id: uint32); - let supports = match interface_id { - // ERC165 - 0x01ffc9a7 => true, - // ERC20 - 0x36372b07 if erc20 => true, - // ERC721 - 0x80ac58cd if erc721 => true, - _ => false, - }; - crate::abi_encode!(bool(supports)) - } - _ => return Err(None) - }) + } } impl pallet_evm::OnMethodCall for NftErcSupport { @@ -265,35 +119,33 @@ fn call( source: &H160, target: &H160, + gas_limit: u64, input: &[u8], + value: U256, ) -> Option { - let collection = map_eth_to_id(&target) - .and_then(>::get)?; + let mut collection = map_eth_to_id(&target) + .and_then(|id| >::get_with_gas_limit(id, gas_limit))?; let (method_id, input) = AbiReader::new_call(input).unwrap(); - let result = call_internal(*source, &collection, method_id, input); - Some(result_to_output(result, collection.logs.retrieve_logs_for_contract(*target))) + let result = call_internal(&mut collection, *source, method_id, input, value); + let cost = gas_limit - collection.gas_left(); + let logs = collection.logs.retrieve_logs(); + match result { + Ok(Some(v)) => Some(PrecompileOutput { + exit_status: ExitReason::Succeed(ExitSucceed::Returned), + cost, + logs, + output: v.finish(), + }), + Ok(None) => None, + Err(e) => Some(PrecompileOutput { + exit_status: ExitReason::Revert(ExitRevert::Reverted), + cost: 0, + logs: Default::default(), + output: AbiWriter::from(e).finish(), + }), + } } } - -pub const TRANSFER_NFT_TOPIC: H256 = event_topic!(Transfer(address, address, uint256)); -pub const APPROVAL_NFT_TOPIC: H256 = event_topic!(Approval(address, address, uint256)); -// TODO: event ApprovalForAll(address indexed owner, address indexed operator, bool approved); - -pub const TRANSFER_FUNGIBLE_TOPIC: H256 = event_topic!(Transfer(address, address, uint256)); -pub const APPROVAL_FUNGIBLE_TOPIC: H256 = event_topic!(Approval(address, address, uint256)); - -pub fn address_to_topic(address: &H160) -> H256 { - let mut output = [0; 32]; - output[12..32].copy_from_slice(&address.0); - H256(output) -} - -pub fn u32_to_topic(id: u32) -> H256 { - let mut output = [0; 32]; - output[28..32].copy_from_slice(&id.to_be_bytes()); - H256(output) -} - // TODO: This function is slow, and output can be memoized pub fn generate_transaction(collection_id: u32, chain_id: u64) -> ethereum::Transaction {