Coverage Report

Created: 2022-11-10 19:56

/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
      &ethless_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
}