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

difftreelog

feat ethereum method handling

Yaroslav Bolyukin2021-04-30parent: #651e1c7.patch.diff
in: master

8 files changed

modifiedpallets/nft/src/eth/mod.rsdiffbeforeafterboth
4use abi::{AbiReader, AbiWriter};4use abi::{AbiReader, AbiWriter};
5pub mod log;5pub mod log;
66
77use sp_std::borrow::ToOwned;
8use sp_std::vec::Vec;
9use sp_std::convert::TryInto;
10
11use codec::{Decode, Encode};
12use pallet_evm::{AddressMapping, PrecompileLog, PrecompileOutput, ExitReason, ExitRevert, ExitSucceed};
13use sp_core::{H160, H256};
14use frame_support::storage::{StorageMap, StorageDoubleMap};
15
16use crate::{Allowances, NftItemList, Module, Balance, Config, CollectionById, CollectionHandle, CollectionId, CollectionMode};
17
18pub struct NftErcSupport<T: Config>(core::marker::PhantomData<T>);
19
20// 0x17c4e6453Cc49AAAaEACA894e6D9683e00000001 - collection
21// TODO: Unhardcode prefix
22const ETH_ACCOUNT_PREFIX: [u8; 16] = [0x17, 0xc4, 0xe6, 0x45, 0x3c, 0xc4, 0x9a, 0xaa, 0xae, 0xac, 0xa8, 0x94, 0xe6, 0xd9, 0x68, 0x3e];
23
24fn map_eth_to_id(eth: &H160) -> Option<CollectionId> {
25 if &eth[0..16] != ETH_ACCOUNT_PREFIX {
26 return None;
27 }
28 let mut id_bytes = [0; 4];
29 id_bytes.copy_from_slice(&eth[16..20]);
30 Some(u32::from_be_bytes(id_bytes))
31}
32pub fn collection_id_to_address(id: u32) -> H160 {
33 let mut out = [0; 20];
34 out[0..16].copy_from_slice(&ETH_ACCOUNT_PREFIX);
35 out[16..20].copy_from_slice(&u32::to_be_bytes(id));
36 H160(out)
37}
38
39fn result_to_output(result: Result<AbiWriter, Option<&'static str>>, logs: Vec<PrecompileLog>) -> PrecompileOutput {
40 sp_io::storage::start_transaction();
41 match result {
42 Ok(result) => {
43 sp_io::storage::commit_transaction();
44 // TODO: weight
45 PrecompileOutput(ExitReason::Succeed(ExitSucceed::Returned), result.finish(), 0, logs)
46 }
47 Err(Some(s)) => {
48 sp_io::storage::rollback_transaction();
49 // Error(string)
50 let mut out = AbiWriter::new_call(0x08c379a0);
51 out.string(&s);
52 PrecompileOutput(ExitReason::Revert(ExitRevert::Reverted), out.finish(), 0, Vec::new())
53 }
54 Err(None) => {
55 sp_io::storage::rollback_transaction();
56 PrecompileOutput(ExitReason::Revert(ExitRevert::Reverted), Vec::new(), 0, Vec::new())
57 }
58 }
59}
60
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::ReFungible);
64
65 Ok(match method_id {
66 // function name() external view returns (string memory)
67 0x06fdde03 => {
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 0x95d89b41 => {
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 0x313ce567 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 0x18160ddd if erc20 => {
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 0x70a08231 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 0x6352211e 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 0xa9059cbb 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 // function allowance(address owner, address spender) external view returns (uint256)
130 0xdd62ed3e if erc20 => {
131 crate::abi_decode!(input, owner: address, spender: address);
132 let owner = T::EvmAddressMapping::into_account_id(owner);
133 let spender = T::EvmAddressMapping::into_account_id(spender);
134 let allowance = <Allowances<T>>::get(collection.id, (1, &owner, &spender));
135 crate::abi_encode!(uint256(allowance))
136 }
137 // function approve(address spender, uint256 amount) external returns (bool)
138 // FIXME: All current implementations resets amount to specified value, ours - adds it
139 // FIXME: Our implementation doesn't handle resets (approve with zero amount)
140 0x095ea7b3 if erc20 => {
141 crate::abi_decode!(input, spender: address, amount: uint256);
142 let sender = T::CrossAccountId::from_eth(sender);
143 let spender = T::CrossAccountId::from_eth(spender);
144
145 <Module<T>>::approve_internal(
146 sender,
147 spender,
148 &collection,
149 1,
150 amount,
151 ).map_err(|_| "approve error")?;
152
153 crate::abi_encode!(bool(true))
154 }
155 // function approve(address approved, uint256 tokenId) external payable
156 0x095ea7b3 if erc721 => {
157 crate::abi_decode!(input, approved: address, token_id: uint256);
158 let sender = T::CrossAccountId::from_eth(sender);
159 let approved = T::CrossAccountId::from_eth(approved);
160 let token_id = token_id.try_into().map_err(|_| "bad token id")?;
161
162 <Module<T>>::approve_internal(
163 sender,
164 approved,
165 &collection,
166 token_id,
167 1,
168 ).map_err(|_| "approve error")?;
169 crate::abi_encode!()
170 }
171 // function transferFrom(address sender, address recipient, uint256 amount) external returns (bool)
172 0x23b872dd if erc20 => {
173 crate::abi_decode!(input, from: address, recipient: address, amount: uint256);
174 let sender = T::CrossAccountId::from_eth(sender);
175 let from = T::CrossAccountId::from_eth(from);
176 let recipient = T::CrossAccountId::from_eth(recipient);
177
178 <Module<T>>::transfer_from_internal(
179 sender,
180 from,
181 recipient,
182 &collection,
183 1,
184 amount,
185 ).map_err(|_| "transfer_from error")?;
186
187 crate::abi_encode!(bool(true))
188 }
189 // function transferFrom(address from, address to, uint256 tokenId) external payable
190 0x23b872dd if erc721 => {
191 crate::abi_decode!(input, from: address, recipient: address, token_id: 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 let token_id = token_id.try_into().map_err(|_| "bad token id")?;
196
197 <Module<T>>::transfer_from_internal(
198 sender,
199 from,
200 recipient,
201 &collection,
202 token_id,
203 1,
204 ).map_err(|_| "transfer_from error")?;
205
206 crate::abi_encode!()
207 }
208 // function supportsInterface(bytes4 interfaceID) public pure returns (bool)
209 0x01ffc9a7 => {
210 crate::abi_decode!(input, interface_id: uint32);
211 let supports = match interface_id {
212 // ERC165
213 0x01ffc9a7 => true,
214 // ERC20
215 0x36372b07 if erc20 => true,
216 // ERC721
217 0x80ac58cd if erc721 => true,
218 _ => false,
219 };
220 crate::abi_encode!(bool(supports))
221 }
222 _ => return Err(None)
223 })
224}
225
226impl<T: Config> pallet_evm::OnMethodCall<T> for NftErcSupport<T> {
227 fn is_reserved(target: &H160) -> bool {
228 map_eth_to_id(target).is_some()
229 }
230 fn is_used(target: &H160) -> bool {
231 map_eth_to_id(target)
232 .map(<CollectionById<T>>::contains_key)
233 .unwrap_or(false)
234 }
235 fn get_code(target: &H160) -> Option<Vec<u8>> {
236 map_eth_to_id(&target)
237 .and_then(<CollectionById<T>>::get)
238 .map(|collection| {
239 match collection.mode {
240 CollectionMode::NFT => include_bytes!("stubs/ERC721.bin") as &[u8],
241 CollectionMode::Fungible(_) => include_bytes!("stubs/ERC20.bin") as &[u8],
242 CollectionMode::ReFungible => include_bytes!("stubs/ERC1633.bin") as &[u8],
243 CollectionMode::Invalid => include_bytes!("stubs/Invalid.bin") as &[u8],
244 }.to_owned()
245 })
246 }
247 fn call(
248 source: &H160,
249 target: &H160,
250 input: &[u8],
251 ) -> Option<PrecompileOutput> {
252 let collection = map_eth_to_id(&target)
253 .and_then(<CollectionHandle<T>>::get)?;
254 let (method_id, input) = AbiReader::new_call(input).unwrap();
255 let result = call_internal(*source, &collection, method_id, input);
256 Some(result_to_output(result, collection.logs.retrieve_logs_for_contract(*target)))
257 }
258}
259
260/// event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);
261pub const TRANSFER_NFT_TOPIC: H256 = H256(hex_literal::hex!("ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"));
262/// event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);
263pub const APPROVAL_NFT_TOPIC: H256 = H256(hex_literal::hex!("8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925"));
264// TODO: event ApprovalForAll(address indexed owner, address indexed operator, bool approved);
265
266/// event Transfer(address indexed from, address indexed to, uint256 indexed amount);
267pub const TRANSFER_FUNGIBLE_TOPIC: H256 = H256(hex_literal::hex!("ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"));
268/// event Approval(address indexed owner, address indexed approved, uint256 indexed amount);
269pub const APPROVAL_FUNGIBLE_TOPIC: H256 = H256(hex_literal::hex!("8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925"));
270
271pub fn address_to_topic(address: &H160) -> H256 {
272 let mut output = [0; 32];
273 output[12..32].copy_from_slice(&address.0);
274 H256(output)
275}
276
277
278// TODO: This function is slow, and output can be memoized
279pub fn generate_transaction(collection_id: u32, chain_id: u64) -> ethereum::Transaction {
280 let contract = collection_id_to_address(collection_id);
281
282 // TODO: Make it work without native runtime by forking ethereum_tx_sign, and
283 // switching to pure-rust implementation of secp256k1
284 #[cfg(feature = "std")]
285 {
286 let signed = ethereum_tx_sign::RawTransaction {
287 nonce: 0.into(),
288 to: Some(contract.0.into()),
289 value: 0.into(),
290 gas_price: 0.into(),
291 gas: 0.into(),
292 // zero selector, this transaction always have same sender, so all data should be acquired from logs
293 data: Vec::from([0, 0, 0, 0]),
294 }.sign(
295 // TODO: move to pallet config
296 // 0xF70631E55faff9f3FD3681545aa6c724226a3853
297 // 9dbaef9b3ebc00e53f67c6a77bcfbf2c4f2aebe4d70d94af4f2df01744b7a91a
298 &hex_literal::hex!("9dbaef9b3ebc00e53f67c6a77bcfbf2c4f2aebe4d70d94af4f2df01744b7a91a").into(),
299 &chain_id
300 );
301 rlp::decode::<ethereum::Transaction>(&signed).expect("transaction is just created, it can't be broken")
302 }
303 #[cfg(not(feature = "std"))]
304 {
305 panic!("transaction generation not yet supported by wasm runtime")
306 }
307}
addedpallets/nft/src/eth/stubs/ERC1633.bindiffbeforeafterboth

no changes

addedpallets/nft/src/eth/stubs/ERC20.bindiffbeforeafterboth

binary blob — no preview

addedpallets/nft/src/eth/stubs/ERC20.soldiffbeforeafterboth

no changes

addedpallets/nft/src/eth/stubs/ERC721.bindiffbeforeafterboth

no changes

addedpallets/nft/src/eth/stubs/ERC721.soldiffbeforeafterboth

no changes

addedpallets/nft/src/eth/stubs/Invalid.bindiffbeforeafterboth

no changes

modifiedpallets/nft/src/lib.rsdiffbeforeafterboth
55mod default_weights;55mod default_weights;
56mod eth;56mod eth;
5757
58pub use eth::NftErcSupport;
58pub use eth::account::*;59pub use eth::account::*;
5960
60pub const MAX_DECIMAL_POINTS: DecimalPoints = 30;61pub const MAX_DECIMAL_POINTS: DecimalPoints = 30;