1use crate::eips::eip712::EIP712;
2use crate::eips::eip7597::{EIP7597, EIP7597PermitMessage};
3use crate::eips::erc20::IERC20;
4use crate::error::Error;
5use crate::types::{
6 DepositInEvmChainWithApprovalResponse, EvmToEvmSpendRequest, EvmToEvmSpendResponse,
7};
8use alloy_primitives::{Address, Bytes, Signature, U256};
9use alloy_provider::{Network, Provider};
10use alloy_sol_types::{Eip712Domain, sol};
11use rand_core::TryRngCore;
12use std::collections::{HashMap, HashSet};
13use tides_core_types::{ChainType, TidesChainId};
14use tonic::codegen::StdError;
15use tonic::transport::{Channel, Endpoint};
16use trident_proto_types::trident::attester::v1::attester_service_client::AttesterServiceClient;
17use trident_proto_types::trident::attester::v1::{GetChainTokenMappingsRequest, SpendRequest};
18use trident_proto_types::trident::attester::v1::{
19 GetDeploymentsRequest, GetLastFinalizedBlockRequest, GetSupportedDepositTokensRequest,
20};
21use trident_proto_types::trident::core::v1::SpendIntentSigned;
22use trident_types::core::{FixedBytes32, SpendIntent, SpendSpec, TridentDeployment};
23use trident_types::evm::abi::{TridentDelivery, TridentDeposits};
24
25pub struct TridentClient {
28 deployments: HashMap<TidesChainId, TridentDeployment>,
29 supported_tokens: HashMap<TidesChainId, HashSet<Address>>,
30 max_block_height_buffer_percentage: u64,
32 pub attester: AttesterServiceClient<Channel>,
34}
35
36impl TridentClient {
37 pub const DEFAULT_MAX_BLOCK_HEIGHT_BUFFER_PERCENTAGE: u64 = 10;
40 pub async fn connect<D>(endpoint: D) -> Result<Self, Error>
42 where
43 D: TryInto<Endpoint>,
44 D::Error: Into<StdError>,
45 {
46 let mut attester = AttesterServiceClient::connect(endpoint).await?;
47
48 let deployments = attester
50 .get_deployments(GetDeploymentsRequest {})
51 .await?
52 .into_inner()
53 .deployments
54 .into_iter()
55 .map(|d| d.deployment.ok_or(Error::InvalidService))
56 .map(|d| Ok(TridentDeployment::try_from(d?)?))
57 .map(|d| d.map(|d| (d.chain_id, d)))
58 .collect::<Result<HashMap<_, _>, Error>>()?;
59
60 let mut supported_tokens: HashMap<TidesChainId, HashSet<Address>> = HashMap::new();
62 for (chain_id, _) in deployments.iter() {
63 let resp = attester
64 .get_supported_deposit_tokens(GetSupportedDepositTokensRequest {
65 chain_id: *chain_id,
66 })
67 .await?
68 .into_inner();
69 for t in resp.tokens.into_iter() {
70 if t.address.len() == 32 {
71 let addr = Address::from_slice(&t.address[12..]);
72 supported_tokens.entry(*chain_id).or_default().insert(addr);
73 }
74 }
75 }
76
77 Ok(Self {
78 attester,
79 deployments,
80 supported_tokens,
81 max_block_height_buffer_percentage: Self::DEFAULT_MAX_BLOCK_HEIGHT_BUFFER_PERCENTAGE,
82 })
83 }
84
85 pub async fn connect_testnet() -> Result<Self, Error> {
86 Self::connect(crate::envs::testnet::trident::ENDPOINT).await
87 }
88
89 pub fn with_max_block_height_buffer_percentage(mut self, buffer: u64) -> Self {
92 self.max_block_height_buffer_percentage = buffer;
93 self
94 }
95
96 pub async fn evm_deposit_with_approval<N: Network, P: Provider<N>>(
99 &self,
100 provider: P,
101 tides_chain_id: u32,
102 token: Address,
103 amount: U256,
104 ) -> Result<DepositInEvmChainWithApprovalResponse<N>, Error> {
105 let deposits_contract = self.validate_evm_deposit(tides_chain_id, token)?;
106 let approval_tx_receipt = self
107 .evm_approve_token(&provider, token, deposits_contract, amount)
108 .await?;
109 let deposit_tx_receipt = self
110 .evm_deposit_token(&provider, deposits_contract, token, amount)
111 .await?;
112
113 Ok(DepositInEvmChainWithApprovalResponse {
114 approval_tx_receipt,
115 deposit_tx_receipt,
116 })
117 }
118
119 fn validate_evm_deposit(
120 &self,
121 tides_chain_id: TidesChainId,
122 token: Address,
123 ) -> Result<Address, Error> {
124 let deployment = self.ensure_evm_chain(tides_chain_id)?;
125
126 self.ensure_supported_token(tides_chain_id, token)?;
127
128 let dep_addr_bytes = &deployment.trident_deposits_contract;
129 if dep_addr_bytes.len() != 32 {
130 return Err(Error::InvalidService);
131 }
132 Ok(Address::from_slice(&dep_addr_bytes[12..]))
133 }
134
135 async fn evm_approve_token<N: Network, P: Provider<N>>(
136 &self,
137 provider: &P,
138 token: Address,
139 spender: Address,
140 amount: U256,
141 ) -> Result<<N as Network>::ReceiptResponse, Error> {
142 let erc20 = IERC20::new(token, provider);
143 let tx = erc20.approve(spender, amount).send().await?;
144 Ok(tx.get_receipt().await?)
145 }
146
147 pub async fn evm_deposit_token<N: Network, P: Provider<N>>(
149 &self,
150 provider: &P,
151 deposits_contract: Address,
152 token: Address,
153 amount: U256,
154 ) -> Result<<N as Network>::ReceiptResponse, Error> {
155 let deposits = TridentDeposits::new(deposits_contract, provider);
156 let tx = deposits.deposit(token, amount).send().await?;
157 Ok(tx.get_receipt().await?)
158 }
159
160 pub async fn evm_sign_and_deposit_with_permit<
161 N: Network,
162 P: Provider<N>,
163 S: alloy_signer::Signer + Send + Sync,
164 >(
165 &self,
166 provider: P,
167 mut signer: S,
168 tides_chain_id: u32,
169 token: Address,
170 amount: U256,
171 deadline: U256,
172 ) -> Result<N::ReceiptResponse, Error> {
173 let deposits_contract = self.validate_evm_deposit(tides_chain_id, token)?;
174 let owner = signer.address();
175
176 let signature = {
177 let domain = EIP712::query_domain(&provider, token).await?;
178 let nonce = EIP7597::get_nonce(&provider, token, owner).await?;
179 self.evm_sign_deposit_with_permit_request(
180 &mut signer,
181 domain,
182 deposits_contract,
183 amount,
184 nonce,
185 deadline,
186 )
187 .await?
188 };
189
190 let deposit_tx_receipt = self
191 .evm_deposit_with_permit(
192 &provider,
193 deposits_contract,
194 token,
195 owner,
196 amount,
197 deadline,
198 Bytes::from(signature.as_bytes()),
199 )
200 .await?;
201
202 Ok(deposit_tx_receipt)
203 }
204
205 pub async fn evm_sign_deposit_with_permit_request<S: alloy_signer::Signer + Send + Sync>(
206 &self,
207 mut signer: S,
208 domain: Eip712Domain,
209 spender: Address,
210 amount: U256,
211 nonce: U256,
212 deadline: U256,
213 ) -> Result<Signature, Error> {
214 let typed_data = EIP7597::permit_typed_data(
215 domain,
216 EIP7597PermitMessage {
217 owner: signer.address(),
218 spender,
219 value: amount,
220 nonce,
221 deadline,
222 },
223 );
224
225 let sig = EIP712::sign(typed_data, &mut signer).await?;
226
227 Ok(sig)
228 }
229
230 #[allow(clippy::too_many_arguments)]
232 pub async fn evm_deposit_with_permit<N: Network, P: Provider<N>>(
233 &self,
234 provider: &P,
235 deposits_contract: Address,
236 token: Address,
237 owner: Address,
238 amount: U256,
239 deadline: U256,
240 signature: Bytes,
241 ) -> Result<<N as Network>::ReceiptResponse, Error> {
242 let deposits = TridentDeposits::new(deposits_contract, provider);
243 let tx = deposits
244 .depositWithPermit(token, owner, amount, deadline, signature)
245 .send()
246 .await?;
247 Ok(tx.get_receipt().await?)
248 }
249
250 pub async fn evm_deliver<N: Network, P: Provider<N>>(
252 &mut self,
253 provider: &P,
254 destination_chain: TidesChainId,
255 attestation_payload: Vec<u8>,
256 signature: Vec<u8>,
257 ) -> Result<<N as Network>::ReceiptResponse, Error> {
258 let dep = self.ensure_evm_chain(destination_chain)?;
260 let delivery_addr = Address::try_from(dep.trident_delivery_contract)?;
261
262 let delivery = TridentDelivery::new(delivery_addr, provider);
264 let tx = delivery
265 .deliverToken(attestation_payload.into(), signature.into())
266 .send()
267 .await?;
268 let receipt = tx.get_receipt().await?;
269 Ok(receipt)
270 }
271
272 pub async fn evm_to_evm_spend<'a, DN: Network, S: alloy_signer::Signer, DP: Provider<DN>>(
275 &mut self,
276 req: EvmToEvmSpendRequest<'a, S, DP, DN>,
277 ) -> Result<EvmToEvmSpendResponse<DN>, Error> {
278 let signer = req.source_signer.address();
279
280 self.ensure_evm_chain(req.source_chain_id)?;
282 self.ensure_evm_chain(req.destination_chain_id)?;
283
284 self.ensure_supported_token(req.source_chain_id, req.source_token)?;
286
287 let dst_token = if let Some(tok) = req.destination_token {
289 tok
290 } else {
291 self.resolve_single_destination_token(
292 req.source_chain_id,
293 req.destination_chain_id,
294 req.source_token,
295 )
296 .await?
297 };
298
299 self.ensure_supported_token(req.destination_chain_id, dst_token)?;
302
303 let spend_spec = self.make_spend_spec(
305 req.source_chain_id,
306 signer,
307 req.source_token,
308 req.source_token_amount,
309 req.destination_chain_id,
310 dst_token,
311 req.destination_recipient,
312 req.destination_caller.unwrap_or(signer),
313 req.salt.map(Ok).unwrap_or_else(|| self.generate_salt())?,
314 req.destination_callback.map(|v| v.to_vec()),
315 )?;
316
317 let fees = match req.source_max_fees {
319 Some(fees) => fees,
320 None => {
321 self.get_suggested_fee(
322 req.source_chain_id,
323 req.source_token,
324 req.source_token_amount,
325 req.destination_chain_id,
326 dst_token,
327 )
328 .await?
329 }
330 };
331
332 self.ensure_sufficient_source_balance(
334 req.source_chain_id,
335 signer,
336 req.source_token,
337 fees + req.source_token_amount,
338 )
339 .await?;
340
341 let max_block_height = match req.max_block_height {
343 Some(max_block_height) => max_block_height,
344 None => {
345 self.suggest_spend_intent_max_block_height(req.source_chain_id)
346 .await?
347 }
348 };
349
350 let spend_intent = self.make_spend_intent(fees, max_block_height, spend_spec);
352
353 let signature = trident_types::evm::spend_intent_signing::sign_spend_intent(
355 req.source_signer,
356 &spend_intent,
357 )
358 .await?
359 .as_bytes()
360 .to_vec();
361
362 let spend_attestation_signed = self
364 .attester
365 .spend(SpendRequest {
366 intent: Some(SpendIntentSigned {
367 intent: Some(spend_intent.clone().into()),
368 signature,
369 }),
370 })
371 .await?
372 .into_inner()
373 .attestation
374 .expect("attester.spend.attestation");
375
376 let delivery_tx_receipt = match self
378 .evm_deliver(
379 req.destination_provider,
380 req.destination_chain_id,
381 spend_attestation_signed.attestation_bytes.clone(),
382 spend_attestation_signed.signature.clone(),
383 )
384 .await
385 {
386 Ok(r) => r,
387 Err(e) => {
388 return Err(Error::EvmDeliveryFailed(Box::new(
389 crate::error::EvmDeliveryFailed {
390 spend_intent,
391 attestation_payload: spend_attestation_signed.attestation_bytes,
392 signature: spend_attestation_signed.signature,
393 cause: Box::new(e),
394 },
395 )));
396 }
397 };
398
399 Ok(EvmToEvmSpendResponse {
400 destination_token: dst_token,
401 applied_fees: fees,
402 max_block_height,
403 delivery_tx_receipt,
404 })
405 }
406
407 async fn resolve_single_destination_token(
408 &mut self,
409 source_chain_id: TidesChainId,
410 destination_chain_id: TidesChainId,
411 source_token: Address,
412 ) -> Result<Address, Error> {
413 let candidates: Vec<Address> = self
414 .resolve_destination_tokens::<Address, Address>(
415 source_chain_id,
416 destination_chain_id,
417 source_token,
418 )
419 .await?;
420
421 match candidates.len() {
422 0 => Err(Error::NoDestinationTokenMapping(
423 source_token,
424 source_chain_id,
425 destination_chain_id,
426 )),
427 1 => Ok(candidates[0]),
428 _ => Err(Error::AmbiguousDestinationTokenMapping(
429 source_token,
430 source_chain_id,
431 destination_chain_id,
432 )),
433 }
434 }
435
436 pub async fn resolve_destination_tokens<T1, T2>(
439 &mut self,
440 source_chain_id: TidesChainId,
441 destination_chain_id: TidesChainId,
442 source_token: T1,
443 ) -> Result<Vec<T2>, Error>
444 where
445 T1: Into<FixedBytes32>,
446 T2: TryFrom<FixedBytes32>,
447 <T2 as TryFrom<FixedBytes32>>::Error: core::fmt::Display,
448 {
449 if source_chain_id == destination_chain_id {
450 let fb32: FixedBytes32 = source_token.into();
451 let t2 = T2::try_from(fb32).map_err(|err: <T2 as TryFrom<FixedBytes32>>::Error| {
452 Error::TokenConversion(err.to_string())
453 })?;
454 return Ok(vec![t2]);
455 }
456
457 let mappings = self
458 .attester
459 .get_chain_token_mappings(GetChainTokenMappingsRequest {
460 chain_id: source_chain_id,
461 })
462 .await?
463 .into_inner()
464 .mappings;
465
466 let fb32: FixedBytes32 = source_token.into();
468 let fb32_bytes = fb32.to_vec();
469
470 let fb32s = mappings
471 .into_iter()
472 .filter(|v| match &v.token {
474 None => false,
475 Some(v) => v.address == fb32_bytes,
476 })
477 .flat_map(|v| v.mappings)
479 .filter(|v| v.chain_id == destination_chain_id)
481 .map(|v| Result::<_, Error>::Ok(FixedBytes32::try_from(v.address)?))
483 .collect::<Result<Vec<FixedBytes32>, Error>>()?;
485
486 fb32s
488 .into_iter()
489 .map(|v| T2::try_from(v).map_err(|err| Error::TokenConversion(err.to_string())))
490 .collect()
491 }
492
493 async fn ensure_sufficient_source_balance(
495 &mut self,
496 chain_id: TidesChainId,
497 account: Address,
498 token: Address,
499 amount: U256,
500 ) -> Result<(), Error> {
501 let resp = self
502 .attester
503 .balances(
504 trident_proto_types::trident::attester::v1::BalancesRequest {
505 chain_id,
506 address: FixedBytes32::from(account).to_vec(),
507 },
508 )
509 .await?
510 .into_inner();
511
512 let mut available = U256::ZERO;
514 for b in resp.available_balances.into_iter() {
515 if b.token.len() == 32 {
517 let fb = FixedBytes32::try_from(b.token)?;
518 let tok_addr = Address::try_from(fb)?;
519 if tok_addr == token {
520 let arr: [u8; 32] = b.amount.as_slice().try_into()?;
522 available = U256::from_be_bytes(arr);
523 break;
524 }
525 }
526 }
527
528 if available < amount {
529 return Err(Error::InsufficientBalance(amount, available));
530 }
531 Ok(())
532 }
533
534 #[allow(clippy::too_many_arguments)]
535 pub fn make_spend_spec(
537 &self,
538 source_chain: TidesChainId,
539 source_depositor: Address,
540 source_token: impl Into<FixedBytes32>,
541 source_token_amount: U256,
542 destination_chain: TidesChainId,
543 destination_token: impl Into<FixedBytes32>,
544 destination_recipient: impl Into<FixedBytes32>,
545 destination_caller: impl Into<FixedBytes32>,
546 salt: impl Into<FixedBytes32>,
547 callback_data: Option<Vec<u8>>,
548 ) -> Result<SpendSpec, Error> {
549 let src_dep = self.ensure_evm_chain(source_chain)?;
551 let dst_dep = self.ensure_evm_chain(destination_chain)?;
552
553 let spec = SpendSpec {
555 version: 0,
556 source_chain,
557 source_contract: src_dep.trident_deposits_contract,
558 source_token: source_token.into(),
559 source_token_amount,
560 source_depositor: FixedBytes32::from(source_depositor),
561 destination_chain,
562 destination_contract: dst_dep.trident_delivery_contract,
563 destination_token: destination_token.into(),
564 destination_recipient: destination_recipient.into(),
565 destination_caller: destination_caller.into(),
566 salt: salt.into(),
567 callback_data: callback_data.unwrap_or_default(),
568 };
569
570 Ok(spec)
571 }
572
573 pub fn make_spend_intent(
575 &self,
576 max_fee: U256,
577 max_block_height: u64,
578 spend_spec: SpendSpec,
579 ) -> SpendIntent {
580 SpendIntent {
581 max_fee: max_fee.try_into().unwrap(),
582 max_block_height,
583 spend_spec,
584 }
585 }
586
587 fn generate_salt(&self) -> Result<FixedBytes32, Error> {
588 let mut rng = rand::rngs::OsRng;
589 let mut buf = [0u8; 32];
590 rng.try_fill_bytes(&mut buf)?;
591 Ok(FixedBytes32::from(buf))
592 }
593
594 pub async fn get_suggested_fee(
596 &mut self,
597 _source_chain: TidesChainId,
598 _source_token: impl Into<FixedBytes32>,
599 _source_token_amount: U256,
600 _destination_chain: TidesChainId,
601 _destination_token: impl Into<FixedBytes32>,
602 ) -> Result<U256, Error> {
603 Ok(U256::ZERO)
605 }
606
607 pub async fn suggest_spend_intent_max_block_height(
610 &mut self,
611 src_chain_id: TidesChainId,
612 ) -> Result<u64, Error> {
613 let min_blocks = {
615 let dep = self.ensure_known_chain(src_chain_id)?;
616 dep.min_spend_intent_validity_blocks
617 };
618
619 let pct = self.max_block_height_buffer_percentage as u128;
621 let buffer_blocks_u128 = (min_blocks as u128)
622 .checked_mul(pct)
623 .ok_or(Error::BlockHeightOverflow)?
624 .checked_div(100u128)
625 .ok_or(Error::BlockHeightOverflow)?;
626 let buffer_blocks: u64 =
627 u64::try_from(buffer_blocks_u128).map_err(|_| Error::BlockHeightOverflow)?;
628
629 let min_blocks_with_buffer = min_blocks
630 .checked_add(buffer_blocks)
631 .ok_or(Error::BlockHeightOverflow)?;
632
633 let last_finalized = self
635 .attester
636 .get_last_finalized_block(GetLastFinalizedBlockRequest {
637 chain_id: src_chain_id,
638 })
639 .await?
640 .into_inner()
641 .last_finalized_block;
642
643 let suggested = last_finalized
645 .checked_add(min_blocks_with_buffer)
646 .ok_or(Error::BlockHeightOverflow)?;
647
648 Ok(suggested)
649 }
650
651 fn ensure_known_chain(&self, chain_id: TidesChainId) -> Result<&TridentDeployment, Error> {
652 self.deployments
653 .get(&chain_id)
654 .ok_or(Error::UnknownChain(chain_id))
655 }
656
657 fn ensure_evm_chain(&self, chain_id: TidesChainId) -> Result<&TridentDeployment, Error> {
658 let dep = self.ensure_known_chain(chain_id)?;
659 if dep.chain_type != ChainType::Evm {
660 return Err(Error::UnsupportedChainType(dep.chain_type as i32));
661 }
662 Ok(dep)
663 }
664
665 fn ensure_supported_token(&self, chain_id: TidesChainId, token: Address) -> Result<(), Error> {
666 let is_supported = self
667 .supported_tokens
668 .get(&chain_id)
669 .map(|set| set.contains(&token))
670 .unwrap_or(false);
671 if is_supported {
672 Ok(())
673 } else {
674 Err(Error::UnsupportedToken(token))
675 }
676 }
677
678 pub async fn evm_faucet<N: Network, P: Provider<N>>(
681 &self,
682 provider: &P,
683 token: Address,
684 recipient: Address,
685 amount: U256,
686 ) -> Result<N::ReceiptResponse, Error> {
687 sol! {
688 #[sol(rpc)]
689 contract Faucet {
690 function mint(address, uint256);
691 }
692 }
693
694 let faucet = Faucet::new(token, provider);
696 let pending = faucet.mint(recipient, amount).send().await?;
697 let receipt = pending.get_receipt().await?;
698
699 Ok(receipt)
700 }
701
702 pub fn get_deployments(&self) -> &HashMap<TidesChainId, TridentDeployment> {
703 &self.deployments
704 }
705
706 pub fn get_deployment_by_name(&self, name: impl AsRef<str>) -> Option<&TridentDeployment> {
707 let name = name.as_ref();
708 self.deployments.iter().find_map(|(_, value)| {
709 if value.chain_name.eq(name) {
710 Some(value)
711 } else {
712 None
713 }
714 })
715 }
716
717 pub async fn evm_balances(
718 &mut self,
719 chain_id: TidesChainId,
720 address: impl Into<Address>,
721 ) -> Result<HashMap<Address, U256>, Error> {
722 self.ensure_evm_chain(chain_id)?;
724
725 self.attester
727 .balances(
728 trident_proto_types::trident::attester::v1::BalancesRequest {
729 chain_id,
730 address: FixedBytes32::from(address.into()).to_vec(),
731 },
732 )
733 .await?
734 .into_inner()
735 .available_balances
736 .into_iter()
737 .map(|balance| {
738 let token: Address = FixedBytes32::try_from(balance.token)?.try_into()?;
739 let amount =
740 U256::try_from_be_slice(&balance.amount).ok_or(Error::InvalidU256ByteSlice)?;
741 Ok((token, amount))
742 })
743 .collect()
744 }
745
746 pub async fn evm_balance(
747 &mut self,
748 chain_id: TidesChainId,
749 address: impl Into<Address>,
750 want: impl Into<Address>,
751 ) -> Result<U256, Error> {
752 let want = want.into();
753 self.evm_balances(chain_id, address).await.map(|v| {
754 v.into_iter()
755 .find_map(|(token, amount)| {
756 if token == want {
757 return Some(amount);
758 }
759 None
760 })
761 .unwrap_or_default()
762 })
763 }
764}
765
766#[cfg(test)]
767mod tests {
768 use crate::{Error, TridentClient};
769 use alloy_primitives::Address;
770 use rand_core::TryRngCore;
771 use std::collections::HashMap;
772 use tides_core_types::{ChainType, TidesChainId};
773 use tonic::transport::Channel;
774 use trident_proto_types::trident::attester::v1::attester_service_client::AttesterServiceClient;
775 use trident_types::core::{FixedBytes32, TridentDeployment};
776
777 fn make_fb32() -> FixedBytes32 {
778 let mut fb = FixedBytes32([0u8; 32]);
779 rand::rngs::OsRng
780 .try_fill_bytes(&mut fb.0)
781 .expect("OsRng.try_fill_bytes");
782 fb
783 }
784
785 fn make_deployment(chain_type: ChainType, chain_id: u32) -> TridentDeployment {
786 TridentDeployment {
787 chain_type,
788 chain_name: "ethereum".to_string(),
789 chain_id,
790 trident_deposits_contract: make_fb32(),
791 trident_claims_contract: make_fb32(),
792 trident_delivery_contract: make_fb32(),
793 min_spend_intent_validity_blocks: 10,
794 max_spend_attestation_validity_blocks: 20,
795 anchor_block: 1,
796 }
797 }
798
799 fn make_test_client(deployments: HashMap<TidesChainId, TridentDeployment>) -> TridentClient {
800 let attester =
801 AttesterServiceClient::new(Channel::from_static("http://localhost:1").connect_lazy());
802
803 TridentClient {
804 deployments,
805 supported_tokens: HashMap::new(),
806 max_block_height_buffer_percentage:
807 TridentClient::DEFAULT_MAX_BLOCK_HEIGHT_BUFFER_PERCENTAGE,
808 attester,
809 }
810 }
811
812 #[tokio::test]
814 async fn test_trident_client_ensure_known_chain_known_chain_success() {
815 let chain_id: TidesChainId = 1;
816 let mut deployments = HashMap::new();
817 deployments.insert(chain_id, make_deployment(ChainType::Evm, chain_id));
818 let client = make_test_client(deployments);
819
820 let res = client.ensure_known_chain(chain_id);
821 assert!(res.is_ok());
822 }
823
824 #[tokio::test]
825 async fn test_trident_client_ensure_known_chain_unknown_chain_error() {
826 let client = make_test_client(HashMap::new());
827 let chain_id: TidesChainId = 42;
828
829 let res = client.ensure_known_chain(chain_id);
830 assert!(matches!(res, Err(Error::UnknownChain(id)) if id == chain_id));
831 }
832
833 #[tokio::test]
835 async fn test_trident_client_ensure_evm_chain_evm_chain_success() {
836 let chain_id: TidesChainId = 5;
837 let mut deployments = HashMap::new();
838 deployments.insert(chain_id, make_deployment(ChainType::Evm, chain_id));
839 let client = make_test_client(deployments);
840
841 let res = client.ensure_evm_chain(chain_id);
842 assert!(res.is_ok());
843 }
844
845 #[tokio::test]
847 async fn test_trident_client_ensure_supported_token_supported_success() {
848 let chain_id: TidesChainId = 9;
849 let mut deployments = HashMap::new();
850 deployments.insert(chain_id, make_deployment(ChainType::Evm, chain_id));
851 let mut client = make_test_client(deployments);
852
853 let token = Address::from([0x11u8; 20]);
855 client
856 .supported_tokens
857 .entry(chain_id)
858 .or_default()
859 .insert(token);
860
861 let res = client.ensure_supported_token(chain_id, token);
862 assert!(matches!(res, Ok(())));
863 }
864
865 #[tokio::test]
866 async fn test_trident_client_ensure_supported_token_unsupported_error() {
867 let chain_id: TidesChainId = 10;
868 let mut deployments = HashMap::new();
869 deployments.insert(chain_id, make_deployment(ChainType::Evm, chain_id));
870 let client = make_test_client(deployments);
871
872 let token = Address::from([0x22u8; 20]);
873 let res = client.ensure_supported_token(chain_id, token);
874 assert!(matches!(res, Err(Error::UnsupportedToken(t)) if t == token));
875 }
876
877 #[tokio::test]
878 async fn test_trident_client_resolve_destination_tokens_same_chain_success() {
879 let chain_id: TidesChainId = 123;
880
881 let mut client = {
882 let deployments = HashMap::new();
884 make_test_client(deployments)
885 };
886
887 let src_token = Address::from([0xAAu8; 20]);
888
889 let res = client
890 .resolve_destination_tokens::<Address, Address>(chain_id, chain_id, src_token)
891 .await;
892
893 assert!(res.is_ok());
894 let v = res.unwrap();
895 assert_eq!(v.len(), 1);
896 assert_eq!(v[0], src_token);
897 }
898}