/home/runner/work/creditcoin/creditcoin/pallets/creditcoin/src/ocw/tasks/collect_coins.rs
Line | Count | Source (jump to first uncovered line) |
1 | | use crate::pallet::{Config as CreditcoinConfig, Pallet}; |
2 | | use crate::{ |
3 | | ocw::{ |
4 | | self, |
5 | | errors::{VerificationFailureCause, VerificationResult}, |
6 | | rpc::{self, EthTransaction, EthTransactionReceipt}, |
7 | | OffchainResult, ETH_CONFIRMATIONS, |
8 | | }, |
9 | | Blockchain, |
10 | | }; |
11 | | use crate::{types::UnverifiedCollectedCoins, ExternalAddress, ExternalAmount}; |
12 | | use codec::{Decode, Encode, MaxEncodedLen}; |
13 | | use core::default::Default; |
14 | | use ethabi::{Function, Param, ParamType, StateMutability, Token}; |
15 | | use ethereum_types::U64; |
16 | | use frame_support::{ensure, RuntimeDebug}; |
17 | | use hex_literal::hex; |
18 | | use scale_info::TypeInfo; |
19 | | use sp_core::H160; |
20 | | use sp_runtime::SaturatedConversion; |
21 | | #[cfg_attr(feature = "std", allow(unused_imports))] |
22 | | use sp_std::prelude::*; |
23 | | |
24 | 25 | #[derive(Clone19 , Encode, D24 ecode24 , Eq, P22 artialEq22 , RuntimeDebug5 , TypeInfo0 , MaxEncodedLen3 )] |
25 | 0 | pub struct GCreContract { |
26 | | pub address: sp_core::H160, |
27 | | pub chain: Blockchain, |
28 | | } |
29 | | |
30 | | impl GCreContract { |
31 | | const DEFAULT_CHAIN: Blockchain = Blockchain::ETHEREUM; |
32 | | } |
33 | | |
34 | | impl Default for GCreContract { |
35 | 54 | fn default() -> Self { |
36 | 54 | let contract_chain: Blockchain = GCreContract::DEFAULT_CHAIN; |
37 | 54 | let contract_address: H160 = |
38 | 54 | sp_core::H160(hex!("a3EE21C306A700E682AbCdfe9BaA6A08F3820419")); |
39 | 54 | Self { address: contract_address, chain: contract_chain } |
40 | 54 | } |
41 | | } |
42 | | |
43 | | impl GCreContract { |
44 | | ///exchange has been deprecated, use burn instead |
45 | 11 | fn burn_vested_cc_abi() -> Function { |
46 | 11 | #[allow(deprecated)] |
47 | 11 | Function { |
48 | 11 | name: "burn".into(), |
49 | 11 | inputs: vec![Param { |
50 | 11 | name: "value".into(), |
51 | 11 | kind: ParamType::Uint(256), |
52 | 11 | internal_type: None, |
53 | 11 | }], |
54 | 11 | outputs: vec![Param { |
55 | 11 | name: "success".into(), |
56 | 11 | kind: ParamType::Bool, |
57 | 11 | internal_type: None, |
58 | 11 | }], |
59 | 11 | constant: false, |
60 | 11 | state_mutability: StateMutability::NonPayable, |
61 | 11 | } |
62 | 11 | } |
63 | | } |
64 | | |
65 | 19 | pub fn validate_collect_coins( |
66 | 19 | to: &ExternalAddress, |
67 | 19 | receipt: &EthTransactionReceipt, |
68 | 19 | transaction: &EthTransaction, |
69 | 19 | eth_tip: U64, |
70 | 19 | contract_address: &H160, |
71 | 19 | ) -> OffchainResult<ExternalAmount> { |
72 | 19 | ensure!(receipt.is_success(), VerificationFailureCause::TaskFailed1 ); |
73 | | |
74 | 18 | let block_number17 = transaction.block_number.ok_or(VerificationFailureCause::TaskPending)?1 ; |
75 | | |
76 | 17 | let diff16 = (eth_tip) |
77 | 17 | .checked_sub(block_number) |
78 | 17 | .ok_or(VerificationFailureCause::TaskInFuture)?1 ; |
79 | 16 | ensure!(diff.as_u64() >= ETH_CONFIRMATIONS, VerificationFailureCause::TaskUnconfirmed1 ); |
80 | | |
81 | 15 | if let Some(to14 ) = &transaction.to { |
82 | 14 | ensure!(to == contract_address, VerificationFailureCause::IncorrectContract1 ); |
83 | | } else { |
84 | 1 | return Err(VerificationFailureCause::MissingReceiver.into()); |
85 | | } |
86 | | |
87 | 13 | if let Some(from12 ) = &transaction.from { |
88 | 12 | ensure!(from[..] == to[..], VerificationFailureCause::IncorrectSender2 ) |
89 | | } else { |
90 | 1 | return Err(VerificationFailureCause::MissingSender.into()); |
91 | | } |
92 | | |
93 | 10 | let transfer_fn = GCreContract::burn_vested_cc_abi(); |
94 | 10 | ensure!(!transaction.is_input_empty(), VerificationFailureCause::EmptyInput1 ); |
95 | | |
96 | | { |
97 | 9 | let selector = transaction.selector(); |
98 | 9 | if selector != transfer_fn.short_signature() { |
99 | 1 | log::error!( |
100 | 1 | "function selector mismatch, expected: {}, got: {}", |
101 | 1 | hex::encode(&transfer_fn.short_signature()), |
102 | 1 | hex::encode(selector) |
103 | | ); |
104 | 1 | return Err(VerificationFailureCause::AbiMismatch.into()); |
105 | 8 | } |
106 | | } |
107 | | |
108 | 8 | let inputs = transfer_fn.decode_input(transaction.input()).map_err(|e| { |
109 | 0 | log::error!("failed to decode inputs: {:?}", e); |
110 | 0 | VerificationFailureCause::AbiMismatch |
111 | 8 | })?0 ; |
112 | | |
113 | 8 | match inputs.get(0) { |
114 | 8 | Some(Token::Uint(value)) => Ok(ExternalAmount::from(value)), |
115 | 0 | _ => Err(VerificationFailureCause::IncorrectInputType.into()), |
116 | | } |
117 | 19 | } |
118 | | |
119 | | impl<T: CreditcoinConfig> Pallet<T> { |
120 | | ///Amount is saturated to u128, don't exchange more than u128::MAX at once. |
121 | 8 | pub fn verify_collect_coins_ocw( |
122 | 8 | u_cc: &UnverifiedCollectedCoins, |
123 | 8 | ) -> VerificationResult<T::Balance> { |
124 | 8 | log::debug!("verifying OCW Collect Coins"); |
125 | 8 | let UnverifiedCollectedCoins { to, tx_id, contract: GCreContract { address, chain } } = |
126 | | u_cc; |
127 | 8 | let rpc_url7 = &chain.rpc_url()?1 ; |
128 | 7 | let tx6 = ocw::eth_get_transaction(tx_id, rpc_url)?1 ; |
129 | 6 | let tx_receipt = rpc::eth_get_transaction_receipt(tx_id, rpc_url)?0 ; |
130 | 6 | let eth_tip = rpc::eth_get_block_number(rpc_url)?0 ; |
131 | | |
132 | 6 | let amount5 = validate_collect_coins(to, &tx_receipt, &tx, eth_tip, address)?1 ; |
133 | | |
134 | 5 | let amount = amount.saturated_into::<u128>().saturated_into::<T::Balance>(); |
135 | 5 | |
136 | 5 | Ok(amount) |
137 | 8 | } |
138 | | } |
139 | | |
140 | | #[cfg(any(test, feature = "runtime-benchmarks"))] |
141 | | pub(crate) mod testing_constants { |
142 | | use super::{Blockchain, GCreContract}; |
143 | | |
144 | | pub const CHAIN: Blockchain = GCreContract::DEFAULT_CHAIN; |
145 | | } |
146 | | |
147 | | #[cfg(test)] |
148 | | pub(crate) mod tests { |
149 | | |
150 | | use super::*; |
151 | | use crate::mock::PendingRequestExt; |
152 | | use crate::TaskId; |
153 | | use std::collections::HashMap; |
154 | | |
155 | | // txn.from has been overriden by 'generate_address_with_proof("collector")' |
156 | 1 | static RESPONSES: Lazy<HashMap<String, JsonRpcResponse<serde_json::Value>>> = Lazy::new(|| { |
157 | 1 | serde_json::from_slice(include_bytes!("../../tests/collectCoins.json")).unwrap() |
158 | 1 | }); |
159 | | |
160 | 1 | static BLOCK_NUMBER: Lazy<U64> = Lazy::new(|| { |
161 | 1 | let responses = &*RESPONSES; |
162 | 1 | let bn = |
163 | 1 | responses["eth_getTransactionByHash"].result.clone().unwrap()["blockNumber"].clone(); |
164 | 1 | serde_json::from_value(bn).unwrap() |
165 | 1 | }); |
166 | | |
167 | 1 | static BLOCK_NUMBER_STR: Lazy<String> = Lazy::new(|| { |
168 | 1 | let responses = &*RESPONSES; |
169 | 1 | let bn = |
170 | 1 | responses["eth_getTransactionByHash"].result.clone().unwrap()["blockNumber"].clone(); |
171 | 1 | serde_json::from_value(bn).unwrap() |
172 | 1 | }); |
173 | | |
174 | 1 | static VESTING_CONTRACT: Lazy<H160> = Lazy::new(|| { |
175 | 1 | let responses = &*RESPONSES; |
176 | 1 | let val = responses["eth_getTransactionByHash"].result.clone().unwrap()["to"].clone(); |
177 | 1 | let val: String = serde_json::from_value(val).unwrap(); |
178 | 1 | let vesting_contract = hex::decode(val.trim_start_matches("0x")).unwrap(); |
179 | 1 | H160::from(<[u8; 20]>::try_from(vesting_contract.as_slice()).unwrap()) |
180 | 1 | }); |
181 | | |
182 | | // txn.from has been overriden by 'generate_address_with_proof("collector")' |
183 | 1 | static FROM: Lazy<String> = Lazy::new(|| { |
184 | 1 | let responses = &*RESPONSES; |
185 | 1 | let val = responses["eth_getTransactionByHash"].result.clone().unwrap()["from"].clone(); |
186 | 1 | serde_json::from_value(val).unwrap() |
187 | 1 | }); |
188 | | |
189 | 1 | static INPUT: Lazy<rpc::Bytes> = Lazy::new(|| { |
190 | 1 | let responses = &*RESPONSES; |
191 | 1 | let val = responses["eth_getTransactionByHash"].result.clone().unwrap()["input"].clone(); |
192 | 1 | let val: String = serde_json::from_value(val).unwrap(); |
193 | 1 | let input_bytes = hex::decode(val.trim_start_matches("0x")).unwrap(); |
194 | 1 | input_bytes.into() |
195 | 1 | }); |
196 | | |
197 | 1 | pub(crate) static TX_HASH: Lazy<String> = Lazy::new(|| { |
198 | 1 | let responses = &*RESPONSES; |
199 | 1 | let val = responses["eth_getTransactionByHash"].result.clone().unwrap()["hash"].clone(); |
200 | 1 | serde_json::from_value(val).unwrap() |
201 | 1 | }); |
202 | | |
203 | 1 | pub(crate) static RPC_RESPONSE_AMOUNT: Lazy<sp_core::U256> = Lazy::new(|| { |
204 | 1 | let transfer_fn = GCreContract::burn_vested_cc_abi(); |
205 | 1 | |
206 | 1 | let inputs = transfer_fn.decode_input(&(INPUT.0)[4..]).unwrap(); |
207 | 1 | |
208 | 1 | let amount = inputs.get(0).unwrap(); |
209 | 1 | if let Token::Uint(value) = amount { |
210 | 1 | ExternalAmount::from(value) |
211 | | } else { |
212 | 0 | panic!("Not Token::Uint"); |
213 | | } |
214 | | }); |
215 | | |
216 | | use crate::helpers::non_paying_error; |
217 | | use crate::helpers::HexToAddress; |
218 | | use crate::mock::{ |
219 | | roll_by_with_ocw, set_rpc_uri, AccountId, Balances, ExtBuilder, MockedRpcRequests, |
220 | | OffchainState, Origin, RwLock, Test, |
221 | | }; |
222 | | use crate::ocw::{ |
223 | | errors::{OffchainError, VerificationFailureCause as Cause}, |
224 | | rpc::{EthTransaction, EthTransactionReceipt}, |
225 | | ETH_CONFIRMATIONS, |
226 | | }; |
227 | | use crate::tests::generate_address_with_proof; |
228 | | use crate::types::{AddressId, CollectedCoins, CollectedCoinsId}; |
229 | | use crate::Pallet as Creditcoin; |
230 | | use crate::{ocw::rpc::JsonRpcResponse, ExternalAddress}; |
231 | | use alloc::sync::Arc; |
232 | | use assert_matches::assert_matches; |
233 | | use codec::Decode; |
234 | | use frame_support::{assert_noop, assert_ok, once_cell::sync::Lazy, traits::Currency}; |
235 | | use frame_system::Pallet as System; |
236 | | use frame_system::RawOrigin; |
237 | | use sp_runtime::traits::{BadOrigin, IdentifyAccount}; |
238 | | use sp_runtime::{ArithmeticError, TokenError}; |
239 | | |
240 | | use std::convert::TryFrom; |
241 | | |
242 | | use super::testing_constants::CHAIN; |
243 | | |
244 | 8 | fn prepare_rpc_mocks() -> MockedRpcRequests { |
245 | 8 | let dummy_url = "dummy"; |
246 | 8 | let contract_chain = Creditcoin::<Test>::collect_coins_contract(); |
247 | 8 | set_rpc_uri(&contract_chain.chain, &dummy_url); |
248 | 8 | |
249 | 8 | MockedRpcRequests::new(dummy_url, &TX_HASH, &BLOCK_NUMBER_STR, &RESPONSES) |
250 | 8 | } |
251 | | |
252 | | /// call from externalities context |
253 | 7 | pub(crate) fn mock_rpc_for_collect_coins(state: &Arc<RwLock<OffchainState>>) { |
254 | 7 | let mut rpcs = prepare_rpc_mocks(); |
255 | 7 | rpcs.mock_get_block_number(&mut state.write()); |
256 | 7 | } |
257 | | |
258 | | struct PassingCollectCoins { |
259 | | to: ExternalAddress, |
260 | | receipt: EthTransactionReceipt, |
261 | | transaction: EthTransaction, |
262 | | eth_tip: U64, |
263 | | contract_address: H160, |
264 | | } |
265 | | |
266 | | impl Default for PassingCollectCoins { |
267 | 16 | fn default() -> Self { |
268 | 16 | let base_height = *BLOCK_NUMBER; |
269 | 16 | let vesting_contract = *VESTING_CONTRACT; |
270 | 16 | let to = FROM.hex_to_address(); |
271 | 16 | let tx_from = H160::from(<[u8; 20]>::try_from(to.as_slice()).unwrap()); |
272 | 16 | |
273 | 16 | let mut transaction = EthTransaction::default(); |
274 | 16 | transaction.block_number = Some(base_height); |
275 | 16 | transaction.from = Some(tx_from); |
276 | 16 | transaction.to = Some(vesting_contract); |
277 | 16 | transaction.set_input(&INPUT.0); |
278 | 16 | |
279 | 16 | Self { |
280 | 16 | to, |
281 | 16 | receipt: EthTransactionReceipt { status: Some(1u64.into()), ..Default::default() }, |
282 | 16 | transaction, |
283 | 16 | eth_tip: (base_height + ETH_CONFIRMATIONS), |
284 | 16 | contract_address: GCreContract::default().address, |
285 | 16 | } |
286 | 16 | } |
287 | | } |
288 | | |
289 | | impl PassingCollectCoins { |
290 | 10 | fn validate(self) -> OffchainResult<ExternalAmount> { |
291 | 10 | let PassingCollectCoins { to, receipt, transaction, eth_tip, contract_address } = self; |
292 | 10 | super::validate_collect_coins(&to, &receipt, &transaction, eth_tip, &contract_address) |
293 | 10 | } |
294 | | } |
295 | | |
296 | 9 | fn assert_invalid(res: OffchainResult<ExternalAmount>, cause: VerificationFailureCause) { |
297 | 9 | assert_matches!0 (res, Err(OffchainError::InvalidTask(c)) =>{ assert_eq!(c,cause); }); |
298 | 9 | } |
299 | | |
300 | 1 | #[test] |
301 | 1 | fn valid() { |
302 | 1 | assert_matches!0 (PassingCollectCoins::default().validate(), Ok(_)); |
303 | 1 | } |
304 | | |
305 | 1 | #[test] |
306 | 1 | fn txn_success() { |
307 | 1 | let mut pcc = PassingCollectCoins::default(); |
308 | 1 | pcc.receipt.status = Some(0u64.into()); |
309 | 1 | assert_invalid(pcc.validate(), Cause::TaskFailed); |
310 | 1 | } |
311 | | |
312 | 1 | #[test] |
313 | 1 | fn pending() { |
314 | 1 | let mut transaction = EthTransaction::default(); |
315 | 1 | transaction.block_number = None; |
316 | 1 | let pcc = PassingCollectCoins { transaction, ..Default::default() }; |
317 | 1 | assert_invalid(pcc.validate(), Cause::TaskPending); |
318 | 1 | } |
319 | | |
320 | 1 | #[test] |
321 | 1 | fn in_the_future() { |
322 | 1 | let pcc = PassingCollectCoins { eth_tip: 0u64.into(), ..Default::default() }; |
323 | 1 | assert_invalid(pcc.validate(), Cause::TaskInFuture); |
324 | 1 | } |
325 | | |
326 | 1 | #[test] |
327 | 1 | fn unconfirmed() { |
328 | 1 | let mut pcc = PassingCollectCoins::default(); |
329 | 1 | pcc.eth_tip = pcc.transaction.block_number.unwrap(); |
330 | 1 | assert_invalid(pcc.validate(), Cause::TaskUnconfirmed); |
331 | 1 | } |
332 | | |
333 | 1 | #[test] |
334 | 1 | fn missing_receiver() { |
335 | 1 | let mut pcc = PassingCollectCoins::default(); |
336 | 1 | pcc.transaction.to = None; |
337 | 1 | assert_invalid(pcc.validate(), Cause::MissingReceiver); |
338 | 1 | } |
339 | | |
340 | 1 | #[test] |
341 | 1 | fn incorrect_contract() { |
342 | 1 | let mut pcc = PassingCollectCoins::default(); |
343 | 1 | let address = [0u8; 20]; |
344 | 1 | let address = H160::from(<[u8; 20]>::try_from(address.as_slice()).unwrap()); |
345 | 1 | pcc.transaction.to = Some(address); |
346 | 1 | assert_invalid(pcc.validate(), Cause::IncorrectContract); |
347 | 1 | } |
348 | | |
349 | 1 | #[test] |
350 | 1 | fn missing_sender() { |
351 | 1 | let mut pcc = PassingCollectCoins::default(); |
352 | 1 | pcc.transaction.from = None; |
353 | 1 | assert_invalid(pcc.validate(), Cause::MissingSender); |
354 | 1 | } |
355 | | |
356 | 1 | #[test] |
357 | 1 | fn incorrect_sender() { |
358 | 1 | let mut pcc = PassingCollectCoins::default(); |
359 | 1 | let address = [0u8; 20]; |
360 | 1 | let address = H160::from(<[u8; 20]>::try_from(address.as_slice()).unwrap()); |
361 | 1 | pcc.transaction.from = Some(address); |
362 | 1 | assert_invalid(pcc.validate(), Cause::IncorrectSender); |
363 | 1 | } |
364 | | |
365 | 1 | #[test] |
366 | 1 | fn empty_input() { |
367 | 1 | let mut pcc = PassingCollectCoins::default(); |
368 | 1 | pcc.transaction.set_input(b""); |
369 | 1 | assert_invalid(pcc.validate(), Cause::EmptyInput); |
370 | 1 | } |
371 | | |
372 | 1 | #[test] |
373 | 1 | fn amount_set() -> OffchainResult<()> { |
374 | 1 | let pcc = PassingCollectCoins::default(); |
375 | 1 | let PassingCollectCoins { to, receipt, transaction, eth_tip, contract_address } = pcc; |
376 | 1 | let amount = |
377 | 1 | super::validate_collect_coins(&to, &receipt, &transaction, eth_tip, &contract_address)?0 ; |
378 | 1 | assert_eq!(amount, *RPC_RESPONSE_AMOUNT); |
379 | 1 | Ok(()) |
380 | 1 | } |
381 | | |
382 | 1 | #[test] |
383 | 1 | fn fail_collect_coins_should_error_when_not_signed() { |
384 | 1 | let ext = ExtBuilder::default(); |
385 | 1 | let expected_collected_coins_id = |
386 | 1 | crate::CollectedCoinsId::new::<crate::mock::Test>(&CHAIN, &[0]); |
387 | 1 | |
388 | 1 | ext.build_offchain_and_execute_with_state(|_state, _pool| { |
389 | 1 | assert_noop!( |
390 | 1 | Creditcoin::<Test>::fail_task( |
391 | 1 | Origin::none(), |
392 | 1 | Test::unverified_transfer_deadline(), |
393 | 1 | expected_collected_coins_id.clone().into(), |
394 | 1 | Cause::AbiMismatch, |
395 | 1 | ), |
396 | 1 | BadOrigin |
397 | 1 | ); |
398 | 1 | }); |
399 | 1 | } |
400 | | |
401 | 1 | #[test] |
402 | 1 | fn fail_collect_coins_should_error_when_no_authority() { |
403 | 1 | let ext = ExtBuilder::default(); |
404 | 1 | let (molly, _, _, _) = generate_address_with_proof("malicious"); |
405 | 1 | let expected_collected_coins_id = |
406 | 1 | crate::CollectedCoinsId::new::<crate::mock::Test>(&CHAIN, &[0]); |
407 | 1 | |
408 | 1 | ext.build_offchain_and_execute_with_state(|_state, _pool| { |
409 | 1 | assert_noop!( |
410 | 1 | Creditcoin::<Test>::fail_task( |
411 | 1 | Origin::signed(molly), |
412 | 1 | Test::unverified_transfer_deadline(), |
413 | 1 | expected_collected_coins_id.clone().into(), |
414 | 1 | Cause::AbiMismatch, |
415 | 1 | ), |
416 | 1 | crate::Error::<Test>::InsufficientAuthority |
417 | 1 | ); |
418 | 1 | }); |
419 | 1 | } |
420 | | |
421 | 1 | #[test] |
422 | 1 | fn fail_collect_coins_should_fail_when_transfer_has_already_been_registered() { |
423 | 1 | let mut ext = ExtBuilder::default(); |
424 | 1 | let acct_pubkey = ext.generate_authority(); |
425 | 1 | let auth = AccountId::from(acct_pubkey.into_account().0); |
426 | 1 | |
427 | 1 | ext.build_offchain_and_execute_with_state(|_state, _pool| { |
428 | 1 | System::<Test>::set_block_number(1); |
429 | 1 | |
430 | 1 | let (acc, addr, sign, _) = generate_address_with_proof("collector"); |
431 | 1 | |
432 | 1 | assert_ok!(Creditcoin::<Test>::register_address( |
433 | 1 | Origin::signed(acc), |
434 | 1 | CHAIN, |
435 | 1 | addr, |
436 | 1 | sign |
437 | 1 | )); |
438 | | |
439 | 1 | let deadline = Test::unverified_transfer_deadline(); |
440 | 1 | |
441 | 1 | let pcc = PassingCollectCoins::default(); |
442 | 1 | |
443 | 1 | let collected_coins = CollectedCoins { |
444 | 1 | to: AddressId::new::<Test>(&CHAIN, &pcc.to[..]), |
445 | 1 | amount: RPC_RESPONSE_AMOUNT.as_u128(), |
446 | 1 | tx_id: TX_HASH.hex_to_address(), |
447 | 1 | }; |
448 | 1 | let collected_coins_id = |
449 | 1 | crate::CollectedCoinsId::new::<crate::mock::Test>(&CHAIN, &collected_coins.tx_id); |
450 | 1 | |
451 | 1 | assert_ok!(Creditcoin::<Test>::persist_task_output( |
452 | 1 | Origin::signed(auth.clone()), |
453 | 1 | deadline, |
454 | 1 | (collected_coins_id.clone(), collected_coins).into(), |
455 | 1 | )); |
456 | | |
457 | 1 | assert_noop!( |
458 | 1 | Creditcoin::<Test>::fail_task( |
459 | 1 | Origin::signed(auth), |
460 | 1 | Test::unverified_transfer_deadline(), |
461 | 1 | collected_coins_id.into(), |
462 | 1 | Cause::AbiMismatch, |
463 | 1 | ), |
464 | 1 | crate::Error::<Test>::CollectCoinsAlreadyRegistered |
465 | 1 | ); |
466 | 1 | }); |
467 | 1 | } |
468 | | |
469 | 1 | #[test] |
470 | 1 | fn fail_collect_coins_emits_events() { |
471 | 1 | let mut ext = ExtBuilder::default(); |
472 | 1 | let acct_pubkey = ext.generate_authority(); |
473 | 1 | let auth = AccountId::from(acct_pubkey.into_account().0); |
474 | 1 | let expected_collected_coins_id = |
475 | 1 | crate::CollectedCoinsId::new::<crate::mock::Test>(&CHAIN, &[0]); |
476 | 1 | |
477 | 1 | ext.build_offchain_and_execute_with_state(|_state, _pool| { |
478 | 1 | System::<Test>::set_block_number(1); |
479 | 1 | |
480 | 1 | assert_ok!(Creditcoin::<Test>::fail_task( |
481 | 1 | Origin::signed(auth), |
482 | 1 | Test::unverified_transfer_deadline(), |
483 | 1 | expected_collected_coins_id.clone().into(), |
484 | 1 | Cause::AbiMismatch, |
485 | 1 | )); |
486 | | |
487 | 1 | let event = System::<Test>::events().pop().expect("an event").event; |
488 | 0 | assert_matches!( |
489 | 1 | event, |
490 | 1 | crate::mock::Event::Creditcoin(crate::Event::<Test>::CollectCoinsFailedVerification(collected_coins_id, cause)) => { |
491 | 1 | assert_eq!(collected_coins_id, expected_collected_coins_id); |
492 | 1 | assert_eq!(cause, Cause::AbiMismatch); |
493 | | } |
494 | | ); |
495 | 1 | }); |
496 | 1 | } |
497 | | |
498 | 1 | #[test] |
499 | 1 | fn ocw_fail_collect_coins_works() { |
500 | 1 | let mut ext = ExtBuilder::default(); |
501 | 1 | let acct_pubkey = ext.generate_authority(); |
502 | 1 | let acct = AccountId::from(acct_pubkey.into_account().0); |
503 | 1 | let expected_collected_coins_id = |
504 | 1 | crate::CollectedCoinsId::new::<crate::mock::Test>(&CHAIN, &[0]); |
505 | 1 | ext.build_offchain_and_execute_with_state(|_state, pool| { |
506 | 1 | crate::mock::roll_to(1); |
507 | 1 | let call = crate::Call::<crate::mock::Test>::fail_task { |
508 | 1 | task_id: expected_collected_coins_id.into(), |
509 | 1 | cause: Cause::AbiMismatch, |
510 | 1 | deadline: Test::unverified_transfer_deadline(), |
511 | 1 | }; |
512 | 1 | assert_ok!(crate::Pallet::<crate::mock::Test>::offchain_signed_tx( |
513 | 1 | acct.clone(), |
514 | 1 | |_| call.clone(), |
515 | 1 | )); |
516 | 1 | crate::mock::roll_to(2); |
517 | | |
518 | 1 | assert_matches!0 (pool.write().transactions.pop(), Some(tx) => { |
519 | 1 | let tx = crate::mock::Extrinsic::decode(&mut &*tx).unwrap(); |
520 | 1 | assert_eq!(tx.call, crate::mock::Call::Creditcoin(call)); |
521 | | }); |
522 | 1 | }); |
523 | 1 | } |
524 | | |
525 | 1 | #[test] |
526 | 1 | fn persist_collect_coins() { |
527 | 1 | let mut ext = ExtBuilder::default(); |
528 | 1 | let acct_pubkey = ext.generate_authority(); |
529 | 1 | let auth = AccountId::from(acct_pubkey.into_account().0); |
530 | 1 | ext.build_offchain_and_execute_with_state(|_, _| { |
531 | 1 | System::<Test>::set_block_number(1); |
532 | 1 | |
533 | 1 | let (acc, addr, sign, _) = generate_address_with_proof("collector"); |
534 | 1 | |
535 | 1 | assert_ok!(Creditcoin::<Test>::register_address( |
536 | 1 | Origin::signed(acc.clone()), |
537 | 1 | CHAIN, |
538 | 1 | addr, |
539 | 1 | sign |
540 | 1 | )); |
541 | | |
542 | 1 | let deadline = Test::unverified_transfer_deadline(); |
543 | 1 | |
544 | 1 | let pcc = PassingCollectCoins::default(); |
545 | 1 | |
546 | 1 | let collected_coins = CollectedCoins { |
547 | 1 | to: AddressId::new::<Test>(&CHAIN, &pcc.to[..]), |
548 | 1 | amount: RPC_RESPONSE_AMOUNT.as_u128(), |
549 | 1 | tx_id: TX_HASH.hex_to_address(), |
550 | 1 | }; |
551 | 1 | |
552 | 1 | let collected_coins_id = CollectedCoinsId::new::<Test>(&CHAIN, &collected_coins.tx_id); |
553 | 1 | |
554 | 1 | let balance = <Balances as Currency<AccountId>>::total_balance; |
555 | 1 | |
556 | 1 | let pre_authority_balance = balance(&auth); |
557 | 1 | let pre_collector_balance = balance(&acc); |
558 | 1 | |
559 | 1 | assert_ok!(Creditcoin::<Test>::persist_task_output( |
560 | 1 | Origin::signed(auth.clone()), |
561 | 1 | deadline, |
562 | 1 | (collected_coins_id.clone(), collected_coins.clone()).into(), |
563 | 1 | )); |
564 | | |
565 | 1 | let event = <frame_system::Pallet<Test>>::events().pop().expect("an event").event; |
566 | | |
567 | 0 | assert_matches!( |
568 | 1 | event, |
569 | 1 | crate::mock::Event::Creditcoin(crate::Event::<Test>::CollectedCoinsMinted(id, item)) => { |
570 | 1 | assert_eq!(id, collected_coins_id); |
571 | 1 | assert_eq!(item, collected_coins); |
572 | | } |
573 | | ); |
574 | | //do not mint into authority |
575 | 1 | assert_eq!(pre_authority_balance, balance(&auth)); |
576 | | // assert on deposit |
577 | 1 | assert_eq!(pre_collector_balance.saturating_add(collected_coins.amount), balance(&acc)); |
578 | 1 | }); |
579 | 1 | } |
580 | | |
581 | 1 | #[test] |
582 | 1 | fn persist_unregistered_address() { |
583 | 1 | let mut ext = ExtBuilder::default(); |
584 | 1 | let acct_pubkey = ext.generate_authority(); |
585 | 1 | let auth = AccountId::from(acct_pubkey.into_account().0); |
586 | 1 | ext.build_offchain_and_execute_with_state(|_, _| { |
587 | 1 | let pcc = PassingCollectCoins::default(); |
588 | 1 | |
589 | 1 | let collected_coins = CollectedCoins { |
590 | 1 | to: AddressId::new::<Test>(&CHAIN, &pcc.to[..]), |
591 | 1 | amount: RPC_RESPONSE_AMOUNT.as_u128(), |
592 | 1 | tx_id: TX_HASH.hex_to_address(), |
593 | 1 | }; |
594 | 1 | let collected_coins_id = CollectedCoinsId::new::<Test>(&CHAIN, &collected_coins.tx_id); |
595 | 1 | |
596 | 1 | let deadline = Test::unverified_transfer_deadline(); |
597 | 1 | |
598 | 1 | assert_noop!( |
599 | 1 | Creditcoin::<Test>::persist_task_output( |
600 | 1 | Origin::signed(auth), |
601 | 1 | deadline, |
602 | 1 | (collected_coins_id, collected_coins).into(), |
603 | 1 | ), |
604 | 1 | crate::Error::<Test>::NonExistentAddress |
605 | 1 | ); |
606 | 1 | }); |
607 | 1 | } |
608 | | |
609 | 1 | #[test] |
610 | 1 | fn persist_more_than_max_balance_should_error() { |
611 | 1 | let mut ext = ExtBuilder::default(); |
612 | 1 | let acct_pubkey = ext.generate_authority(); |
613 | 1 | let auth = AccountId::from(acct_pubkey.into_account().0); |
614 | 1 | ext.build_offchain_and_execute_with_state(|_, _| { |
615 | 1 | let (acc, addr, sign, _) = generate_address_with_proof("collector"); |
616 | 1 | assert_ok!(Creditcoin::<Test>::register_address( |
617 | 1 | Origin::signed(acc), |
618 | 1 | CHAIN, |
619 | 1 | addr, |
620 | 1 | sign |
621 | 1 | )); |
622 | | |
623 | 1 | let pcc = PassingCollectCoins::default(); |
624 | 1 | |
625 | 1 | // lower free balance so that collect coins would overflow |
626 | 1 | let cash = <crate::mock::Balances as Currency<AccountId>>::minimum_balance(); |
627 | 1 | <crate::mock::Balances as Currency<AccountId>>::make_free_balance_be(&auth, cash); |
628 | 1 | |
629 | 1 | let collected_coins_id = |
630 | 1 | crate::CollectedCoinsId::new::<Test>(&CHAIN, &TX_HASH.hex_to_address()); |
631 | 1 | let collected_coins = CollectedCoins { |
632 | 1 | to: AddressId::new::<Test>(&CHAIN, &pcc.to[..]), |
633 | 1 | amount: u128::MAX, |
634 | 1 | tx_id: TX_HASH.hex_to_address(), |
635 | 1 | }; |
636 | 1 | |
637 | 1 | assert_noop!( |
638 | 1 | Creditcoin::<Test>::persist_task_output( |
639 | 1 | Origin::signed(auth), |
640 | 1 | Test::unverified_transfer_deadline(), |
641 | 1 | (collected_coins_id, collected_coins).into(), |
642 | 1 | ), |
643 | 1 | ArithmeticError::Overflow |
644 | 1 | ); |
645 | 1 | }); |
646 | 1 | } |
647 | | |
648 | 1 | #[test] |
649 | 1 | fn request_persisted_not_reentrant() { |
650 | 1 | let mut ext = ExtBuilder::default(); |
651 | 1 | let acct_pubkey = ext.generate_authority(); |
652 | 1 | let auth = AccountId::from(acct_pubkey.into_account().0); |
653 | 1 | ext.build_offchain_and_execute_with_state(|_, _pool| { |
654 | 1 | let (acc, addr, sign, _) = generate_address_with_proof("collector"); |
655 | 1 | |
656 | 1 | assert_ok!(Creditcoin::<Test>::register_address( |
657 | 1 | Origin::signed(acc.clone()), |
658 | 1 | CHAIN, |
659 | 1 | addr.clone(), |
660 | 1 | sign |
661 | 1 | )); |
662 | | |
663 | 1 | let collected_coins = CollectedCoins { |
664 | 1 | to: AddressId::new::<Test>(&CHAIN, &addr[..]), |
665 | 1 | amount: RPC_RESPONSE_AMOUNT.as_u128(), |
666 | 1 | tx_id: TX_HASH.hex_to_address(), |
667 | 1 | }; |
668 | 1 | let collected_coins_id = CollectedCoinsId::new::<Test>(&CHAIN, &collected_coins.tx_id); |
669 | 1 | |
670 | 1 | assert_ok!(Creditcoin::<Test>::persist_task_output( |
671 | 1 | Origin::signed(auth), |
672 | 1 | Test::unverified_transfer_deadline(), |
673 | 1 | (collected_coins_id, collected_coins).into(), |
674 | 1 | )); |
675 | | |
676 | 1 | roll_by_with_ocw(1); |
677 | 1 | |
678 | 1 | assert_noop!( |
679 | 1 | Creditcoin::<Test>::request_collect_coins( |
680 | 1 | Origin::signed(acc), |
681 | 1 | addr, |
682 | 1 | TX_HASH.hex_to_address(), |
683 | 1 | ), |
684 | 1 | crate::Error::<Test>::CollectCoinsAlreadyRegistered |
685 | 1 | ); |
686 | 1 | }); |
687 | 1 | } |
688 | | |
689 | 1 | #[test] |
690 | 1 | fn request_pending_not_reentrant() { |
691 | 1 | let mut ext = ExtBuilder::default(); |
692 | 1 | ext.generate_authority(); |
693 | 1 | ext.build_offchain_and_execute_with_state(|_, _| { |
694 | 1 | System::<Test>::set_block_number(1); |
695 | 1 | |
696 | 1 | let (acc, addr, sign, _) = generate_address_with_proof("collector"); |
697 | 1 | |
698 | 1 | assert_ok!(Creditcoin::<Test>::register_address( |
699 | 1 | Origin::signed(acc.clone()), |
700 | 1 | CHAIN, |
701 | 1 | addr.clone(), |
702 | 1 | sign |
703 | 1 | )); |
704 | | |
705 | 1 | assert_ok!(Creditcoin::<Test>::request_collect_coins( |
706 | 1 | Origin::signed(acc.clone()), |
707 | 1 | addr.clone(), |
708 | 1 | TX_HASH.hex_to_address() |
709 | 1 | )); |
710 | | |
711 | 1 | let collected_coins_id = |
712 | 1 | CollectedCoinsId::new::<Test>(&CHAIN, TX_HASH.hex_to_address().as_slice()); |
713 | 1 | |
714 | 1 | let event = <frame_system::Pallet<Test>>::events().pop().expect("an event").event; |
715 | 0 | assert_matches!( |
716 | 1 | event, |
717 | 1 | crate::mock::Event::Creditcoin(crate::Event::<Test>::CollectCoinsRegistered(collect_coins_id, pending)) => { |
718 | 1 | assert_eq!(collect_coins_id, collected_coins_id); |
719 | | |
720 | 1 | let UnverifiedCollectedCoins { to, tx_id, .. } = pending; |
721 | 1 | assert_eq!(to, addr); |
722 | 1 | assert_eq!(tx_id, TX_HASH.hex_to_address()); |
723 | | } |
724 | | ); |
725 | | |
726 | 1 | assert!(Creditcoin::<Test>::pending_tasks( |
727 | 1 | Test::unverified_transfer_deadline(), |
728 | 1 | TaskId::from(collected_coins_id.clone()), |
729 | 1 | ) |
730 | 1 | .is_some()); |
731 | | |
732 | 1 | assert_noop!( |
733 | 1 | Creditcoin::<Test>::request_collect_coins( |
734 | 1 | Origin::signed(acc), |
735 | 1 | addr, |
736 | 1 | TX_HASH.hex_to_address(), |
737 | 1 | ), |
738 | 1 | crate::Error::<Test>::CollectCoinsAlreadyRegistered |
739 | 1 | ); |
740 | | |
741 | 1 | assert!(Creditcoin::<Test>::collected_coins(collected_coins_id).is_none()); |
742 | 1 | }); |
743 | 1 | } |
744 | | |
745 | 1 | #[test] |
746 | 1 | fn request_address_not_registered() { |
747 | 1 | let ext = ExtBuilder::default(); |
748 | 1 | ext.build_offchain_and_execute_with_state(|_, _| { |
749 | 1 | let (acc, addr, _, _) = generate_address_with_proof("collector"); |
750 | 1 | |
751 | 1 | assert_noop!( |
752 | 1 | Creditcoin::<Test>::request_collect_coins( |
753 | 1 | Origin::signed(acc), |
754 | 1 | addr, |
755 | 1 | TX_HASH.hex_to_address(), |
756 | 1 | ), |
757 | 1 | crate::Error::<Test>::NonExistentAddress |
758 | 1 | ); |
759 | 1 | }); |
760 | 1 | } |
761 | | |
762 | 1 | #[test] |
763 | 1 | fn request_not_owner() { |
764 | 1 | let ext = ExtBuilder::default(); |
765 | 1 | ext.build_offchain_and_execute_with_state(|_, _| { |
766 | 1 | let (acc, addr, sign, _) = generate_address_with_proof("collector"); |
767 | 1 | let (molly, _, _, _) = generate_address_with_proof("malicious"); |
768 | 1 | |
769 | 1 | assert_ok!(Creditcoin::<Test>::register_address( |
770 | 1 | Origin::signed(acc), |
771 | 1 | CHAIN, |
772 | 1 | addr.clone(), |
773 | 1 | sign |
774 | 1 | )); |
775 | | |
776 | 1 | assert_noop!( |
777 | 1 | Creditcoin::<Test>::request_collect_coins( |
778 | 1 | Origin::signed(molly), |
779 | 1 | addr, |
780 | 1 | TX_HASH.hex_to_address(), |
781 | 1 | ), |
782 | 1 | crate::Error::<Test>::NotAddressOwner |
783 | 1 | ); |
784 | 1 | }); |
785 | 1 | } |
786 | | |
787 | 1 | #[test] |
788 | 1 | fn persist_not_authority() { |
789 | 1 | let ext = ExtBuilder::default(); |
790 | 1 | ext.build_offchain_and_execute_with_state(|_, _| { |
791 | 1 | let (molly, addr, _, _) = generate_address_with_proof("malicious"); |
792 | 1 | |
793 | 1 | let collected_coins = CollectedCoins { |
794 | 1 | to: AddressId::new::<Test>(&CHAIN, &addr[..]), |
795 | 1 | amount: RPC_RESPONSE_AMOUNT.as_u128(), |
796 | 1 | tx_id: TX_HASH.hex_to_address(), |
797 | 1 | }; |
798 | 1 | let collected_coins_id = CollectedCoinsId::new::<Test>(&CHAIN, &collected_coins.tx_id); |
799 | 1 | |
800 | 1 | assert_noop!( |
801 | 1 | Creditcoin::<Test>::persist_task_output( |
802 | 1 | Origin::signed(molly), |
803 | 1 | Test::unverified_transfer_deadline(), |
804 | 1 | (collected_coins_id, collected_coins).into(), |
805 | 1 | ), |
806 | 1 | crate::Error::<Test>::InsufficientAuthority |
807 | 1 | ); |
808 | 1 | }); |
809 | 1 | } |
810 | | |
811 | 1 | #[test] |
812 | 1 | fn persist_is_submitted() { |
813 | 1 | let mut ext = ExtBuilder::default(); |
814 | 1 | ext.generate_authority(); |
815 | 1 | ext.build_offchain_and_execute_with_state(|state, pool| { |
816 | 1 | mock_rpc_for_collect_coins(&state); |
817 | 1 | |
818 | 1 | let (acc, addr, sign, _) = generate_address_with_proof("collector"); |
819 | 1 | |
820 | 1 | assert_ok!(Creditcoin::<Test>::register_address( |
821 | 1 | Origin::signed(acc.clone()), |
822 | 1 | CHAIN, |
823 | 1 | addr.clone(), |
824 | 1 | sign |
825 | 1 | )); |
826 | | |
827 | 1 | assert_ok!(Creditcoin::<Test>::request_collect_coins( |
828 | 1 | Origin::signed(acc), |
829 | 1 | addr.clone(), |
830 | 1 | TX_HASH.hex_to_address() |
831 | 1 | )); |
832 | | |
833 | 1 | let deadline = Test::unverified_transfer_deadline(); |
834 | 1 | |
835 | 1 | roll_by_with_ocw(1); |
836 | 1 | |
837 | 1 | assert!(!pool.read().transactions.is_empty()); |
838 | | |
839 | 1 | let collected_coins = CollectedCoins { |
840 | 1 | to: AddressId::new::<Test>(&CHAIN, &addr[..]), |
841 | 1 | amount: RPC_RESPONSE_AMOUNT.as_u128(), |
842 | 1 | tx_id: TX_HASH.hex_to_address(), |
843 | 1 | }; |
844 | 1 | let collected_coins_id = CollectedCoinsId::new::<Test>(&CHAIN, &collected_coins.tx_id); |
845 | 1 | |
846 | 1 | let call = crate::Call::<crate::mock::Test>::persist_task_output { |
847 | 1 | task_output: (collected_coins_id, collected_coins).into(), |
848 | 1 | deadline, |
849 | 1 | }; |
850 | | |
851 | 1 | assert_matches!0 (pool.write().transactions.pop(), Some(tx) => { |
852 | 1 | let tx = crate::mock::Extrinsic::decode(&mut &*tx).unwrap(); |
853 | 1 | assert_eq!(tx.call, crate::mock::Call::Creditcoin(call)); |
854 | | }); |
855 | 1 | }); |
856 | 1 | } |
857 | | |
858 | 1 | #[test] |
859 | 1 | fn persist_not_reentrant() { |
860 | 1 | let mut ext = ExtBuilder::default(); |
861 | 1 | let acct_pubkey = ext.generate_authority(); |
862 | 1 | let auth = AccountId::from(acct_pubkey.into_account().0); |
863 | 1 | ext.build_offchain_and_execute_with_state(|_, _| { |
864 | 1 | let (acc, addr, sign, _) = generate_address_with_proof("collector"); |
865 | 1 | |
866 | 1 | assert_ok!(Creditcoin::<Test>::register_address( |
867 | 1 | Origin::signed(acc), |
868 | 1 | CHAIN, |
869 | 1 | addr.clone(), |
870 | 1 | sign |
871 | 1 | )); |
872 | | |
873 | 1 | let collected_coins = CollectedCoins { |
874 | 1 | to: AddressId::new::<Test>(&CHAIN, &addr[..]), |
875 | 1 | amount: RPC_RESPONSE_AMOUNT.as_u128(), |
876 | 1 | tx_id: TX_HASH.hex_to_address(), |
877 | 1 | }; |
878 | 1 | let collected_coins_id = CollectedCoinsId::new::<Test>(&CHAIN, &collected_coins.tx_id); |
879 | 1 | |
880 | 1 | assert_ok!(Creditcoin::<Test>::persist_task_output( |
881 | 1 | Origin::signed(auth.clone()), |
882 | 1 | Test::unverified_transfer_deadline(), |
883 | 1 | (collected_coins_id.clone(), collected_coins.clone()).into(), |
884 | 1 | )); |
885 | | |
886 | 1 | assert_noop!( |
887 | 1 | Creditcoin::<Test>::persist_task_output( |
888 | 1 | Origin::signed(auth), |
889 | 1 | Test::unverified_transfer_deadline(), |
890 | 1 | (collected_coins_id, collected_coins).into(), |
891 | 1 | ), |
892 | 1 | non_paying_error(crate::Error::<Test>::CollectCoinsAlreadyRegistered) |
893 | 1 | ); |
894 | 1 | }); |
895 | 1 | } |
896 | | |
897 | 1 | #[test] |
898 | 1 | fn unverified_collect_coins_are_removed() { |
899 | 1 | let mut ext = ExtBuilder::default(); |
900 | 1 | ext.generate_authority(); |
901 | 1 | ext.build_offchain_and_execute_with_state(|state, _| { |
902 | 1 | mock_rpc_for_collect_coins(&state); |
903 | 1 | |
904 | 1 | let (acc, addr, sign, _) = generate_address_with_proof("collector"); |
905 | 1 | |
906 | 1 | assert_ok!(Creditcoin::<Test>::register_address( |
907 | 1 | Origin::signed(acc.clone()), |
908 | 1 | CHAIN, |
909 | 1 | addr.clone(), |
910 | 1 | sign |
911 | 1 | )); |
912 | | |
913 | 1 | assert_ok!(Creditcoin::<Test>::request_collect_coins( |
914 | 1 | Origin::signed(acc), |
915 | 1 | addr, |
916 | 1 | TX_HASH.hex_to_address() |
917 | 1 | )); |
918 | 1 | let deadline = Test::unverified_transfer_deadline(); |
919 | 1 | |
920 | 1 | roll_by_with_ocw(deadline); |
921 | 1 | |
922 | 1 | let collected_coins_id = |
923 | 1 | CollectedCoinsId::new::<Test>(&CHAIN, TX_HASH.hex_to_address().as_slice()); |
924 | 1 | |
925 | 1 | roll_by_with_ocw(1); |
926 | 1 | |
927 | 1 | assert!(Creditcoin::<Test>::pending_tasks(deadline, TaskId::from(collected_coins_id)) |
928 | 1 | .is_none()); |
929 | 1 | }); |
930 | 1 | } |
931 | | |
932 | 1 | #[test] |
933 | 1 | fn owner_credited() { |
934 | 1 | let mut ext = ExtBuilder::default(); |
935 | 1 | let acct_pubkey = ext.generate_authority(); |
936 | 1 | let auth = AccountId::from(acct_pubkey.into_account().0); |
937 | 1 | ext.build_offchain_and_execute_with_state(|_, _| { |
938 | 1 | let (acc, addr, sign, _) = generate_address_with_proof("collector"); |
939 | 1 | |
940 | 1 | let collected_coins = CollectedCoins { |
941 | 1 | to: AddressId::new::<Test>(&CHAIN, &addr[..]), |
942 | 1 | amount: RPC_RESPONSE_AMOUNT.as_u128(), |
943 | 1 | tx_id: TX_HASH.hex_to_address(), |
944 | 1 | }; |
945 | 1 | let collected_coins_id = CollectedCoinsId::new::<Test>(&CHAIN, &collected_coins.tx_id); |
946 | 1 | |
947 | 1 | assert_ok!(Creditcoin::<Test>::register_address( |
948 | 1 | Origin::signed(acc.clone()), |
949 | 1 | CHAIN, |
950 | 1 | addr, |
951 | 1 | sign |
952 | 1 | )); |
953 | | |
954 | 1 | assert_ok!(Creditcoin::<Test>::persist_task_output( |
955 | 1 | Origin::signed(auth.clone()), |
956 | 1 | Test::unverified_transfer_deadline(), |
957 | 1 | (collected_coins_id, collected_coins.clone()).into(), |
958 | 1 | )); |
959 | | |
960 | 1 | assert_eq!( |
961 | 1 | frame_system::pallet::Account::<Test>::get(&acc).data.free, |
962 | 1 | collected_coins.amount |
963 | 1 | ); |
964 | 1 | }); |
965 | 1 | } |
966 | | |
967 | 1 | #[test] |
968 | 1 | fn selector_mismatch() { |
969 | 1 | let ext = ExtBuilder::default(); |
970 | 1 | ext.build_offchain_and_execute_with_state(|state, _| { |
971 | 1 | mock_rpc_for_collect_coins(&state); |
972 | 1 | |
973 | 1 | let (_, to, ..) = generate_address_with_proof("collector"); |
974 | 1 | let tx_id = &TX_HASH.hex_to_address(); |
975 | 1 | |
976 | 1 | let rpc_url = &CHAIN.rpc_url().unwrap(); |
977 | 1 | let mut tx = rpc::eth_get_transaction(tx_id, rpc_url).unwrap(); |
978 | 1 | let tx_receipt = rpc::eth_get_transaction_receipt(tx_id, rpc_url).unwrap(); |
979 | 1 | let eth_tip = rpc::eth_get_block_number(rpc_url).unwrap(); |
980 | 1 | let PassingCollectCoins { contract_address, .. } = Default::default(); |
981 | 1 | validate_collect_coins(&to, &tx_receipt, &tx, eth_tip, &contract_address) |
982 | 1 | .expect("valid"); |
983 | 1 | // Forged selector |
984 | 1 | tx.set_input(b"ffffffff"); |
985 | 0 | assert_matches!( |
986 | 1 | validate_collect_coins(&to, &tx_receipt, &tx, eth_tip, &contract_address), |
987 | | Err(OffchainError::InvalidTask(VerificationFailureCause::AbiMismatch)) |
988 | | ); |
989 | 1 | }); |
990 | 1 | } |
991 | | |
992 | 1 | #[test] |
993 | 1 | fn set_collect_coins_only_as_root() { |
994 | 1 | let mut ext = ExtBuilder::default(); |
995 | 1 | let acct_pubkey = ext.generate_authority(); |
996 | 1 | let _auth = AccountId::from(acct_pubkey.into_account().0); |
997 | 1 | ext.build_and_execute(|| { |
998 | 1 | let contract = GCreContract { |
999 | 1 | address: sp_core::H160(hex!("aaaaabbbbbcccccdddddeeeeefffff08F3820419")), |
1000 | 1 | chain: Blockchain::RINKEBY, |
1001 | 1 | }; |
1002 | 1 | assert_ok!(Creditcoin::<Test>::set_collect_coins_contract( |
1003 | 1 | RawOrigin::Root.into(), |
1004 | 1 | contract.clone() |
1005 | 1 | )); |
1006 | 1 | let from_storage = Creditcoin::<Test>::collect_coins_contract(); |
1007 | 1 | assert_eq!(contract, from_storage); |
1008 | 1 | assert_ne!(from_storage, GCreContract::default()); |
1009 | | |
1010 | 1 | let (acc, ..) = generate_address_with_proof("somebody"); |
1011 | 1 | |
1012 | 1 | assert_noop!( |
1013 | 1 | Creditcoin::<Test>::set_collect_coins_contract( |
1014 | 1 | RawOrigin::Signed(acc).into(), |
1015 | 1 | contract.clone() |
1016 | 1 | ), |
1017 | 1 | BadOrigin |
1018 | 1 | ); |
1019 | | |
1020 | 1 | assert_noop!( |
1021 | 1 | Creditcoin::<Test>::set_collect_coins_contract(RawOrigin::None.into(), contract), |
1022 | 1 | BadOrigin |
1023 | 1 | ); |
1024 | 1 | }); |
1025 | 1 | } |
1026 | | |
1027 | 1 | #[test] |
1028 | 1 | fn gcrecontract_value_query_is_default() { |
1029 | 1 | let contract = GCreContract::default(); |
1030 | 1 | let ext = ExtBuilder::default(); |
1031 | 1 | ext.build_and_execute(|| { |
1032 | 1 | let value_query = Creditcoin::<Test>::collect_coins_contract(); |
1033 | 1 | assert_eq!(contract, value_query); |
1034 | 1 | }); |
1035 | 1 | } |
1036 | | |
1037 | 1 | #[test] |
1038 | 1 | fn persist_minimum_existential_deposit_errors() { |
1039 | 1 | let mut ext = ExtBuilder::default(); |
1040 | 1 | let acct_pubkey = ext.generate_authority(); |
1041 | 1 | let auth = AccountId::from(acct_pubkey.into_account().0); |
1042 | 1 | ext.build_offchain_and_execute_with_state(|_, _| { |
1043 | 1 | let (acc, addr, sign, _) = generate_address_with_proof("collector"); |
1044 | 1 | |
1045 | 1 | let collected_coins = CollectedCoins { |
1046 | 1 | to: AddressId::new::<Test>(&CHAIN, &addr[..]), |
1047 | 1 | amount: 1u128, |
1048 | 1 | tx_id: TX_HASH.hex_to_address(), |
1049 | 1 | }; |
1050 | 1 | let collected_coins_id = CollectedCoinsId::new::<Test>(&CHAIN, &collected_coins.tx_id); |
1051 | 1 | |
1052 | 1 | assert_ok!(Creditcoin::<Test>::register_address( |
1053 | 1 | Origin::signed(acc.clone()), |
1054 | 1 | CHAIN, |
1055 | 1 | addr, |
1056 | 1 | sign |
1057 | 1 | )); |
1058 | | |
1059 | 1 | assert_eq!(Balances::total_balance(&acc), 0); |
1060 | | |
1061 | 1 | assert_noop!( |
1062 | 1 | Creditcoin::<Test>::persist_task_output( |
1063 | 1 | Origin::signed(auth.clone()), |
1064 | 1 | Test::unverified_transfer_deadline(), |
1065 | 1 | (collected_coins_id, collected_coins).into(), |
1066 | 1 | ), |
1067 | 1 | TokenError::BelowMinimum |
1068 | 1 | ); |
1069 | 1 | }); |
1070 | 1 | } |
1071 | | |
1072 | 1 | #[test] |
1073 | 1 | fn transaction_not_found() { |
1074 | 1 | ExtBuilder::default().build_offchain_and_execute_with_state(|state, _| { |
1075 | 1 | let mut rpcs = prepare_rpc_mocks(); |
1076 | 1 | rpcs.get_transaction.set_empty_response(); |
1077 | 1 | rpcs.mock_get_transaction(&mut state.write()); |
1078 | 1 | |
1079 | 1 | let (_, addr, _, _) = generate_address_with_proof("collector"); |
1080 | 1 | let cc = UnverifiedCollectedCoins { |
1081 | 1 | to: addr, |
1082 | 1 | tx_id: TX_HASH.hex_to_address(), |
1083 | 1 | contract: GCreContract::default(), |
1084 | 1 | }; |
1085 | 0 | assert_matches!( |
1086 | 1 | Creditcoin::<Test>::verify_collect_coins_ocw(&cc), |
1087 | | Err(OffchainError::InvalidTask(VerificationFailureCause::TransactionNotFound)) |
1088 | | ); |
1089 | 1 | }); |
1090 | 1 | } |
1091 | | } |