1use std::{collections::HashMap, sync::Arc};
17
18use alloy::{
19 primitives::{Address, Bytes, U256},
20 sol,
21 sol_types::SolCall,
22};
23use strum::Display;
24use thiserror::Error;
25
26use super::base::{BaseContract, ContractCall, Multicall3};
27use crate::rpc::{error::BlockchainRpcClientError, http::BlockchainHttpRpcClient};
28
29sol! {
30 #[sol(rpc)]
31 contract ERC20 {
32 function name() external view returns (string);
33 function symbol() external view returns (string);
34 function decimals() external view returns (uint8);
35 function balanceOf(address account) external view returns (uint256);
36 function allowance(address owner, address spender) external view returns (uint256);
37 function approve(address spender, uint256 amount) external returns (bool);
38 }
39}
40
41#[derive(Debug, Clone, Copy, PartialEq, Eq, Display)]
42pub enum Erc20Field {
43 Name,
44 Symbol,
45 Decimals,
46}
47
48#[derive(Debug, Clone)]
50pub struct TokenInfo {
51 pub name: String,
53 pub symbol: String,
55 pub decimals: u8,
57}
58
59#[derive(Debug, Error)]
61pub enum TokenInfoError {
62 #[error("RPC error: {0}")]
63 RpcError(#[from] BlockchainRpcClientError),
64 #[error("Token {field} is empty for address {address}")]
65 EmptyTokenField { field: Erc20Field, address: Address },
66 #[error("Multicall returned unexpected number of results: expected {expected}, was {actual}")]
67 UnexpectedResultCount { expected: usize, actual: usize },
68 #[error("Call failed for {field} at address {address}: {reason} (raw data: {raw_data})")]
69 CallFailed {
70 field: String,
71 address: Address,
72 reason: String,
73 raw_data: String,
74 },
75 #[error("Failed to decode {field} for address {address}: {reason} (raw data: {raw_data})")]
76 DecodingError {
77 field: String,
78 address: Address,
79 reason: String,
80 raw_data: String,
81 },
82}
83
84#[derive(Debug)]
89pub struct Erc20Contract {
90 base: BaseContract,
92 enforce_token_fields: bool,
94}
95
96impl Erc20Contract {
97 #[must_use]
99 pub fn new(client: Arc<BlockchainHttpRpcClient>, enforce_token_fields: bool) -> Self {
100 Self {
101 base: BaseContract::new(client),
102 enforce_token_fields,
103 }
104 }
105
106 #[must_use]
109 pub fn new_with_timeout(
110 client: Arc<BlockchainHttpRpcClient>,
111 rpc_timeout_secs: Option<u64>,
112 enforce_token_fields: bool,
113 ) -> Self {
114 Self {
115 base: BaseContract::new_with_multicall_limit_and_timeout(
116 client,
117 super::base::DEFAULT_MULTICALL_CALLS_PER_RPC_REQUEST,
118 rpc_timeout_secs,
119 ),
120 enforce_token_fields,
121 }
122 }
123
124 pub async fn fetch_token_info(
132 &self,
133 token_address: &Address,
134 ) -> Result<TokenInfo, TokenInfoError> {
135 let calls = vec![
136 ContractCall {
137 target: *token_address,
138 allow_failure: true,
139 call_data: ERC20::nameCall.abi_encode(),
140 },
141 ContractCall {
142 target: *token_address,
143 allow_failure: true,
144 call_data: ERC20::symbolCall.abi_encode(),
145 },
146 ContractCall {
147 target: *token_address,
148 allow_failure: true,
149 call_data: ERC20::decimalsCall.abi_encode(),
150 },
151 ];
152
153 let results = self.base.execute_multicall(calls, None).await?;
154
155 if results.len() != 3 {
156 return Err(TokenInfoError::UnexpectedResultCount {
157 expected: 3,
158 actual: results.len(),
159 });
160 }
161
162 let name = parse_erc20_string_result(&results[0], Erc20Field::Name, token_address)?;
163 let symbol = parse_erc20_string_result(&results[1], Erc20Field::Symbol, token_address)?;
164 let decimals = parse_erc20_decimals_result(&results[2], token_address)?;
165
166 if self.enforce_token_fields && name.is_empty() {
167 return Err(TokenInfoError::EmptyTokenField {
168 field: Erc20Field::Name,
169 address: *token_address,
170 });
171 }
172
173 if self.enforce_token_fields && symbol.is_empty() {
174 return Err(TokenInfoError::EmptyTokenField {
175 field: Erc20Field::Symbol,
176 address: *token_address,
177 });
178 }
179
180 Ok(TokenInfo {
181 name,
182 symbol,
183 decimals,
184 })
185 }
186
187 pub async fn batch_fetch_token_info(
198 &self,
199 token_addresses: &[Address],
200 ) -> Result<HashMap<Address, Result<TokenInfo, TokenInfoError>>, BlockchainRpcClientError> {
201 let mut calls = Vec::with_capacity(token_addresses.len() * 3);
203
204 for token_address in token_addresses {
205 calls.extend([
206 ContractCall {
207 target: *token_address,
208 allow_failure: true, call_data: ERC20::nameCall.abi_encode(),
210 },
211 ContractCall {
212 target: *token_address,
213 allow_failure: true,
214 call_data: ERC20::symbolCall.abi_encode(),
215 },
216 ContractCall {
217 target: *token_address,
218 allow_failure: true,
219 call_data: ERC20::decimalsCall.abi_encode(),
220 },
221 ]);
222 }
223
224 let results = match self.base.execute_multicall(calls, None).await {
226 Ok(results) => results,
227 Err(e) => {
228 log::warn!(
230 "Batch multicall failed: {}. Falling back to individual fetches for {} tokens",
231 e,
232 token_addresses.len()
233 );
234
235 let mut token_infos = HashMap::with_capacity(token_addresses.len());
237 for token_address in token_addresses {
238 match self.fetch_token_info(token_address).await {
239 Ok(info) => {
240 token_infos.insert(*token_address, Ok(info));
241 }
242 Err(e) => {
243 log::debug!(
244 "Token {token_address} failed individual fetch (likely expired/broken): {e}"
245 );
246 token_infos.insert(*token_address, Err(e));
247 }
248 }
249 }
250
251 return Ok(token_infos);
252 }
253 };
254
255 let mut token_infos = HashMap::with_capacity(token_addresses.len());
256 for (i, token_address) in token_addresses.iter().enumerate() {
257 let base_idx = i * 3;
258
259 if base_idx + 2 >= results.len() {
261 log::error!("Incomplete results from multicall for token {token_address}");
262 token_infos.insert(
263 *token_address,
264 Err(TokenInfoError::UnexpectedResultCount {
265 expected: 3,
266 actual: results.len().saturating_sub(base_idx),
267 }),
268 );
269 continue;
270 }
271
272 let token_info =
273 parse_batch_token_results(&results[base_idx..base_idx + 3], token_address);
274 token_infos.insert(*token_address, token_info);
275 }
276
277 Ok(token_infos)
278 }
279
280 pub async fn balance_of(
288 &self,
289 token_address: &Address,
290 account: &Address,
291 ) -> Result<U256, BlockchainRpcClientError> {
292 self.balance_of_with_block(token_address, account, None)
293 .await
294 }
295
296 async fn balance_of_with_block(
297 &self,
298 token_address: &Address,
299 account: &Address,
300 block: Option<u64>,
301 ) -> Result<U256, BlockchainRpcClientError> {
302 let call_data = ERC20::balanceOfCall { account: *account }.abi_encode();
303 let result = self
304 .base
305 .execute_call(token_address, &call_data, block)
306 .await?;
307
308 ERC20::balanceOfCall::abi_decode_returns(&result)
309 .map_err(|e| BlockchainRpcClientError::AbiDecodingError(e.to_string()))
310 }
311
312 pub async fn decimals(&self, token_address: &Address) -> Result<u8, BlockchainRpcClientError> {
318 self.decimals_with_block(token_address, None).await
319 }
320
321 async fn decimals_with_block(
322 &self,
323 token_address: &Address,
324 block: Option<u64>,
325 ) -> Result<u8, BlockchainRpcClientError> {
326 let call_data = ERC20::decimalsCall {}.abi_encode();
327 let result = self
328 .base
329 .execute_call(token_address, &call_data, block)
330 .await?;
331
332 ERC20::decimalsCall::abi_decode_returns(&result)
333 .map_err(|e| BlockchainRpcClientError::AbiDecodingError(e.to_string()))
334 }
335
336 pub async fn allowance(
344 &self,
345 token_address: &Address,
346 owner: &Address,
347 spender: &Address,
348 ) -> Result<U256, BlockchainRpcClientError> {
349 self.allowance_with_block(token_address, owner, spender, None)
350 .await
351 }
352
353 async fn allowance_with_block(
354 &self,
355 token_address: &Address,
356 owner: &Address,
357 spender: &Address,
358 block: Option<u64>,
359 ) -> Result<U256, BlockchainRpcClientError> {
360 let call_data = ERC20::allowanceCall {
361 owner: *owner,
362 spender: *spender,
363 }
364 .abi_encode();
365 let result = self
366 .base
367 .execute_call(token_address, &call_data, block)
368 .await?;
369
370 ERC20::allowanceCall::abi_decode_returns(&result)
371 .map_err(|e| BlockchainRpcClientError::AbiDecodingError(e.to_string()))
372 }
373}
374
375fn parse_erc20_string_result(
377 result: &Multicall3::Result,
378 field_name: Erc20Field,
379 token_address: &Address,
380) -> Result<String, TokenInfoError> {
381 let data = validate_multicall_result(result, field_name, token_address)?;
382
383 match field_name {
384 Erc20Field::Name => ERC20::nameCall::abi_decode_returns(data),
385 Erc20Field::Symbol => ERC20::symbolCall::abi_decode_returns(data),
386 Erc20Field::Decimals => {
387 return Err(TokenInfoError::DecodingError {
388 field: field_name.to_string(),
389 address: *token_address,
390 reason: "Expected Name or Symbol for parse_erc20_string_result function argument"
391 .to_string(),
392 raw_data: data.to_string(),
393 });
394 }
395 }
396 .map_err(|e| TokenInfoError::DecodingError {
397 field: field_name.to_string(),
398 address: *token_address,
399 reason: e.to_string(),
400 raw_data: result.returnData.to_string(),
401 })
402}
403
404fn parse_erc20_decimals_result(
406 result: &Multicall3::Result,
407 token_address: &Address,
408) -> Result<u8, TokenInfoError> {
409 let data = validate_multicall_result(result, Erc20Field::Decimals, token_address)?;
410
411 ERC20::decimalsCall::abi_decode_returns(data).map_err(|e| TokenInfoError::DecodingError {
412 field: "decimals".to_string(),
413 address: *token_address,
414 reason: e.to_string(),
415 raw_data: result.returnData.to_string(),
416 })
417}
418
419fn validate_multicall_result<'a>(
420 result: &'a Multicall3::Result,
421 field: Erc20Field,
422 token_address: &Address,
423) -> Result<&'a Bytes, TokenInfoError> {
424 if !result.success {
425 return Err(TokenInfoError::CallFailed {
426 field: match field {
427 Erc20Field::Decimals => "decimals".to_string(),
428 _ => field.to_string(),
429 },
430 address: *token_address,
431 reason: decode_revert_reason(&result.returnData),
432 raw_data: result.returnData.to_string(),
433 });
434 }
435
436 if result.returnData.is_empty() {
437 return Err(TokenInfoError::EmptyTokenField {
438 field,
439 address: *token_address,
440 });
441 }
442
443 Ok(&result.returnData)
444}
445
446fn decode_revert_reason(data: &Bytes) -> String {
447 if data.is_empty() {
448 "Call failed without revert data".to_string()
449 } else {
450 format!("Call failed with data: {data}")
451 }
452}
453
454fn parse_batch_token_results(
460 results: &[Multicall3::Result],
461 token_address: &Address,
462) -> Result<TokenInfo, TokenInfoError> {
463 if results.len() != 3 {
464 return Err(TokenInfoError::UnexpectedResultCount {
465 expected: 3,
466 actual: results.len(),
467 });
468 }
469
470 let name = parse_erc20_string_result(&results[0], Erc20Field::Name, token_address)?;
471 let symbol = parse_erc20_string_result(&results[1], Erc20Field::Symbol, token_address)?;
472 let decimals = parse_erc20_decimals_result(&results[2], token_address)?;
473
474 Ok(TokenInfo {
475 name,
476 symbol,
477 decimals,
478 })
479}
480
481#[cfg(test)]
482mod tests {
483 use alloy::primitives::{Bytes, address};
484 use nautilus_core::hex;
485 use rstest::{fixture, rstest};
486
487 use super::*;
488 use crate::rpc::http::tests::mock::{MockRpcState, start_mock_rpc_server};
489
490 const CALL_BALANCE: &str = include_str!("../../test_data/execution/rpc_eth_call_balance.json");
491 const CALL_ALLOWANCE: &str =
492 include_str!("../../test_data/execution/rpc_eth_call_allowance.json");
493 #[fixture]
494 fn token_address() -> Address {
495 address!("25b76A90E389bD644a29db919b136Dc63B174Ec7")
496 }
497
498 #[fixture]
499 fn successful_name_result() -> Multicall3::Result {
500 Multicall3::Result {
501 success: true,
502 returnData: Bytes::from(hex::decode("00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000007546f6b656e204100000000000000000000000000000000000000000000000000").unwrap()),
503 }
504 }
505
506 #[fixture]
507 fn successful_symbol_result() -> Multicall3::Result {
508 Multicall3::Result {
509 success: true,
510 returnData: Bytes::from(hex::decode("0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000776546f6b656e4100000000000000000000000000000000000000000000000000").unwrap()),
511 }
512 }
513
514 #[fixture]
515 fn failed_name_result() -> Multicall3::Result {
516 Multicall3::Result {
517 success: false,
518 returnData: Bytes::from(vec![]),
519 }
520 }
521
522 #[fixture]
523 fn failed_token_address() -> Address {
524 address!("00000000049084A92F8964B76845ab6DE54EB229")
525 }
526
527 #[fixture]
528 fn success_but_empty_result() -> Multicall3::Result {
529 Multicall3::Result {
530 success: true,
531 returnData: Bytes::from(vec![]),
532 }
533 }
534
535 #[fixture]
536 fn empty_token_address() -> Address {
537 address!("a5b00cEc63694319495d605AA414203F9714F47E")
538 }
539
540 #[fixture]
541 fn non_abi_encoded_string_result() -> Multicall3::Result {
542 Multicall3::Result {
544 success: true,
545 returnData: Bytes::from(
546 hex::decode("5269636f00000000000000000000000000000000000000000000000000000000")
547 .unwrap(),
548 ),
549 }
550 }
551
552 #[fixture]
553 fn non_abi_encoded_token_address() -> Address {
554 address!("5374EcC160A4bd68446B43B5A6B132F9c001C54C")
555 }
556
557 #[fixture]
558 fn non_standard_selector_result() -> Multicall3::Result {
559 Multicall3::Result {
561 success: true,
562 returnData: Bytes::from(
563 hex::decode("06fdde0300000000000000000000000000000000000000000000000000000000")
564 .unwrap(),
565 ),
566 }
567 }
568
569 #[fixture]
570 fn non_abi_encoded_long_string_result() -> Multicall3::Result {
571 Multicall3::Result {
573 success: true,
574 returnData: Bytes::from(
575 hex::decode("5269636f62616e6b205269736b20536861726500000000000000000000000000")
576 .unwrap(),
577 ),
578 }
579 }
580
581 #[rstest]
582 fn test_parse_erc20_string_result_name_success(
583 successful_name_result: Multicall3::Result,
584 token_address: Address,
585 ) {
586 let result =
587 parse_erc20_string_result(&successful_name_result, Erc20Field::Name, &token_address);
588 assert!(result.is_ok());
589 assert_eq!(result.unwrap(), "Token A");
590 }
591
592 #[rstest]
593 fn test_parse_erc20_string_result_symbol_success(
594 successful_symbol_result: Multicall3::Result,
595 token_address: Address,
596 ) {
597 let result = parse_erc20_string_result(
598 &successful_symbol_result,
599 Erc20Field::Symbol,
600 &token_address,
601 );
602 assert!(result.is_ok());
603 assert_eq!(result.unwrap(), "vTokenA");
604 }
605
606 #[rstest]
607 fn test_parse_erc20_string_result_name_failed_with_specific_address(
608 failed_name_result: Multicall3::Result,
609 failed_token_address: Address,
610 ) {
611 let result =
612 parse_erc20_string_result(&failed_name_result, Erc20Field::Name, &failed_token_address);
613 assert!(result.is_err());
614 match result.unwrap_err() {
615 TokenInfoError::CallFailed {
616 field,
617 address,
618 reason,
619 raw_data: _,
620 } => {
621 assert_eq!(field, "Name");
622 assert_eq!(address, failed_token_address);
623 assert_eq!(reason, "Call failed without revert data");
624 }
625 _ => panic!("Expected DecodingError"),
626 }
627 }
628
629 #[rstest]
630 fn test_parse_erc20_string_result_success_but_empty_name(
631 success_but_empty_result: Multicall3::Result,
632 empty_token_address: Address,
633 ) {
634 let result = parse_erc20_string_result(
635 &success_but_empty_result,
636 Erc20Field::Name,
637 &empty_token_address,
638 );
639 assert!(result.is_err());
640 match result.unwrap_err() {
641 TokenInfoError::EmptyTokenField { field, address } => {
642 assert!(matches!(field, Erc20Field::Name));
643 assert_eq!(address, empty_token_address);
644 }
645 _ => panic!("Expected EmptyTokenField error"),
646 }
647 }
648
649 #[rstest]
650 fn test_parse_erc20_decimals_result_failed(
651 failed_name_result: Multicall3::Result,
652 failed_token_address: Address,
653 ) {
654 let result = parse_erc20_decimals_result(&failed_name_result, &failed_token_address);
655 match result.unwrap_err() {
656 TokenInfoError::CallFailed {
657 field,
658 address,
659 reason,
660 raw_data,
661 } => {
662 assert_eq!(field, "decimals");
663 assert_eq!(address, failed_token_address);
664 assert_eq!(reason, "Call failed without revert data");
665 assert_eq!(raw_data, "0x");
666 }
667 _ => panic!("Expected CallFailed"),
668 }
669 }
670
671 #[rstest]
672 fn test_parse_erc20_decimals_result_success_but_empty(
673 success_but_empty_result: Multicall3::Result,
674 empty_token_address: Address,
675 ) {
676 let result = parse_erc20_decimals_result(&success_but_empty_result, &empty_token_address);
677 assert!(result.is_err());
678 match result.unwrap_err() {
679 TokenInfoError::EmptyTokenField { field, address } => {
680 assert!(matches!(field, Erc20Field::Decimals));
681 assert_eq!(address, empty_token_address);
682 }
683 _ => panic!("Expected EmptyTokenField error"),
684 }
685 }
686
687 #[rstest]
688 fn test_parse_non_abi_encoded_string(
689 non_abi_encoded_string_result: Multicall3::Result,
690 non_abi_encoded_token_address: Address,
691 ) {
692 let result = parse_erc20_string_result(
693 &non_abi_encoded_string_result,
694 Erc20Field::Name,
695 &non_abi_encoded_token_address,
696 );
697 assert!(result.is_err());
698 match result.unwrap_err() {
699 TokenInfoError::DecodingError {
700 field,
701 address,
702 reason,
703 raw_data,
704 } => {
705 assert_eq!(field, "Name");
706 assert_eq!(address, non_abi_encoded_token_address);
707 assert!(reason.contains("type check failed"));
708 assert_eq!(
709 raw_data,
710 "0x5269636f00000000000000000000000000000000000000000000000000000000"
711 );
712 }
714 _ => panic!("Expected DecodingError"),
715 }
716 }
717
718 #[rstest]
719 fn test_parse_non_standard_selector_return(
720 non_standard_selector_result: Multicall3::Result,
721 token_address: Address,
722 ) {
723 let result = parse_erc20_string_result(
724 &non_standard_selector_result,
725 Erc20Field::Name,
726 &token_address,
727 );
728 assert!(result.is_err());
729 match result.unwrap_err() {
730 TokenInfoError::DecodingError {
731 field,
732 address,
733 reason,
734 raw_data,
735 } => {
736 assert_eq!(field, "Name");
737 assert_eq!(address, token_address);
738 assert!(reason.contains("type check failed"));
739 assert_eq!(
740 raw_data,
741 "0x06fdde0300000000000000000000000000000000000000000000000000000000"
742 );
743 }
744 _ => panic!("Expected DecodingError"),
745 }
746 }
747
748 #[rstest]
749 fn test_parse_non_abi_encoded_long_string(
750 non_abi_encoded_long_string_result: Multicall3::Result,
751 non_abi_encoded_token_address: Address,
752 ) {
753 let result = parse_erc20_string_result(
754 &non_abi_encoded_long_string_result,
755 Erc20Field::Name,
756 &non_abi_encoded_token_address,
757 );
758 assert!(result.is_err());
759 match result.unwrap_err() {
760 TokenInfoError::DecodingError {
761 field,
762 address,
763 reason,
764 raw_data,
765 } => {
766 assert_eq!(field, "Name");
767 assert_eq!(address, non_abi_encoded_token_address);
768 assert!(reason.contains("type check failed"));
769 assert_eq!(
770 raw_data,
771 "0x5269636f62616e6b205269736b20536861726500000000000000000000000000"
772 );
773 }
775 _ => panic!("Expected DecodingError"),
776 }
777 }
778
779 #[rstest]
780 fn test_approve_calldata_matches_known_vector() {
781 let spender = address!("E592427A0AEce92De3Edee1F18E0157C05861564");
782 let calldata = ERC20::approveCall {
783 spender,
784 amount: U256::from(1_000_000_000_000_000_000u64),
785 }
786 .abi_encode();
787
788 let expected = format!(
790 "095ea7b3{:0>64}{:0>64}",
791 "e592427a0aece92de3edee1f18e0157c05861564", "0de0b6b3a7640000"
792 );
793 assert_eq!(hex::encode(&calldata), expected);
794 }
795
796 #[rstest]
797 fn test_allowance_calldata_matches_known_vector() {
798 let owner = address!("f39Fd6e51aad88F6F4ce6aB8827279cffFb92266");
799 let spender = address!("E592427A0AEce92De3Edee1F18E0157C05861564");
800 let calldata = ERC20::allowanceCall { owner, spender }.abi_encode();
801
802 let expected = format!(
804 "dd62ed3e{:0>64}{:0>64}",
805 "f39fd6e51aad88f6f4ce6ab8827279cfffb92266", "e592427a0aece92de3edee1f18e0157c05861564"
806 );
807 assert_eq!(hex::encode(&calldata), expected);
808 }
809
810 async fn erc20_contract_against(state: MockRpcState) -> (Erc20Contract, MockRpcState) {
811 let addr = start_mock_rpc_server(state.clone()).await;
812 let rpc_client = Arc::new(BlockchainHttpRpcClient::new(
813 format!("http://{addr}"),
814 None,
815 None,
816 ));
817 (Erc20Contract::new(rpc_client, true), state)
818 }
819
820 #[tokio::test]
821 async fn test_balance_of_against_mock_rpc() {
822 let state = MockRpcState::default().with_call_response("0x70a08231", CALL_BALANCE);
823 let (contract, state) = erc20_contract_against(state).await;
824
825 let balance = contract
826 .balance_of(
827 &address!("82aF49447D8a07e3bd95BD0d56f35241523fBab1"),
828 &address!("f39Fd6e51aad88F6F4ce6aB8827279cffFb92266"),
829 )
830 .await
831 .unwrap();
832
833 assert_eq!(balance, U256::from(500_000_000_000_000_000u64));
834 let requests = state.recorded_requests();
835 assert_eq!(requests.len(), 1);
836 assert_eq!(requests[0]["params"][1], "latest");
837 }
838
839 #[tokio::test]
840 async fn test_allowance_against_mock_rpc() {
841 let state = MockRpcState::default().with_call_response("0xdd62ed3e", CALL_ALLOWANCE);
842 let (contract, state) = erc20_contract_against(state).await;
843
844 let allowance = contract
845 .allowance(
846 &address!("82aF49447D8a07e3bd95BD0d56f35241523fBab1"),
847 &address!("f39Fd6e51aad88F6F4ce6aB8827279cffFb92266"),
848 &address!("E592427A0AEce92De3Edee1F18E0157C05861564"),
849 )
850 .await
851 .unwrap();
852
853 assert_eq!(allowance, U256::from(1_000_000_000_000_000_000u64));
854 let requests = state.recorded_requests();
855 assert_eq!(requests.len(), 1);
856 assert_eq!(requests[0]["params"][1], "latest");
857 }
858}