Coverage Report

Created: 2022-11-10 19:56

/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
ecod
e24
, Eq,
P22
artialE
q22
,
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
}