1use super::{
2 types::{Account, RemittanceReciept, WithheldAccount},
3 DC_CANISTERS,
4};
5use crate::crypto::string_to_vec_u8;
6use candid::Principal;
7use easy_hasher::easy_hasher;
8use eth_encode_packed::{
9 ethabi::{ethereum_types::U256, Address},
10 SolidityDataType,
11};
12use ic_cdk::{api::time, caller};
13
14pub fn only_whitelisted_dc_canister() {
17 let caller_principal_id = caller();
18 if !DC_CANISTERS.with(|publisher| publisher.borrow().contains(&caller_principal_id)) {
19 panic!("NOT_ALLOWED");
20 }
21}
22
23pub fn hash_remittance_parameters(
36 nonce: u64,
37 amount: u64,
38 address: &str,
39 chain_id: &str,
40 dc_canister_id: &str,
41 token_address: &str,
42) -> Vec<u8> {
43 let address: [u8; 20] = string_to_vec_u8(address).try_into().unwrap();
45 let token_address: [u8; 20] = string_to_vec_u8(token_address).try_into().unwrap();
46
47 let input = vec![
49 SolidityDataType::Number(U256::from(nonce)),
50 SolidityDataType::Number(U256::from(amount)),
51 SolidityDataType::Address(Address::from(address)),
52 SolidityDataType::String(chain_id),
53 SolidityDataType::String(dc_canister_id),
54 SolidityDataType::Address(Address::from(token_address)),
55 ];
56 let (_bytes, __) = eth_encode_packed::abi::encode_packed(&input);
57
58 easy_hasher::raw_keccak256(_bytes.clone()).to_vec()
59}
60
61pub fn get_remitted_balance(
73 token: super::types::Wallet,
74 chain: super::types::Chain,
75 account: super::types::Wallet,
76 dc_canister: Principal,
77 amount: u64,
78) -> WithheldAccount {
79 let withheld_amount = super::WITHHELD_REMITTANCE.with(|withheld| {
80 let existing_key = (token, chain, account.clone(), dc_canister, amount);
81 withheld
82 .borrow()
83 .get(&existing_key)
84 .cloned()
85 .unwrap_or_default()
86 });
87
88 withheld_amount
89}
90
91pub fn get_available_balance(
102 token: super::types::Wallet,
103 chain: super::types::Chain,
104 account: super::types::Wallet,
105 dc_canister: Principal,
106) -> Account {
107 let available_amount = super::REMITTANCE.with(|remittance| {
108 let existing_key = (token, chain, account, dc_canister);
109 remittance
110 .borrow()
111 .get(&existing_key)
112 .cloned()
113 .unwrap_or_default()
114 });
115
116 available_amount
117}
118
119pub fn get_canister_balance(
129 token: super::types::Wallet,
130 chain: super::types::Chain,
131 dc_canister: Principal,
132) -> Account {
133 let canister_balance = super::CANISTER_BALANCE.with(|cb| {
134 let existing_key = (token, chain, dc_canister);
135 cb.borrow().get(&existing_key).cloned().unwrap_or_default()
136 });
137
138 canister_balance
139}
140
141pub fn update_balance(new_remittance: &super::types::DataModel, dc_canister: Principal) {
147 super::REMITTANCE.with(|remittance| {
148 let mut remittance_store = remittance.borrow_mut();
149
150 let hash_key = (
151 new_remittance.token.clone(),
152 new_remittance.chain.clone(),
153 new_remittance.account.clone(),
154 dc_canister.clone(),
155 );
156
157 if let Some(existing_data) = remittance_store.get_mut(&hash_key) {
158 existing_data.balance =
159 ((existing_data.balance as i64) + (new_remittance.amount as i64)) as u64;
160 } else {
161 remittance_store.insert(
162 hash_key,
163 Account {
164 balance: new_remittance.amount as u64,
165 },
166 );
167 }
168 });
169}
170
171pub fn update_canister_balance(
179 token: super::types::Wallet,
180 chain: super::types::Chain,
181 dc_canister: Principal,
182 amount: i64,
183) {
184 super::CANISTER_BALANCE.with(|canister_balance| {
185 let mut canister_balance_store = canister_balance.borrow_mut();
186
187 let hash_key = (token.clone(), chain.clone(), dc_canister.clone());
188
189 if let Some(existing_data) = canister_balance_store.get_mut(&hash_key) {
190 existing_data.balance = ((existing_data.balance as i64) + (amount as i64)) as u64;
191 } else {
192 canister_balance_store.insert(
193 hash_key,
194 Account {
195 balance: amount as u64,
196 },
197 );
198 }
199 });
200}
201
202pub fn confirm_withdrawal(
214 token: String,
215 chain: String,
216 account: String,
217 amount_withdrawn: u64,
218 dc_canister: Principal,
219) -> bool {
220 let chain: super::types::Chain = chain.try_into().unwrap();
221 let token: super::types::Wallet = token.try_into().unwrap();
222 let account: super::types::Wallet = account.try_into().unwrap();
223
224 let hash_key = (
225 token.clone(),
226 chain.clone(),
227 account.clone(),
228 dc_canister.clone(),
229 );
230
231 super::WITHHELD_AMOUNTS.with(|witheld_amounts| {
233 let mut mut_witheld_amounts = witheld_amounts.borrow_mut();
234 let unwithdrawn_amounts = mut_witheld_amounts
235 .get(&hash_key)
236 .expect("WITHDRAWAL_CONFIRMATION_ERROR:AMOUNT_NOT_WITHELD")
237 .into_iter()
238 .filter(|&amount_to_withdraw| *amount_to_withdraw != amount_withdrawn)
239 .cloned()
240 .collect();
241 mut_witheld_amounts.insert(hash_key, unwithdrawn_amounts);
242 });
243
244 let withdrawn_details = super::WITHHELD_REMITTANCE.with(|withheld_remittance| {
246 let key = (
247 token.clone(),
248 chain.clone(),
249 account.clone(),
250 dc_canister.clone(),
251 amount_withdrawn,
252 );
253 let withdrawn_balance = withheld_remittance.borrow().get(&key).unwrap().clone();
254 withheld_remittance.borrow_mut().remove(&key);
255
256 withdrawn_balance
257 });
258
259 super::REMITTANCE_RECIEPTS.with(|remittance_reciepts| {
261 remittance_reciepts.borrow_mut().insert(
262 (dc_canister, withdrawn_details.nonce),
263 RemittanceReciept {
264 token: token.to_string(),
265 chain: chain.to_string(),
266 amount: amount_withdrawn,
267 account: account.to_string(),
268 timestamp: time(),
269 },
270 );
271 });
272 return true;
273}
274
275pub fn cancel_withdrawal(
287 token: String,
288 chain: String,
289 account: String,
290 amount_canceled: u64,
291 dc_canister: Principal,
292) -> bool {
293 let chain: super::types::Chain = chain.try_into().unwrap();
294 let token: super::types::Wallet = token.try_into().unwrap();
295 let account: super::types::Wallet = account.try_into().unwrap();
296
297 let hash_key = (
298 token.clone(),
299 chain.clone(),
300 account.clone(),
301 dc_canister.clone(),
302 );
303
304 super::WITHHELD_AMOUNTS.with(|witheld_amounts| {
306 let mut mut_witheld_amounts = witheld_amounts.borrow_mut();
307 let unwithdrawn_amounts = mut_witheld_amounts
308 .get(&hash_key)
309 .expect("CANCEL_WITHDRAW_ERROR:AMOUNT_NOT_WITHELD")
310 .into_iter()
311 .filter(|&amount_to_withdraw| *amount_to_withdraw != amount_canceled)
312 .cloned()
313 .collect();
314 mut_witheld_amounts.insert(hash_key.clone(), unwithdrawn_amounts);
315 });
316
317 super::WITHHELD_REMITTANCE.with(|withheld_remittance| {
319 withheld_remittance.borrow_mut().remove(&(
320 token.clone(),
321 chain.clone(),
322 account.clone(),
323 dc_canister.clone(),
324 amount_canceled,
325 ));
326 });
327
328 super::REMITTANCE.with(|remittance| {
330 if let Some(existing_data) = remittance.borrow_mut().get_mut(&hash_key) {
331 existing_data.balance = existing_data.balance + amount_canceled;
332 }
333 });
334
335 return true;
336}
337
338pub fn validate_remittance_data(
348 is_pdc: bool,
349 new_remittances: &Vec<super::types::DataModel>,
350 dc_canister: Principal,
351) -> Result<(), String> {
352 match is_pdc {
353 true => validate_pdc_remittance_data(new_remittances, dc_canister),
354 false => validate_dc_remittance_data(new_remittances, dc_canister),
355 }
356}
357
358pub fn validate_pdc_remittance_data(
367 new_remittances: &Vec<super::types::DataModel>,
368 dc_canister: Principal,
369) -> Result<(), String> {
370 let adjust_operations: Vec<super::types::DataModel> = new_remittances
372 .into_iter()
373 .filter(|&single_remittance| single_remittance.action == super::types::Action::Adjust)
374 .cloned()
375 .collect();
376 if let Err(err_message) = validate_dc_remittance_data(&adjust_operations, dc_canister) {
378 return Err(err_message);
379 }
380
381 let non_adjust_operations_gt_0: Vec<&super::types::DataModel> = new_remittances
385 .into_iter()
386 .filter(|single_remittance| {
387 single_remittance.action != super::types::Action::Adjust && single_remittance.amount < 0
388 })
389 .collect();
390 if non_adjust_operations_gt_0.len() > 0 {
391 return Err("NON_ADJUST_AMOUNT_MUST_BE_GT_0".to_string());
392 }
393
394 Ok(())
395}
396
397pub fn validate_dc_remittance_data(
406 new_remittances: &Vec<super::types::DataModel>,
407 dc_canister: Principal,
408) -> Result<(), String> {
409 let amount_delta = new_remittances
411 .iter()
412 .fold(0, |acc, account| acc + account.amount);
413
414 if amount_delta != 0 {
415 return Err("SUM_ADJUST_AMOUNTS != 0".to_string());
416 }
417
418 let is_action_valid = new_remittances
420 .iter()
421 .all(|item| item.action == super::types::Action::Adjust);
422
423 if !is_action_valid {
424 return Err("INVALID_ACTION_FOUND".to_string());
425 }
426
427 let mut sufficient_balance_error: Result<(), String> = Ok(());
429 new_remittances
430 .iter()
431 .filter(|&item| item.amount < 0)
432 .for_each(|item| {
433 let existing_balance = get_available_balance(
434 item.token.clone(),
435 item.chain.clone(),
436 item.account.clone(),
437 dc_canister.clone(),
438 );
439
440 if existing_balance.balance < (item.amount.abs() as u64) {
441 sufficient_balance_error = Err("INSUFFICIENT_USER_BALANCE".to_string());
442 }
443 });
444 if let Err(_) = sufficient_balance_error {
445 return sufficient_balance_error;
446 }
447 let mut insufficient_canister_balance: Result<(), String> = Ok(());
449 new_remittances
450 .iter()
451 .filter(|&item| item.amount > 0)
452 .for_each(|item| {
453 let existing_balance =
454 get_canister_balance(item.token.clone(), item.chain.clone(), dc_canister.clone());
455
456 if existing_balance.balance < (item.amount as u64) {
457 insufficient_canister_balance = Err("INSUFFICIENT_CANISTER_BALANCE".to_string());
458 }
459 });
460
461 insufficient_canister_balance
462}
463
464#[cfg(test)]
465mod tests {
466 use super::*;
467
468 #[test]
469 fn test_recover_address_from_eth_signature() {
470 let nonce: u64 = 10;
471 let amount: u64 = 100;
472 let address = "0x5c8e3a7c16fa5cdde9f74751d6b2395176f05c55".to_string();
473 let chain_id = "ethereum:5";
474 let dc_canister_id = "br5f7-7uaaa-aaaaa-qaaca-cai";
475 let token_address = "0x5c8e3a7c16fa5cdde9f74751d6b2395176f05c55";
476 let hashed_output = [
477 8, 134, 176, 185, 121, 53, 193, 199, 185, 42, 238, 73, 122, 96, 223, 42, 230, 175, 125,
478 59, 72, 6, 36, 6, 38, 59, 74, 94, 51, 57, 117, 88,
479 ]
480 .to_vec();
481
482 let recovered_address = hash_remittance_parameters(
483 nonce,
484 amount,
485 &address,
486 chain_id,
487 dc_canister_id,
488 token_address,
489 );
490 assert_eq!(recovered_address, hashed_output);
491 }
492}