/home/runner/work/creditcoin/creditcoin/pallets/creditcoin/src/ocw/tasks/verify_transfer.rs
Line | Count | Source (jump to first uncovered line) |
1 | | use ethabi::{Function, Param, ParamType, StateMutability, Token}; |
2 | | use ethereum_types::U64; |
3 | | use frame_support::ensure; |
4 | | use frame_system::pallet_prelude::BlockNumberFor; |
5 | | use sp_core::U256; |
6 | | use sp_runtime::traits::UniqueSaturatedFrom; |
7 | | #[cfg(not(feature = "std"))] |
8 | | use sp_std::prelude::*; |
9 | | |
10 | | use crate::{ |
11 | | ocw::{ |
12 | | self, parse_eth_address, |
13 | | rpc::{self, Address, EthBlock, EthTransaction, EthTransactionReceipt}, |
14 | | OffchainError, OffchainResult, VerificationFailureCause, VerificationResult, |
15 | | ETH_CONFIRMATIONS, |
16 | | }, |
17 | | Blockchain, Config, Currency, DealOrderId, EvmChainId, EvmInfo, ExternalAddress, |
18 | | ExternalAmount, ExternalTxId, Id, LegacyTransferKind, Transfer, UnverifiedTransfer, |
19 | | }; |
20 | | |
21 | 75 | pub(crate) fn ethless_transfer_function_abi() -> Function { |
22 | 75 | #[allow(deprecated)] |
23 | 75 | Function { |
24 | 75 | name: "transfer".into(), |
25 | 75 | inputs: vec![ |
26 | 75 | Param { name: "_from".into(), kind: ParamType::Address, internal_type: None }, |
27 | 75 | Param { name: "_to".into(), kind: ParamType::Address, internal_type: None }, |
28 | 75 | Param { name: "_value".into(), kind: ParamType::Uint(256), internal_type: None }, |
29 | 75 | Param { name: "_fee".into(), kind: ParamType::Uint(256), internal_type: None }, |
30 | 75 | Param { name: "_nonce".into(), kind: ParamType::Uint(256), internal_type: None }, |
31 | 75 | Param { name: "_sig".into(), kind: ParamType::Bytes, internal_type: None }, |
32 | 75 | ], |
33 | 75 | outputs: vec![Param { name: "success".into(), kind: ParamType::Bool, internal_type: None }], |
34 | 75 | constant: false, |
35 | 75 | state_mutability: StateMutability::NonPayable, |
36 | 75 | } |
37 | 75 | } |
38 | | |
39 | 22 | pub(in crate::ocw) fn validate_ethless_transfer( |
40 | 22 | from: &Address, |
41 | 22 | to: &Address, |
42 | 22 | contract: &Address, |
43 | 22 | amount: &ExternalAmount, |
44 | 22 | receipt: &EthTransactionReceipt, |
45 | 22 | transaction: &EthTransaction, |
46 | 22 | eth_tip: U64, |
47 | 22 | id_hash: impl ethereum_types::BigEndianHash<Uint = U256>, |
48 | 22 | ) -> OffchainResult<()> { |
49 | 22 | let transfer_fn = ethless_transfer_function_abi(); |
50 | 22 | ensure!(receipt.is_success(), VerificationFailureCause::TaskFailed1 ); |
51 | | |
52 | 21 | let block_number20 = transaction.block_number.ok_or(VerificationFailureCause::TaskPending)?1 ; |
53 | | |
54 | 20 | ensure!(block_number <= eth_tip, VerificationFailureCause::TaskInFuture1 ); |
55 | | |
56 | 19 | let diff = eth_tip - block_number; |
57 | 19 | |
58 | 19 | ensure!(diff.as_u64() >= ETH_CONFIRMATIONS, VerificationFailureCause::TaskUnconfirmed3 ); |
59 | | |
60 | 16 | if let Some(to15 ) = &transaction.to { |
61 | 15 | ensure!(to == contract, VerificationFailureCause::IncorrectContract1 ); |
62 | | } else { |
63 | 1 | return Err(VerificationFailureCause::MissingReceiver.into()); |
64 | | } |
65 | | |
66 | 14 | let inputs13 = transfer_fn.decode_input(transaction.input()).map_err(|e| { |
67 | 1 | log::error!("failed to decode inputs: {:?}", e); |
68 | 1 | VerificationFailureCause::AbiMismatch |
69 | 14 | })?1 ; |
70 | | |
71 | | // IncorrectInputLength and IncorrectInputType are unreachable |
72 | | // under normal circumstances. We get AbiMismatch or InvalidData errors |
73 | 13 | ensure!( |
74 | 13 | inputs.len() == transfer_fn.inputs.len(), |
75 | 0 | VerificationFailureCause::IncorrectInputLength |
76 | | ); |
77 | | |
78 | 13 | let input_from = match inputs.get(0) { |
79 | 13 | Some(Token::Address(addr)) => addr, |
80 | 0 | _ => return Err(VerificationFailureCause::IncorrectInputType.into()), |
81 | | }; |
82 | 13 | ensure!(input_from == from, VerificationFailureCause::IncorrectSender1 ); |
83 | | |
84 | 12 | let input_to = match inputs.get(1) { |
85 | 12 | Some(Token::Address(addr)) => addr, |
86 | 0 | _ => return Err(VerificationFailureCause::IncorrectInputType.into()), |
87 | | }; |
88 | 12 | ensure!(input_to == to, VerificationFailureCause::IncorrectReceiver1 ); |
89 | | |
90 | 11 | let input_amount = match inputs.get(2) { |
91 | 11 | Some(Token::Uint(value)) => ExternalAmount::from(value), |
92 | 0 | _ => return Err(VerificationFailureCause::IncorrectInputType.into()), |
93 | | }; |
94 | 11 | ensure!(&input_amount == amount, VerificationFailureCause::IncorrectAmount1 ); |
95 | | |
96 | 10 | let nonce = match inputs.get(4) { |
97 | 10 | Some(Token::Uint(value)) => ExternalAmount::from(value), |
98 | 0 | _ => return Err(VerificationFailureCause::IncorrectInputType.into()), |
99 | | }; |
100 | 10 | let expected_nonce = id_hash.into_uint(); |
101 | 10 | ensure!(nonce == expected_nonce, VerificationFailureCause::IncorrectNonce3 ); |
102 | | |
103 | 7 | Ok(()) |
104 | 22 | } |
105 | | |
106 | 3 | fn verify_chain_id(rpc_url: &str, expected: EvmChainId) -> VerificationResult<()> { |
107 | 3 | let id = rpc::eth_chain_id(rpc_url)?0 .as_u64(); |
108 | 3 | if id == expected.as_u64() { |
109 | 3 | Ok(()) |
110 | | } else { |
111 | 0 | Err(OffchainError::IncorrectChainId) |
112 | | } |
113 | 3 | } |
114 | | |
115 | | impl<T: Config> crate::Pallet<T> { |
116 | 20 | pub fn verify_transfer_ocw( |
117 | 20 | transfer: &UnverifiedTransfer<T::AccountId, BlockNumberFor<T>, T::Hash, T::Moment>, |
118 | 20 | ) -> VerificationResult<Option<T::Moment>> { |
119 | 20 | let UnverifiedTransfer { |
120 | 20 | transfer: Transfer { blockchain, deal_order_id, amount, tx_id: tx, .. }, |
121 | 20 | from_external: from, |
122 | 20 | to_external: to, |
123 | 20 | currency_to_check, |
124 | 20 | .. |
125 | 20 | } = transfer; |
126 | 20 | log::debug!("verifying OCW transfer"); |
127 | 20 | match currency_to_check { |
128 | 17 | crate::CurrencyOrLegacyTransferKind::TransferKind(kind) => match kind { |
129 | 14 | LegacyTransferKind::Ethless(contract) => Self::verify_ethless_transfer( |
130 | 14 | blockchain, |
131 | 14 | contract, |
132 | 14 | from, |
133 | 14 | to, |
134 | 14 | deal_order_id, |
135 | 14 | amount, |
136 | 14 | tx, |
137 | 14 | None, |
138 | 14 | ), |
139 | | LegacyTransferKind::Native |
140 | | | LegacyTransferKind::Erc20(_) |
141 | 3 | | LegacyTransferKind::Other(_) => Err(VerificationFailureCause::UnsupportedMethod.into()), |
142 | | }, |
143 | 3 | crate::CurrencyOrLegacyTransferKind::Currency(currency) => match currency { |
144 | 3 | Currency::Evm(currency_type, EvmInfo { chain_id }) => match currency_type { |
145 | 3 | crate::EvmCurrencyType::SmartContract(contract, _) => { |
146 | 3 | Self::verify_ethless_transfer( |
147 | 3 | blockchain, |
148 | 3 | contract, |
149 | 3 | from, |
150 | 3 | to, |
151 | 3 | deal_order_id, |
152 | 3 | amount, |
153 | 3 | tx, |
154 | 3 | Some(*chain_id), |
155 | 3 | ) |
156 | | }, |
157 | | }, |
158 | | }, |
159 | | } |
160 | 20 | } |
161 | | |
162 | 18 | pub fn verify_ethless_transfer( |
163 | 18 | blockchain: &Blockchain, |
164 | 18 | contract_address: &ExternalAddress, |
165 | 18 | from: &ExternalAddress, |
166 | 18 | to: &ExternalAddress, |
167 | 18 | deal_order_id: &DealOrderId<BlockNumberFor<T>, T::Hash>, |
168 | 18 | amount: &ExternalAmount, |
169 | 18 | tx_id: &ExternalTxId, |
170 | 18 | chain_id: Option<EvmChainId>, |
171 | 18 | ) -> VerificationResult<Option<T::Moment>> { |
172 | 18 | let rpc_url17 = blockchain.rpc_url()?1 ; |
173 | | |
174 | 17 | if let Some(chain_id3 ) = chain_id { |
175 | 3 | verify_chain_id(&rpc_url, chain_id)?0 ; |
176 | 14 | } |
177 | | |
178 | 17 | let tx15 = ocw::eth_get_transaction(tx_id, &rpc_url)?2 ; |
179 | 15 | let tx_receipt14 = rpc::eth_get_transaction_receipt(tx_id, &rpc_url)?1 ; |
180 | 14 | let eth_tip13 = rpc::eth_get_block_number(&rpc_url)?1 ; |
181 | | |
182 | 13 | let tx_block_num = tx.block_number; |
183 | | |
184 | 13 | let from_addr12 = parse_eth_address(from)?1 ; |
185 | 12 | let to_addr11 = parse_eth_address(to)?1 ; |
186 | | |
187 | 11 | let ethless_contract10 = parse_eth_address(contract_address)?1 ; |
188 | | |
189 | 10 | validate_ethless_transfer( |
190 | 10 | &from_addr, |
191 | 10 | &to_addr, |
192 | 10 | ðless_contract, |
193 | 10 | amount, |
194 | 10 | &tx_receipt, |
195 | 10 | &tx, |
196 | 10 | eth_tip, |
197 | 10 | T::HashIntoNonce::from(deal_order_id.hash()), |
198 | 10 | )?4 ; |
199 | | |
200 | 6 | let timestamp = if let Some(num) = tx_block_num { |
201 | 5 | if let Ok(EthBlock { timestamp: block_timestamp }) = |
202 | 6 | rpc::eth_get_block_by_number(num, &rpc_url) |
203 | | { |
204 | 5 | Some(T::Moment::unique_saturated_from(block_timestamp.as_u64())) |
205 | | } else { |
206 | 1 | None |
207 | | } |
208 | | } else { |
209 | 0 | None |
210 | | }; |
211 | | |
212 | 6 | Ok(timestamp) |
213 | 18 | } |
214 | | } |