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::TidesChainId;
14use tonic::codegen::StdError;
15use tonic::transport::{Channel, Endpoint};
16use trident_proto_types::tides::core::v1::ChainType;
17use trident_proto_types::trident::attester::v1::attester_service_client::AttesterServiceClient;
18use trident_proto_types::trident::attester::v1::{GetChainTokenMappingsRequest, SpendRequest};
19use trident_proto_types::trident::attester::v1::{
20 GetDeploymentsRequest, GetLastFinalizedBlockRequest, GetSupportedDepositTokensRequest,
21};
22use trident_proto_types::trident::core::v1::{SpendIntentSigned, TridentDeployment};
23use trident_types::core::{FixedBytes32, SpendIntent, SpendSpec};
24use trident_types::evm::abi::{TridentDelivery, TridentDeposits};
25
26pub struct TridentClient {
29 deployments: HashMap<TidesChainId, TridentDeployment>,
30 supported_tokens: HashMap<TidesChainId, HashSet<Address>>,
31 max_block_height_buffer_percentage: u64,
33 pub attester: AttesterServiceClient<Channel>,
35}
36
37impl TridentClient {
38 pub const DEFAULT_MAX_BLOCK_HEIGHT_BUFFER_PERCENTAGE: u64 = 10;
41 pub async fn connect<D>(endpoint: D) -> Result<Self, Error>
43 where
44 D: TryInto<Endpoint>,
45 D::Error: Into<StdError>,
46 {
47 let mut attester = AttesterServiceClient::connect(endpoint).await?;
48
49 let deployments: HashMap<TidesChainId, TridentDeployment> = attester
51 .get_deployments(GetDeploymentsRequest {})
52 .await?
53 .into_inner()
54 .deployments
55 .into_iter()
56 .map(|d| Ok((d.chain_id, d.deployment.ok_or(Error::InvalidService)?)))
57 .collect::<Result<_, Error>>()?;
58
59 let mut supported_tokens: HashMap<TidesChainId, HashSet<Address>> = HashMap::new();
61 for (chain_id, _) in deployments.iter() {
62 let resp = attester
63 .get_supported_deposit_tokens(GetSupportedDepositTokensRequest {
64 chain_id: *chain_id,
65 })
66 .await?
67 .into_inner();
68 for t in resp.tokens.into_iter() {
69 if t.address.len() == 32 {
70 let addr = Address::from_slice(&t.address[12..]);
71 supported_tokens.entry(*chain_id).or_default().insert(addr);
72 }
73 }
74 }
75
76 Ok(Self {
77 attester,
78 deployments,
79 supported_tokens,
80 max_block_height_buffer_percentage: Self::DEFAULT_MAX_BLOCK_HEIGHT_BUFFER_PERCENTAGE,
81 })
82 }
83
84 pub async fn connect_testnet() -> Result<Self, Error> {
85 Self::connect(crate::envs::testnet::trident::ENDPOINT).await
86 }
87
88 pub fn with_max_block_height_buffer_percentage(mut self, buffer: u64) -> Self {
91 self.max_block_height_buffer_percentage = buffer;
92 self
93 }
94
95 pub async fn evm_deposit_with_approval<N: Network, P: Provider<N>>(
98 &self,
99 provider: P,
100 tides_chain_id: u32,
101 token: Address,
102 amount: U256,
103 ) -> Result<DepositInEvmChainWithApprovalResponse<N>, Error> {
104 let deposits_contract = self.validate_evm_deposit(tides_chain_id, token)?;
105 let approval_tx_receipt = self
106 .evm_approve_token(&provider, token, deposits_contract, amount)
107 .await?;
108 let deposit_tx_receipt = self
109 .evm_deposit_token(&provider, deposits_contract, token, amount)
110 .await?;
111
112 Ok(DepositInEvmChainWithApprovalResponse {
113 approval_tx_receipt,
114 deposit_tx_receipt,
115 })
116 }
117
118 fn validate_evm_deposit(
119 &self,
120 tides_chain_id: TidesChainId,
121 token: Address,
122 ) -> Result<Address, Error> {
123 let deployment = self.ensure_evm_chain(tides_chain_id)?;
124
125 self.ensure_supported_token(tides_chain_id, token)?;
126
127 let dep_addr_bytes = &deployment.trident_deposits_contract;
128 if dep_addr_bytes.len() != 32 {
129 return Err(Error::InvalidService);
130 }
131 Ok(Address::from_slice(&dep_addr_bytes[12..]))
132 }
133
134 async fn evm_approve_token<N: Network, P: Provider<N>>(
135 &self,
136 provider: &P,
137 token: Address,
138 spender: Address,
139 amount: U256,
140 ) -> Result<<N as Network>::ReceiptResponse, Error> {
141 let erc20 = IERC20::new(token, provider);
142 let tx = erc20.approve(spender, amount).send().await?;
143 Ok(tx.get_receipt().await?)
144 }
145
146 pub async fn evm_deposit_token<N: Network, P: Provider<N>>(
148 &self,
149 provider: &P,
150 deposits_contract: Address,
151 token: Address,
152 amount: U256,
153 ) -> Result<<N as Network>::ReceiptResponse, Error> {
154 let deposits = TridentDeposits::new(deposits_contract, provider);
155 let tx = deposits.deposit(token, amount).send().await?;
156 Ok(tx.get_receipt().await?)
157 }
158
159 pub async fn evm_sign_and_deposit_with_permit<
160 N: Network,
161 P: Provider<N>,
162 S: alloy_signer::Signer + Send + Sync,
163 >(
164 &self,
165 provider: P,
166 mut signer: S,
167 tides_chain_id: u32,
168 token: Address,
169 amount: U256,
170 deadline: U256,
171 ) -> Result<N::ReceiptResponse, Error> {
172 let deposits_contract = self.validate_evm_deposit(tides_chain_id, token)?;
173 let owner = signer.address();
174
175 let signature = {
176 let domain = EIP712::query_domain(&provider, token).await?;
177 let nonce = EIP7597::get_nonce(&provider, token, owner).await?;
178 self.evm_sign_deposit_with_permit_request(
179 &mut signer,
180 domain,
181 deposits_contract,
182 amount,
183 nonce,
184 deadline,
185 )
186 .await?
187 };
188
189 let deposit_tx_receipt = self
190 .evm_deposit_with_permit(
191 &provider,
192 deposits_contract,
193 token,
194 owner,
195 amount,
196 deadline,
197 Bytes::from(signature.as_bytes()),
198 )
199 .await?;
200
201 Ok(deposit_tx_receipt)
202 }
203
204 pub async fn evm_sign_deposit_with_permit_request<S: alloy_signer::Signer + Send + Sync>(
205 &self,
206 mut signer: S,
207 domain: Eip712Domain,
208 spender: Address,
209 amount: U256,
210 nonce: U256,
211 deadline: U256,
212 ) -> Result<Signature, Error> {
213 let typed_data = EIP7597::permit_typed_data(
214 domain,
215 EIP7597PermitMessage {
216 owner: signer.address(),
217 spender,
218 value: amount,
219 nonce,
220 deadline,
221 },
222 );
223
224 let sig = EIP712::sign(typed_data, &mut signer).await?;
225
226 Ok(sig)
227 }
228
229 #[allow(clippy::too_many_arguments)]
231 pub async fn evm_deposit_with_permit<N: Network, P: Provider<N>>(
232 &self,
233 provider: &P,
234 deposits_contract: Address,
235 token: Address,
236 owner: Address,
237 amount: U256,
238 deadline: U256,
239 signature: Bytes,
240 ) -> Result<<N as Network>::ReceiptResponse, Error> {
241 let deposits = TridentDeposits::new(deposits_contract, provider);
242 let tx = deposits
243 .depositWithPermit(token, owner, amount, deadline, signature)
244 .send()
245 .await?;
246 Ok(tx.get_receipt().await?)
247 }
248
249 pub async fn evm_deliver<N: Network, P: Provider<N>>(
251 &mut self,
252 provider: &P,
253 destination_chain: TidesChainId,
254 attestation_payload: Vec<u8>,
255 signature: Vec<u8>,
256 ) -> Result<<N as Network>::ReceiptResponse, Error> {
257 let dep = self.ensure_evm_chain(destination_chain)?;
259 let delivery_fb32 = FixedBytes32::try_from(dep.trident_delivery_contract.clone())?;
260 let delivery_addr = Address::try_from(delivery_fb32)?;
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: FixedBytes32::try_from(src_dep.trident_deposits_contract.clone())?,
558 source_token: source_token.into(),
559 source_token_amount,
560 source_depositor: FixedBytes32::from(source_depositor),
561 destination_chain,
562 destination_contract: FixedBytes32::try_from(
563 dst_dep.trident_delivery_contract.clone(),
564 )?,
565 destination_token: destination_token.into(),
566 destination_recipient: destination_recipient.into(),
567 destination_caller: destination_caller.into(),
568 salt: salt.into(),
569 callback_data: callback_data.unwrap_or_default(),
570 };
571
572 Ok(spec)
573 }
574
575 pub fn make_spend_intent(
577 &self,
578 max_fee: U256,
579 max_block_height: u64,
580 spend_spec: SpendSpec,
581 ) -> SpendIntent {
582 SpendIntent {
583 max_fee: max_fee.try_into().unwrap(),
584 max_block_height,
585 spend_spec,
586 }
587 }
588
589 fn generate_salt(&self) -> Result<FixedBytes32, Error> {
590 let mut rng = rand::rngs::OsRng;
591 let mut buf = [0u8; 32];
592 rng.try_fill_bytes(&mut buf)?;
593 Ok(FixedBytes32::from(buf))
594 }
595
596 pub async fn get_suggested_fee(
598 &mut self,
599 _source_chain: TidesChainId,
600 _source_token: impl Into<FixedBytes32>,
601 _source_token_amount: U256,
602 _destination_chain: TidesChainId,
603 _destination_token: impl Into<FixedBytes32>,
604 ) -> Result<U256, Error> {
605 Ok(U256::ZERO)
607 }
608
609 pub async fn suggest_spend_intent_max_block_height(
612 &mut self,
613 src_chain_id: TidesChainId,
614 ) -> Result<u64, Error> {
615 let min_blocks = {
617 let dep = self.ensure_known_chain(src_chain_id)?;
618 dep.min_spend_intent_validity_blocks
619 };
620
621 let pct = self.max_block_height_buffer_percentage as u128;
623 let buffer_blocks_u128 = (min_blocks as u128)
624 .checked_mul(pct)
625 .ok_or(Error::BlockHeightOverflow)?
626 .checked_div(100u128)
627 .ok_or(Error::BlockHeightOverflow)?;
628 let buffer_blocks: u64 =
629 u64::try_from(buffer_blocks_u128).map_err(|_| Error::BlockHeightOverflow)?;
630
631 let min_blocks_with_buffer = min_blocks
632 .checked_add(buffer_blocks)
633 .ok_or(Error::BlockHeightOverflow)?;
634
635 let last_finalized = self
637 .attester
638 .get_last_finalized_block(GetLastFinalizedBlockRequest {
639 chain_id: src_chain_id,
640 })
641 .await?
642 .into_inner()
643 .last_finalized_block;
644
645 let suggested = last_finalized
647 .checked_add(min_blocks_with_buffer)
648 .ok_or(Error::BlockHeightOverflow)?;
649
650 Ok(suggested)
651 }
652
653 fn ensure_known_chain(&self, chain_id: TidesChainId) -> Result<&TridentDeployment, Error> {
654 self.deployments
655 .get(&chain_id)
656 .ok_or(Error::UnknownChain(chain_id))
657 }
658
659 fn ensure_evm_chain(&self, chain_id: TidesChainId) -> Result<&TridentDeployment, Error> {
660 let dep = self.ensure_known_chain(chain_id)?;
661 if dep.chain_type != (ChainType::Evm as i32) {
662 return Err(Error::UnsupportedChainType(dep.chain_type));
663 }
664 Ok(dep)
665 }
666
667 fn ensure_supported_token(&self, chain_id: TidesChainId, token: Address) -> Result<(), Error> {
668 let is_supported = self
669 .supported_tokens
670 .get(&chain_id)
671 .map(|set| set.contains(&token))
672 .unwrap_or(false);
673 if is_supported {
674 Ok(())
675 } else {
676 Err(Error::UnsupportedToken(token))
677 }
678 }
679
680 pub async fn evm_faucet<N: Network, P: Provider<N>>(
683 &self,
684 provider: &P,
685 token: Address,
686 recipient: Address,
687 amount: U256,
688 ) -> Result<N::ReceiptResponse, Error> {
689 sol! {
690 #[sol(rpc)]
691 contract Faucet {
692 function mint(address, uint256);
693 }
694 }
695
696 let faucet = Faucet::new(token, provider);
698 let pending = faucet.mint(recipient, amount).send().await?;
699 let receipt = pending.get_receipt().await?;
700
701 Ok(receipt)
702 }
703
704 pub fn get_deployment_by_name(&self, name: impl AsRef<str>) -> Option<&TridentDeployment> {
705 let name = name.as_ref();
706 self.deployments.iter().find_map(|(_, value)| {
707 if value.chain_name.eq(name) {
708 Some(value)
709 } else {
710 None
711 }
712 })
713 }
714
715 pub async fn evm_balances(
716 &mut self,
717 chain_id: TidesChainId,
718 address: impl Into<Address>,
719 ) -> Result<HashMap<Address, U256>, Error> {
720 self.ensure_evm_chain(chain_id)?;
722
723 self.attester
725 .balances(
726 trident_proto_types::trident::attester::v1::BalancesRequest {
727 chain_id,
728 address: FixedBytes32::from(address.into()).to_vec(),
729 },
730 )
731 .await?
732 .into_inner()
733 .available_balances
734 .into_iter()
735 .map(|balance| {
736 let token: Address = FixedBytes32::try_from(balance.token)?.try_into()?;
737 let amount =
738 U256::try_from_be_slice(&balance.amount).ok_or(Error::InvalidU256ByteSlice)?;
739 Ok((token, amount))
740 })
741 .collect()
742 }
743
744 pub async fn evm_balance(
745 &mut self,
746 chain_id: TidesChainId,
747 address: impl Into<Address>,
748 want: impl Into<Address>,
749 ) -> Result<U256, Error> {
750 let want = want.into();
751 self.evm_balances(chain_id, address).await.map(|v| {
752 v.into_iter()
753 .find_map(|(token, amount)| {
754 if token == want {
755 return Some(amount);
756 }
757 None
758 })
759 .unwrap_or_default()
760 })
761 }
762}
763
764#[cfg(test)]
765mod tests {
766 use super::*;
767 use alloy_primitives::Address;
768 use std::collections::HashMap;
769 use tonic::transport::Channel;
770 use trident_proto_types::tides::core::v1::ChainType;
771
772 fn make_fb32() -> Vec<u8> {
773 vec![0u8; 32]
774 }
775
776 fn make_deployment(chain_type: ChainType, chain_id: u32) -> TridentDeployment {
777 TridentDeployment {
778 chain_type: chain_type as i32,
779 chain_name: "ethereum".to_string(),
780 tides_chain_id: chain_id,
781 trident_deposits_contract: make_fb32(),
782 trident_claims_contract: make_fb32(),
783 trident_delivery_contract: make_fb32(),
784 min_spend_intent_validity_blocks: 10,
785 max_spend_attestation_validity_blocks: 20,
786 anchor_block: 1,
787 }
788 }
789
790 fn make_test_client(deployments: HashMap<TidesChainId, TridentDeployment>) -> TridentClient {
791 let attester =
792 AttesterServiceClient::new(Channel::from_static("http://localhost:1").connect_lazy());
793
794 TridentClient {
795 deployments,
796 supported_tokens: HashMap::new(),
797 max_block_height_buffer_percentage:
798 TridentClient::DEFAULT_MAX_BLOCK_HEIGHT_BUFFER_PERCENTAGE,
799 attester,
800 }
801 }
802
803 #[tokio::test]
805 async fn test_trident_client_ensure_known_chain_known_chain_success() {
806 let chain_id: TidesChainId = 1;
807 let mut deployments = HashMap::new();
808 deployments.insert(chain_id, make_deployment(ChainType::Evm, chain_id));
809 let client = make_test_client(deployments);
810
811 let res = client.ensure_known_chain(chain_id);
812 assert!(res.is_ok());
813 }
814
815 #[tokio::test]
816 async fn test_trident_client_ensure_known_chain_unknown_chain_error() {
817 let client = make_test_client(HashMap::new());
818 let chain_id: TidesChainId = 42;
819
820 let res = client.ensure_known_chain(chain_id);
821 assert!(matches!(res, Err(Error::UnknownChain(id)) if id == chain_id));
822 }
823
824 #[tokio::test]
826 async fn test_trident_client_ensure_evm_chain_evm_chain_success() {
827 let chain_id: TidesChainId = 5;
828 let mut deployments = HashMap::new();
829 deployments.insert(chain_id, make_deployment(ChainType::Evm, chain_id));
830 let client = make_test_client(deployments);
831
832 let res = client.ensure_evm_chain(chain_id);
833 assert!(res.is_ok());
834 }
835
836 #[tokio::test]
837 async fn test_trident_client_ensure_evm_chain_unsupported_chain_type_error() {
838 let chain_id: TidesChainId = 7;
839 let mut deployments = HashMap::new();
840 deployments.insert(chain_id, make_deployment(ChainType::Unspecified, chain_id));
841 let client = make_test_client(deployments);
842
843 let res = client.ensure_evm_chain(chain_id);
844 assert!(
845 matches!(res, Err(Error::UnsupportedChainType(t)) if t == ChainType::Unspecified as i32)
846 );
847 }
848
849 #[tokio::test]
851 async fn test_trident_client_ensure_supported_token_supported_success() {
852 let chain_id: TidesChainId = 9;
853 let mut deployments = HashMap::new();
854 deployments.insert(chain_id, make_deployment(ChainType::Evm, chain_id));
855 let mut client = make_test_client(deployments);
856
857 let token = Address::from([0x11u8; 20]);
859 client
860 .supported_tokens
861 .entry(chain_id)
862 .or_default()
863 .insert(token);
864
865 let res = client.ensure_supported_token(chain_id, token);
866 assert!(matches!(res, Ok(())));
867 }
868
869 #[tokio::test]
870 async fn test_trident_client_ensure_supported_token_unsupported_error() {
871 let chain_id: TidesChainId = 10;
872 let mut deployments = HashMap::new();
873 deployments.insert(chain_id, make_deployment(ChainType::Evm, chain_id));
874 let client = make_test_client(deployments);
875
876 let token = Address::from([0x22u8; 20]);
877 let res = client.ensure_supported_token(chain_id, token);
878 assert!(matches!(res, Err(Error::UnsupportedToken(t)) if t == token));
879 }
880
881 #[tokio::test]
882 async fn test_trident_client_resolve_destination_tokens_same_chain_success() {
883 let chain_id: TidesChainId = 123;
884
885 let mut client = {
886 let deployments = HashMap::new();
888 make_test_client(deployments)
889 };
890
891 let src_token = Address::from([0xAAu8; 20]);
892
893 let res = client
894 .resolve_destination_tokens::<Address, Address>(chain_id, chain_id, src_token)
895 .await;
896
897 assert!(res.is_ok());
898 let v = res.unwrap();
899 assert_eq!(v.len(), 1);
900 assert_eq!(v[0], src_token);
901 }
902}