difftreelog
refactor switch to macro-based ERC impls
in: master
2 files changed
pallets/nft/src/eth/erc_impl.rsdiffbeforeafterboth--- /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<T> = core::result::Result<T, StringError>;
+
+impl<T: Config> InlineNameSymbol for CollectionHandle<T> {
+ type Error = StringError;
+
+ fn name(&self) -> Result<string> {
+ Ok(decode_utf16(self.name.iter().copied())
+ .map(|r| r.unwrap_or(REPLACEMENT_CHARACTER))
+ .collect::<string>())
+ }
+
+ fn symbol(&self) -> Result<string> {
+ Ok(string::from_utf8_lossy(&self.token_prefix).into())
+ }
+}
+
+impl<T: Config> InlineTotalSupply for CollectionHandle<T> {
+ type Error = StringError;
+
+ fn total_supply(&self) -> Result<uint256> {
+ // TODO: we do not track total amount of all tokens
+ Ok(0.into())
+ }
+}
+
+impl<T: Config> ERC721Metadata for CollectionHandle<T> {
+ type Error = StringError;
+
+ fn token_uri(&self, token_id: uint256) -> Result<string> {
+ // 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<InlineNameSymbolCall>) -> Result<AbiWriter> {
+ <Self as InlineNameSymbol>::call(self, c)
+ }
+}
+
+impl<T: Config> ERC721Enumerable for CollectionHandle<T> {
+ type Error = StringError;
+
+ fn token_by_index(&self, index: uint256) -> Result<uint256> {
+ Ok(index)
+ }
+
+ fn token_of_owner_by_index(&self, owner: address, index: uint256) -> Result<uint256> {
+ // TODO: Not implemetable
+ Err("not implemented".into())
+ }
+
+ fn call_inline_total_supply(&mut self, c: Msg<InlineTotalSupplyCall>) -> Result<AbiWriter> {
+ <Self as InlineTotalSupply>::call(self, c)
+ }
+}
+
+impl<T: Config> ERC721 for CollectionHandle<T> {
+ type Error = StringError;
+
+ fn balance_of(&self, owner: address) -> Result<uint256> {
+ let owner = T::EvmAddressMapping::into_account_id(owner);
+ let balance = <Balance<T>>::get(self.id, owner);
+ Ok(balance.into())
+ }
+ fn owner_of(&self, token_id: uint256) -> Result<address> {
+ let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;
+ let token = <NftItemList<T>>::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<void> {
+ // TODO: Not implemetable
+ Err("not implemented".into())
+ }
+ fn safe_transfer_from(
+ &mut self,
+ _from: address,
+ _to: address,
+ _token_id: uint256,
+ _value: value,
+ ) -> Result<void> {
+ // TODO: Not implemetable
+ Err("not implemented".into())
+ }
+
+ fn transfer_from(
+ &mut self,
+ caller: caller,
+ from: address,
+ to: address,
+ token_id: uint256,
+ _value: value,
+ ) -> Result<void> {
+ 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")?;
+
+ <Module<T>>::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<void> {
+ 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")?;
+
+ <Module<T>>::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<void> {
+ // TODO: Not implemetable
+ Err("not implemented".into())
+ }
+
+ fn get_approved(&self, _token_id: uint256) -> Result<address> {
+ // TODO: Not implemetable
+ Err("not implemented".into())
+ }
+
+ fn is_approved_for_all(&self, _owner: address, _operator: address) -> Result<address> {
+ // TODO: Not implemetable
+ Err("not implemented".into())
+ }
+
+ fn call_erc165(&mut self, c: Msg<ERC165Call>) -> Result<AbiWriter> {
+ let ERC165Call::SupportsInterface { interface_id } = c.call;
+ Ok(evm_coder::abi_encode!(bool(
+ &ERC721Call::supports_interface(interface_id)
+ )))
+ }
+}
+
+impl<T: Config> ERC721UniqueExtensions for CollectionHandle<T> {
+ type Error = StringError;
+ fn transfer(
+ &mut self,
+ caller: caller,
+ to: address,
+ token_id: uint256,
+ value: value,
+ ) -> Result<void> {
+ 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")?;
+
+ <Module<T>>::transfer_internal(&caller, &to, &self, token_id, 1)
+ .map_err(|_| "transfer error")?;
+ Ok(())
+ }
+}
+
+impl<T: Config> UniqueNFT for CollectionHandle<T> {
+ type Error = StringError;
+ fn call_erc165(&mut self, c: Msg<ERC165Call>) -> Result<AbiWriter> {
+ 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<ERC721Call>) -> Result<AbiWriter> {
+ <Self as ERC721>::call(self, c)
+ }
+ fn call_erc721_metadata(&mut self, c: Msg<ERC721MetadataCall>) -> Result<AbiWriter> {
+ <Self as ERC721Metadata>::call(self, c)
+ }
+ fn call_erc721_enumerable(&mut self, c: Msg<ERC721EnumerableCall>) -> Result<AbiWriter> {
+ <Self as ERC721Enumerable>::call(self, c)
+ }
+ fn call_erc721_unique_extensions(
+ &mut self,
+ c: Msg<ERC721UniqueExtensionsCall>,
+ ) -> Result<AbiWriter> {
+ <Self as ERC721UniqueExtensions>::call(self, c)
+ }
+}
+
+impl<T: Config> ERC20 for CollectionHandle<T> {
+ type Error = StringError;
+ fn decimals(&self) -> Result<uint8> {
+ Ok(if let CollectionMode::Fungible(decimals) = &self.mode {
+ *decimals
+ } else {
+ unreachable!()
+ })
+ }
+ fn balance_of(&self, owner: address) -> Result<uint256> {
+ let owner = T::EvmAddressMapping::into_account_id(owner);
+ let balance = <Balance<T>>::get(self.id, owner);
+ Ok(balance.into())
+ }
+ fn transfer(&mut self, caller: caller, to: address, amount: uint256) -> Result<bool> {
+ let caller = T::CrossAccountId::from_eth(caller);
+ let to = T::CrossAccountId::from_eth(to);
+ let amount = amount.try_into().map_err(|_| "amount overflow")?;
+
+ <Module<T>>::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<bool> {
+ 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")?;
+
+ <Module<T>>::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<bool> {
+ let caller = T::CrossAccountId::from_eth(caller);
+ let spender = T::CrossAccountId::from_eth(spender);
+ let amount = amount.try_into().map_err(|_| "amount overflow")?;
+
+ <Module<T>>::approve_internal(&caller, &spender, &self, 1, amount)
+ .map_err(|_| "approve internal")?;
+ Ok(true)
+ }
+ fn allowance(&self, owner: address, spender: address) -> Result<uint256> {
+ let owner = T::CrossAccountId::from_eth(owner);
+ let spender = T::CrossAccountId::from_eth(spender);
+
+ Ok(<Allowances<T>>::get(self.id, (1, owner.as_sub(), spender.as_sub())).into())
+ }
+ fn call_inline_name_symbol(&mut self, c: Msg<InlineNameSymbolCall>) -> Result<AbiWriter> {
+ <Self as InlineNameSymbol>::call(self, c)
+ }
+ fn call_inline_total_supply(&mut self, c: Msg<InlineTotalSupplyCall>) -> Result<AbiWriter> {
+ <Self as InlineTotalSupply>::call(self, c)
+ }
+}
+
+impl<T: Config> UniqueFungible for CollectionHandle<T> {
+ type Error = StringError;
+ fn call_erc165(&mut self, c: Msg<ERC165Call>) -> Result<AbiWriter> {
+ 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<ERC20Call>) -> Result<AbiWriter> {
+ <Self as ERC20>::call(self, c)
+ }
+}
pallets/nft/src/eth/mod.rsdiffbeforeafterboth36 H160(out)36 H160(out)37}37}383839fn result_to_output(result: Result<AbiWriter, Option<&'static str>>, logs: Vec<PrecompileLog>) -> PrecompileOutput {39fn call_internal<T: Config>(40 collection: &mut CollectionHandle<T>,41 caller: caller,42 method_id: u32,43 mut input: AbiReader,44 value: U256,45) -> Result<Option<AbiWriter>, evm_coder::abi::StringError> {40 sp_io::storage::start_transaction();46 match collection.mode.clone() {47 CollectionMode::Fungible(_) => {48 #[cfg(feature = "std")]49 {50 println!("Parse fungible call {:x}", method_id);41 match result {51 }52 let call = match UniqueFungibleCall::parse(method_id, &mut input)? {42 Ok(result) => {53 Some(v) => v,54 None => {43 sp_io::storage::commit_transaction();55 #[cfg(feature = "std")]56 {57 println!("Method not found");44 // TODO: weight58 }59 return Ok(None);60 }61 };62 #[cfg(feature = "std")]45 PrecompileOutput(ExitReason::Succeed(ExitSucceed::Returned), result.finish(), 0, logs)63 {64 dbg!(&call);65 }66 Ok(Some(<CollectionHandle<T> as UniqueFungible>::call(67 collection,68 Msg {69 call,70 caller,71 value,72 },73 )?))46 }74 }47 Err(Some(s)) => {75 CollectionMode::NFT => {48 sp_io::storage::rollback_transaction();76 let call = match UniqueNFTCall::parse(method_id, &mut input)? {49 // Error(string)77 Some(v) => v,50 let mut out = AbiWriter::new_call(0x08c379a0);78 None => return Ok(None),51 out.string(&s);79 };52 PrecompileOutput(ExitReason::Revert(ExitRevert::Reverted), out.finish(), 0, Vec::new())80 Ok(Some(<CollectionHandle<T> as UniqueNFT>::call(81 collection,82 Msg {83 call,84 caller,85 value,86 },87 )?))53 }88 }54 Err(None) => {89 _ => {55 sp_io::storage::rollback_transaction();90 return Err(StringError::from(56 PrecompileOutput(ExitReason::Revert(ExitRevert::Reverted), Vec::new(), 0, Vec::new())91 "erc calls only supported to fungible and nft collections for now",92 )93 .into())57 }94 }58 }95 }59}96}609761fn call_internal<T: Config>(sender: H160, collection: &CollectionHandle<T>, method_id: u32, mut input: AbiReader) -> Result<AbiWriter, Option<&'static str>> {62 let erc20 = matches!(collection.mode, CollectionMode::Fungible(_));63 let erc721 = matches!(collection.mode, CollectionMode::NFT);6465 Ok(match method_id {66 // function name() external view returns (string memory)67 fn_selector!(name()) => {68 let name = collection.name.iter()69 .map(|&e| e.try_into().ok() as Option<u8>)70 .collect::<Option<Vec<u8>>>()71 .ok_or(Some("non-ascii name"))?;7273 crate::abi_encode!(memory(&name))74 }75 // function symbol() external view returns (string memory)76 fn_selector!(symbol()) => {77 let name = collection.token_prefix.iter()78 .map(|&e| e.is_ascii_uppercase().then(|| e))79 .collect::<Option<Vec<u8>>>()80 .ok_or(Some("non-uppercase prefix"))?;8182 crate::abi_encode!(memory(&name))83 }84 // function decimals() external view returns (uint8 decimals)85 fn_selector!(decimals()) if erc20 => {86 if let CollectionMode::Fungible(decimals) = &collection.mode {87 crate::abi_encode!(uint8(*decimals))88 } else {89 unreachable!()90 }91 }92 // function totalSupply() external view returns (uint256)93 fn_selector!(totalSupply()) if erc20 || erc721 => {94 // TODO: can't be implemented, as we don't track total amount of fungibles95 crate::abi_encode!(uint256(0))96 }97 // function balanceOf(address account) external view returns (uint256)98 fn_selector!(balanceOf(address)) if erc20 || erc721 => {99 crate::abi_decode!(input, account: address);100 let account = T::EvmAddressMapping::into_account_id(account);101 let balance = <Balance<T>>::get(collection.id, account);102 crate::abi_encode!(uint256(balance))103 }104 // function ownerOf(uint256 tokenId) external view returns (address)105 fn_selector!(ownerOf(uint256)) if erc721 => {106 crate::abi_decode!(input, token_id: uint256);107 let token_id: u32 = token_id.try_into().map_err(|_| "bad token id")?;108109 let token = <NftItemList<T>>::get(collection.id, token_id).ok_or("unknown token")?;110111 crate::abi_encode!(address(token.owner.as_eth().clone()))112 }113 // function transfer(address recipient, uint256 amount) external returns (bool) {114 fn_selector!(transfer(address, uint256)) if erc20 => {115 crate::abi_decode!(input, recipient: address, amount: uint256);116 let sender = T::CrossAccountId::from_eth(sender);117 let recipient = T::CrossAccountId::from_eth(recipient);118119 <Module<T>>::transfer_internal(120 &sender,121 &recipient,122 &collection,123 1,124 amount,125 ).map_err(|_| "transfer error")?;126127 crate::abi_encode!(bool(true))128 }129130 fn_selector!(transfer(address, uint256)) if erc721 => {131 crate::abi_decode!(input, recipient: address, token_id: uint256);132 let sender = T::CrossAccountId::from_eth(sender);133 let recipient = T::CrossAccountId::from_eth(recipient);134 let token_id: u32 = token_id.try_into().map_err(|_| "bad token id")?;135136 <Module<T>>::transfer_internal(137 &sender,138 &recipient,139 &collection,140 token_id,141 1,142 ).map_err(|_| "transfer error")?;143144 crate::abi_encode!(bool(true))145 }146147 // function allowance(address owner, address spender) external view returns (uint256)148 fn_selector!(allowance(address, address)) if erc20 => {149 crate::abi_decode!(input, owner: address, spender: address);150 let owner = T::EvmAddressMapping::into_account_id(owner);151 let spender = T::EvmAddressMapping::into_account_id(spender);152 let allowance = <Allowances<T>>::get(collection.id, (1, &owner, &spender));153 crate::abi_encode!(uint256(allowance))154 }155 // function approve(address spender, uint256 amount) external returns (bool)156 // FIXME: All current implementations resets amount to specified value, ours - adds it157 // FIXME: Our implementation doesn't handle resets (approve with zero amount)158 fn_selector!(approve(address, uint256)) if erc20 => {159 crate::abi_decode!(input, spender: address, amount: uint256);160 let sender = T::CrossAccountId::from_eth(sender);161 let spender = T::CrossAccountId::from_eth(spender);162163 <Module<T>>::approve_internal(164 &sender,165 &spender,166 &collection,167 1,168 amount,169 ).map_err(|_| "approve error")?;170171 crate::abi_encode!(bool(true))172 }173 // function approve(address approved, uint256 tokenId) external payable174 fn_selector!(approve(address, uint256)) if erc721 => {175 crate::abi_decode!(input, approved: address, token_id: uint256);176 let sender = T::CrossAccountId::from_eth(sender);177 let approved = T::CrossAccountId::from_eth(approved);178 let token_id = token_id.try_into().map_err(|_| "bad token id")?;179180 <Module<T>>::approve_internal(181 &sender,182 &approved,183 &collection,184 token_id,185 1,186 ).map_err(|_| "approve error")?;187 crate::abi_encode!()188 }189 // function transferFrom(address sender, address recipient, uint256 amount) external returns (bool)190 fn_selector!(transferFrom(address, address, uint256)) if erc20 => {191 crate::abi_decode!(input, from: address, recipient: address, amount: uint256);192 let sender = T::CrossAccountId::from_eth(sender);193 let from = T::CrossAccountId::from_eth(from);194 let recipient = T::CrossAccountId::from_eth(recipient);195196 <Module<T>>::transfer_from_internal(197 &sender,198 &from,199 &recipient,200 &collection,201 1,202 amount,203 ).map_err(|_| "transfer_from error")?;204205 crate::abi_encode!(bool(true))206 }207 // function transferFrom(address from, address to, uint256 tokenId) external payable208 fn_selector!(transferFrom(address, address, uint256)) if erc721 => {209 crate::abi_decode!(input, from: address, recipient: address, token_id: uint256);210 let sender = T::CrossAccountId::from_eth(sender);211 let from = T::CrossAccountId::from_eth(from);212 let recipient = T::CrossAccountId::from_eth(recipient);213 let token_id = token_id.try_into().map_err(|_| "bad token id")?;214215 <Module<T>>::transfer_from_internal(216 &sender,217 &from,218 &recipient,219 &collection,220 token_id,221 1,222 ).map_err(|_| "transfer_from error")?;223224 crate::abi_encode!()225 }226 // function supportsInterface(bytes4 interfaceID) public pure returns (bool)227 fn_selector!(supportsInterface(bytes4)) => {228 crate::abi_decode!(input, interface_id: uint32);229 let supports = match interface_id {230 // ERC165231 0x01ffc9a7 => true,232 // ERC20233 0x36372b07 if erc20 => true,234 // ERC721235 0x80ac58cd if erc721 => true,236 _ => false,237 };238 crate::abi_encode!(bool(supports))239 }240 _ => return Err(None)241 })242}243244impl<T: Config> pallet_evm::OnMethodCall<T> for NftErcSupport<T> {98impl<T: Config> pallet_evm::OnMethodCall<T> for NftErcSupport<T> {245 fn is_reserved(target: &H160) -> bool {99 fn is_reserved(target: &H160) -> bool {265 fn call(119 fn call(266 source: &H160,120 source: &H160,267 target: &H160,121 target: &H160,122 gas_limit: u64,268 input: &[u8],123 input: &[u8],124 value: U256,269 ) -> Option<PrecompileOutput> {125 ) -> Option<PrecompileOutput> {270 let collection = map_eth_to_id(&target)126 let mut collection = map_eth_to_id(&target)271 .and_then(<CollectionHandle<T>>::get)?;127 .and_then(|id| <CollectionHandle<T>>::get_with_gas_limit(id, gas_limit))?;272 let (method_id, input) = AbiReader::new_call(input).unwrap();128 let (method_id, input) = AbiReader::new_call(input).unwrap();273 let result = call_internal(*source, &collection, method_id, input);129 let result = call_internal(&mut collection, *source, method_id, input, value);274 Some(result_to_output(result, collection.logs.retrieve_logs_for_contract(*target)))130 let cost = gas_limit - collection.gas_left();131 let logs = collection.logs.retrieve_logs();132 match result {133 Ok(Some(v)) => Some(PrecompileOutput {134 exit_status: ExitReason::Succeed(ExitSucceed::Returned),135 cost,136 logs,137 output: v.finish(),138 }),139 Ok(None) => None,140 Err(e) => Some(PrecompileOutput {141 exit_status: ExitReason::Revert(ExitRevert::Reverted),142 cost: 0,143 logs: Default::default(),144 output: AbiWriter::from(e).finish(),145 }),146 }275 }147 }276}148}277278pub const TRANSFER_NFT_TOPIC: H256 = event_topic!(Transfer(address, address, uint256));279pub const APPROVAL_NFT_TOPIC: H256 = event_topic!(Approval(address, address, uint256));280// TODO: event ApprovalForAll(address indexed owner, address indexed operator, bool approved);281282pub const TRANSFER_FUNGIBLE_TOPIC: H256 = event_topic!(Transfer(address, address, uint256));283pub const APPROVAL_FUNGIBLE_TOPIC: H256 = event_topic!(Approval(address, address, uint256));284285pub fn address_to_topic(address: &H160) -> H256 {286 let mut output = [0; 32];287 output[12..32].copy_from_slice(&address.0);288 H256(output)289}290291pub fn u32_to_topic(id: u32) -> H256 {292 let mut output = [0; 32];293 output[28..32].copy_from_slice(&id.to_be_bytes());294 H256(output)295}296297149298// TODO: This function is slow, and output can be memoized150// TODO: This function is slow, and output can be memoized299pub fn generate_transaction(collection_id: u32, chain_id: u64) -> ethereum::Transaction {151pub fn generate_transaction(collection_id: u32, chain_id: u64) -> ethereum::Transaction {