git.delta.rocks / unique-network / refs/commits / d612b7eed2eb

difftreelog

refactor switch to macro-based ERC impls

Yaroslav Bolyukin2021-06-02parent: #1a0920c.patch.diff
in: master

2 files changed

addedpallets/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)
+	}
+}
modifiedpallets/nft/src/eth/mod.rsdiffbeforeafterboth
36 H160(out)36 H160(out)
37}37}
3838
39fn 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}
6097
61fn 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);
64
65 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"))?;
72
73 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"))?;
81
82 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 fungibles
95 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")?;
108
109 let token = <NftItemList<T>>::get(collection.id, token_id).ok_or("unknown token")?;
110
111 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);
118
119 <Module<T>>::transfer_internal(
120 &sender,
121 &recipient,
122 &collection,
123 1,
124 amount,
125 ).map_err(|_| "transfer error")?;
126
127 crate::abi_encode!(bool(true))
128 }
129
130 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")?;
135
136 <Module<T>>::transfer_internal(
137 &sender,
138 &recipient,
139 &collection,
140 token_id,
141 1,
142 ).map_err(|_| "transfer error")?;
143
144 crate::abi_encode!(bool(true))
145 }
146
147 // 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 it
157 // 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);
162
163 <Module<T>>::approve_internal(
164 &sender,
165 &spender,
166 &collection,
167 1,
168 amount,
169 ).map_err(|_| "approve error")?;
170
171 crate::abi_encode!(bool(true))
172 }
173 // function approve(address approved, uint256 tokenId) external payable
174 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")?;
179
180 <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);
195
196 <Module<T>>::transfer_from_internal(
197 &sender,
198 &from,
199 &recipient,
200 &collection,
201 1,
202 amount,
203 ).map_err(|_| "transfer_from error")?;
204
205 crate::abi_encode!(bool(true))
206 }
207 // function transferFrom(address from, address to, uint256 tokenId) external payable
208 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")?;
214
215 <Module<T>>::transfer_from_internal(
216 &sender,
217 &from,
218 &recipient,
219 &collection,
220 token_id,
221 1,
222 ).map_err(|_| "transfer_from error")?;
223
224 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 // ERC165
231 0x01ffc9a7 => true,
232 // ERC20
233 0x36372b07 if erc20 => true,
234 // ERC721
235 0x80ac58cd if erc721 => true,
236 _ => false,
237 };
238 crate::abi_encode!(bool(supports))
239 }
240 _ => return Err(None)
241 })
242}
243
244impl<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}
277
278pub 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);
281
282pub const TRANSFER_FUNGIBLE_TOPIC: H256 = event_topic!(Transfer(address, address, uint256));
283pub const APPROVAL_FUNGIBLE_TOPIC: H256 = event_topic!(Approval(address, address, uint256));
284
285pub 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}
290
291pub 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}
296
297149
298// TODO: This function is slow, and output can be memoized150// TODO: This function is slow, and output can be memoized
299pub fn generate_transaction(collection_id: u32, chain_id: u64) -> ethereum::Transaction {151pub fn generate_transaction(collection_id: u32, chain_id: u64) -> ethereum::Transaction {