1use crate::{
9 data_store::{PackageStore, cached_package_store::CachedPackageStore},
10 execution_mode::ExecutionMode,
11 execution_value::ExecutionState,
12 static_programmable_transactions::{
13 execution::context::subst_signature,
14 linkage::{analysis::LinkageAnalyzer, resolved_linkage::ExecutableLinkage},
15 loading::ast::{self as L, Datatype, LoadedFunction, LoadedFunctionInstantiation, Type},
16 },
17};
18use move_binary_format::{
19 errors::VMError,
20 file_format::{Ability, AbilitySet, TypeParameterIndex},
21};
22use move_core_types::{
23 annotated_value,
24 identifier::IdentStr,
25 language_storage::{ModuleId, StructTag},
26 resolver::IntraPackageName,
27 runtime_value::{self, MoveTypeLayout},
28 vm_status::StatusCode,
29};
30use move_vm_runtime::{
31 execution::{self as vm_runtime, vm::MoveVM},
32 runtime::MoveRuntime,
33};
34use std::{cell::OnceCell, marker::PhantomData, rc::Rc};
35use sui_protocol_config::ProtocolConfig;
36use sui_types::{
37 Identifier, SUI_FRAMEWORK_PACKAGE_ID, TypeTag,
38 balance::RESOLVED_BALANCE_STRUCT,
39 base_types::{ObjectID, TxContext},
40 coin::RESOLVED_COIN_STRUCT,
41 error::ExecutionErrorTrait,
42 execution_status::{ExecutionErrorKind, TypeArgumentError},
43 funds_accumulator::RESOLVED_WITHDRAWAL_STRUCT,
44 gas_coin::GasCoin,
45 move_package::{UpgradeCap, UpgradeReceipt, UpgradeTicket},
46 object::Object,
47 type_input::{StructInput, TypeInput},
48};
49
50pub struct Env<'pc, 'vm, 'state, 'linkage, 'extensions, Mode>
51where
52 Mode: ExecutionMode,
53{
54 pub protocol_config: &'pc ProtocolConfig,
55 pub vm: &'vm MoveRuntime,
56 pub state_view: &'state mut dyn ExecutionState,
57 pub linkable_store: &'linkage CachedPackageStore<'state, 'vm>,
58 pub linkage_analysis: &'linkage LinkageAnalyzer,
59 gas_coin_type: OnceCell<Type>,
60 upgrade_ticket_type: OnceCell<Type>,
61 upgrade_receipt_type: OnceCell<Type>,
62 upgrade_cap_type: OnceCell<Type>,
63 tx_context_type: OnceCell<Type>,
64 input_type_resolution_vm: &'linkage MoveVM<'extensions>,
68 _mode: PhantomData<fn() -> Mode>,
69}
70
71macro_rules! get_or_init_ty {
72 ($env:expr, $ident:ident, $tag:expr) => {{
73 let env = $env;
74 if env.$ident.get().is_none() {
75 let tag = $tag;
76 let ty = env.load_type_from_struct(&tag)?;
77 env.$ident.set(ty.clone()).unwrap();
78 }
79 Ok(env.$ident.get().unwrap().clone())
80 }};
81}
82
83impl<'pc, 'vm, 'state, 'linkage, 'extensions, Mode>
84 Env<'pc, 'vm, 'state, 'linkage, 'extensions, Mode>
85where
86 Mode: ExecutionMode,
87{
88 pub fn new(
89 protocol_config: &'pc ProtocolConfig,
90 vm: &'vm MoveRuntime,
91 state_view: &'state mut dyn ExecutionState,
92 linkable_store: &'linkage CachedPackageStore<'state, 'vm>,
93 linkage_analysis: &'linkage LinkageAnalyzer,
94 input_type_resolution_vm: &'linkage MoveVM<'extensions>,
95 ) -> Self {
96 Self {
97 protocol_config,
98 vm,
99 state_view,
100 linkable_store,
101 linkage_analysis,
102 gas_coin_type: OnceCell::new(),
103 upgrade_ticket_type: OnceCell::new(),
104 upgrade_receipt_type: OnceCell::new(),
105 upgrade_cap_type: OnceCell::new(),
106 tx_context_type: OnceCell::new(),
107 input_type_resolution_vm,
108 _mode: PhantomData,
109 }
110 }
111
112 pub fn convert_linked_vm_error(&self, e: VMError, linkage: &ExecutableLinkage) -> Mode::Error {
113 convert_vm_error(e, self.linkable_store, Some(linkage), self.protocol_config)
114 }
115
116 pub fn convert_vm_error(&self, e: VMError) -> Mode::Error {
117 convert_vm_error(e, self.linkable_store, None, self.protocol_config)
118 }
119
120 pub fn convert_type_argument_error(
121 &self,
122 idx: usize,
123 e: VMError,
124 linkage: &ExecutableLinkage,
125 ) -> Mode::Error {
126 use move_core_types::vm_status::StatusCode;
127 let argument_idx = match checked_as!(idx, TypeParameterIndex) {
128 Err(e) => return e.into(),
129 Ok(v) => v,
130 };
131 match e.major_status() {
132 StatusCode::NUMBER_OF_TYPE_ARGUMENTS_MISMATCH => {
133 Mode::Error::from_kind(ExecutionErrorKind::TypeArityMismatch)
134 }
135 StatusCode::EXTERNAL_RESOLUTION_REQUEST_ERROR => {
136 Mode::Error::from_kind(ExecutionErrorKind::TypeArgumentError {
137 argument_idx,
138 kind: TypeArgumentError::TypeNotFound,
139 })
140 }
141 StatusCode::CONSTRAINT_NOT_SATISFIED => {
142 Mode::Error::from_kind(ExecutionErrorKind::TypeArgumentError {
143 argument_idx,
144 kind: TypeArgumentError::ConstraintNotSatisfied,
145 })
146 }
147 _ => self.convert_linked_vm_error(e, linkage),
148 }
149 }
150
151 pub fn fully_annotated_layout(
152 &self,
153 ty: &Type,
154 ) -> Result<annotated_value::MoveTypeLayout, Mode::Error> {
155 let tag: TypeTag = ty.clone().try_into().map_err(|s| {
156 Mode::Error::new_with_source(ExecutionErrorKind::VMInvariantViolation, s)
157 })?;
158 let objects = tag.all_addresses();
159 let tag_linkage = ExecutableLinkage::type_linkage::<_, Mode::Error>(
160 self.linkage_analysis.config().clone(),
161 objects.into_iter().map(ObjectID::from),
162 self.linkable_store,
163 )?;
164 self.input_type_resolution_vm
165 .annotated_type_layout(&tag)
166 .map_err(|e| self.convert_linked_vm_error(e, &tag_linkage))
167 }
168
169 pub fn runtime_layout(&self, ty: &Type) -> Result<runtime_value::MoveTypeLayout, Mode::Error> {
170 let tag: TypeTag = ty.clone().try_into().map_err(|s| {
171 Mode::Error::new_with_source(ExecutionErrorKind::VMInvariantViolation, s)
172 })?;
173 let objects = tag.all_addresses();
174 let tag_linkage = ExecutableLinkage::type_linkage::<_, Mode::Error>(
175 self.linkage_analysis.config().clone(),
176 objects.into_iter().map(ObjectID::from),
177 self.linkable_store,
178 )?;
179 self.input_type_resolution_vm
180 .runtime_type_layout(&tag)
181 .map_err(|e| self.convert_linked_vm_error(e, &tag_linkage))
182 }
183
184 pub fn load_framework_function(
185 &self,
186 module: &IdentStr,
187 function: &IdentStr,
188 type_arguments: Vec<Type>,
189 ) -> Result<LoadedFunction, Mode::Error> {
190 self.load_function(
191 SUI_FRAMEWORK_PACKAGE_ID,
192 module.to_string(),
193 function.to_string(),
194 type_arguments,
195 )
196 }
197
198 pub fn load_function(
199 &self,
200 package: ObjectID,
201 module: String,
202 function: String,
203 type_arguments: Vec<Type>,
204 ) -> Result<LoadedFunction, Mode::Error> {
205 let module = to_identifier(module)?;
206 let name = to_identifier(function)?;
207
208 let linkage = self.linkage_analysis.compute_call_linkage::<Mode::Error>(
209 &package,
210 module.as_ident_str(),
211 name.as_ident_str(),
212 &type_arguments,
213 self.linkable_store,
214 )?;
215
216 let Some(original_id) = linkage.0.resolve_to_original_id(&package) else {
217 invariant_violation!(
218 "Package ID {:?} is not found in linkage generated for that package",
219 package
220 );
221 };
222 let version_mid = ModuleId::new(package.into(), module.clone());
223 let original_mid = ModuleId::new(original_id.into(), module);
224 let loaded_type_arguments = type_arguments
225 .iter()
226 .enumerate()
227 .map(|(idx, ty)| self.load_vm_type_argument_from_adapter_type(idx, ty))
228 .collect::<Result<Vec<_>, _>>()?;
229 let vm = self
233 .vm
234 .make_vm(
235 &self.linkable_store.package_store,
236 linkage.linkage_context::<Mode::Error>()?,
237 )
238 .map_err(|e| self.convert_linked_vm_error(e, &linkage))?;
239 let runtime_signature = vm
240 .function_information(&original_mid, name.as_ident_str(), &loaded_type_arguments)
241 .map_err(|e| {
242 if e.major_status() == StatusCode::EXTERNAL_RESOLUTION_REQUEST_ERROR {
243 Mode::Error::new_with_source(
244 ExecutionErrorKind::FunctionNotFound,
245 format!(
246 "Could not resolve function '{}' in module '{}'",
247 name, &version_mid,
248 ),
249 )
250 } else {
251 self.convert_linked_vm_error(e, &linkage)
252 }
253 })?;
254 let runtime_signature = subst_signature(runtime_signature, &loaded_type_arguments)
255 .map_err(|e| self.convert_linked_vm_error(e, &linkage))?;
256 let parameters = runtime_signature
257 .parameters
258 .into_iter()
259 .map(|ty| self.adapter_type_from_vm_type(&vm, &ty))
260 .collect::<Result<Vec<_>, _>>()?;
261 let return_ = runtime_signature
262 .return_
263 .into_iter()
264 .map(|ty| self.adapter_type_from_vm_type(&vm, &ty))
265 .collect::<Result<Vec<_>, _>>()?;
266 let signature = LoadedFunctionInstantiation {
267 parameters,
268 return_,
269 };
270 Ok(LoadedFunction {
271 version_mid,
272 original_mid,
273 name,
274 type_arguments,
275 signature,
276 linkage,
277 instruction_length: runtime_signature.instruction_count,
278 definition_index: runtime_signature.index,
279 visibility: runtime_signature.visibility,
280 is_entry: runtime_signature.is_entry,
281 is_native: runtime_signature.is_native,
282 })
283 }
284
285 pub fn load_type_input(&self, idx: usize, ty: TypeInput) -> Result<Type, Mode::Error> {
286 let vm_type = self.load_vm_type_from_type_input(idx, ty)?;
287 self.adapter_type_from_vm_type(self.input_type_resolution_vm, &vm_type)
288 }
289
290 pub fn load_type_tag(&self, idx: usize, ty: &TypeTag) -> Result<Type, Mode::Error> {
291 let vm_type = self.load_vm_type_from_type_tag(Some(idx), ty)?;
292 self.adapter_type_from_vm_type(self.input_type_resolution_vm, &vm_type)
293 }
294
295 pub fn load_type_from_struct(&self, tag: &StructTag) -> Result<Type, Mode::Error> {
297 let vm_type =
298 self.load_vm_type_from_type_tag(None, &TypeTag::Struct(Box::new(tag.clone())))?;
299 self.adapter_type_from_vm_type(self.input_type_resolution_vm, &vm_type)
300 }
301
302 pub fn type_layout_for_struct(&self, tag: &StructTag) -> Result<MoveTypeLayout, Mode::Error> {
303 let ty: Type = self.load_type_from_struct(tag)?;
304 self.runtime_layout(&ty)
305 }
306
307 pub fn gas_coin_type(&self) -> Result<Type, Mode::Error> {
308 get_or_init_ty!(self, gas_coin_type, GasCoin::type_())
309 }
310
311 pub fn upgrade_ticket_type(&self) -> Result<Type, Mode::Error> {
312 get_or_init_ty!(self, upgrade_ticket_type, UpgradeTicket::type_())
313 }
314
315 pub fn upgrade_receipt_type(&self) -> Result<Type, Mode::Error> {
316 get_or_init_ty!(self, upgrade_receipt_type, UpgradeReceipt::type_())
317 }
318
319 pub fn upgrade_cap_type(&self) -> Result<Type, Mode::Error> {
320 get_or_init_ty!(self, upgrade_cap_type, UpgradeCap::type_())
321 }
322
323 pub fn tx_context_type(&self) -> Result<Type, Mode::Error> {
324 get_or_init_ty!(self, tx_context_type, TxContext::type_())
325 }
326
327 pub fn coin_type(&self, inner_type: Type) -> Result<Type, Mode::Error> {
328 const COIN_ABILITIES: AbilitySet =
329 AbilitySet::singleton(Ability::Key).union(AbilitySet::singleton(Ability::Store));
330 let (a, m, n) = RESOLVED_COIN_STRUCT;
331 let module = ModuleId::new(*a, m.to_owned());
332 Ok(Type::Datatype(Rc::new(Datatype {
333 abilities: COIN_ABILITIES,
334 module,
335 name: n.to_owned(),
336 type_arguments: vec![inner_type],
337 })))
338 }
339
340 pub fn balance_type(&self, inner_type: Type) -> Result<Type, Mode::Error> {
341 const BALANCE_ABILITIES: AbilitySet = AbilitySet::singleton(Ability::Store);
342 let (a, m, n) = RESOLVED_BALANCE_STRUCT;
343 let module = ModuleId::new(*a, m.to_owned());
344 Ok(Type::Datatype(Rc::new(Datatype {
345 abilities: BALANCE_ABILITIES,
346 module,
347 name: n.to_owned(),
348 type_arguments: vec![inner_type],
349 })))
350 }
351
352 pub fn withdrawal_type(&self, inner_type: Type) -> Result<Type, Mode::Error> {
353 const WITHDRAWAL_ABILITIES: AbilitySet = AbilitySet::singleton(Ability::Drop);
354 let (a, m, n) = RESOLVED_WITHDRAWAL_STRUCT;
355 let module = ModuleId::new(*a, m.to_owned());
356 Ok(Type::Datatype(Rc::new(Datatype {
357 abilities: WITHDRAWAL_ABILITIES,
358 module,
359 name: n.to_owned(),
360 type_arguments: vec![inner_type],
361 })))
362 }
363
364 pub fn vector_type(&self, element_type: Type) -> Result<Type, Mode::Error> {
365 let abilities = AbilitySet::polymorphic_abilities(
366 AbilitySet::VECTOR,
367 [false],
368 [element_type.abilities()],
369 )
370 .map_err(|e| {
371 Mode::Error::new_with_source(ExecutionErrorKind::VMInvariantViolation, e.to_string())
372 })?;
373 Ok(Type::Vector(Rc::new(L::Vector {
374 abilities,
375 element_type,
376 })))
377 }
378
379 pub fn read_object(&self, id: &ObjectID) -> Result<&Object, Mode::Error> {
380 let Some(obj) = self.state_view.read_object(id) else {
381 invariant_violation!("Object {:?} does not exist", id);
383 };
384 Ok(obj)
385 }
386
387 pub fn load_vm_type_argument_from_adapter_type(
389 &self,
390 idx: usize,
391 ty: &Type,
392 ) -> Result<vm_runtime::Type, Mode::Error> {
393 self.load_vm_type_from_adapter_type(Some(idx), ty)
394 }
395
396 fn load_vm_type_from_adapter_type(
397 &self,
398 type_arg_idx: Option<usize>,
399 ty: &Type,
400 ) -> Result<vm_runtime::Type, Mode::Error> {
401 let tag: TypeTag = ty.clone().try_into().map_err(|s| {
402 Mode::Error::new_with_source(ExecutionErrorKind::VMInvariantViolation, s)
403 })?;
404 self.load_vm_type_from_type_tag(type_arg_idx, &tag)
405 }
406
407 fn load_vm_type_from_type_tag(
409 &self,
410 type_arg_idx: Option<usize>,
411 tag: &TypeTag,
412 ) -> Result<vm_runtime::Type, Mode::Error> {
413 fn execution_error<Mode: ExecutionMode>(
414 env: &Env<Mode>,
415 type_arg_idx: Option<usize>,
416 e: VMError,
417 linkage: &ExecutableLinkage,
418 ) -> Mode::Error {
419 if let Some(idx) = type_arg_idx {
420 env.convert_type_argument_error(idx, e, linkage)
421 } else {
422 env.convert_linked_vm_error(e, linkage)
423 }
424 }
425
426 let objects = tag.all_addresses();
427
428 let tag_linkage = ExecutableLinkage::type_linkage::<_, Mode::Error>(
429 self.linkage_analysis.config().clone(),
430 objects.iter().map(|a| ObjectID::from(*a)),
431 self.linkable_store,
432 )?;
433 let ty = self
434 .input_type_resolution_vm
435 .load_type(tag)
436 .map_err(|e| execution_error(self, type_arg_idx, e, &tag_linkage))?;
437 Ok(ty)
438 }
439
440 pub(crate) fn adapter_type_from_vm_type(
442 &self,
443 vm: &MoveVM,
444 vm_type: &vm_runtime::Type,
445 ) -> Result<Type, Mode::Error> {
446 use vm_runtime as VRT;
447
448 Ok(match vm_type {
449 VRT::Type::Bool => Type::Bool,
450 VRT::Type::U8 => Type::U8,
451 VRT::Type::U16 => Type::U16,
452 VRT::Type::U32 => Type::U32,
453 VRT::Type::U64 => Type::U64,
454 VRT::Type::U128 => Type::U128,
455 VRT::Type::U256 => Type::U256,
456 VRT::Type::Address => Type::Address,
457 VRT::Type::Signer => Type::Signer,
458
459 VRT::Type::Reference(ref_ty) => {
460 let inner_ty = self.adapter_type_from_vm_type(vm, ref_ty)?;
461 Type::Reference(false, Rc::new(inner_ty))
462 }
463 VRT::Type::MutableReference(ref_ty) => {
464 let inner_ty = self.adapter_type_from_vm_type(vm, ref_ty)?;
465 Type::Reference(true, Rc::new(inner_ty))
466 }
467
468 VRT::Type::Vector(inner) => {
469 let element_type = self.adapter_type_from_vm_type(vm, inner)?;
470 self.vector_type(element_type)?
471 }
472 VRT::Type::Datatype(_) => {
473 let type_information = vm
474 .type_information(vm_type)
475 .map_err(|e| self.convert_vm_error(e))?;
476 let Some(data_type_info) = type_information.datatype_info else {
477 invariant_violation!("Expected datatype info for datatype type {:?}", vm_type);
478 };
479 let datatype = Datatype {
480 abilities: type_information.abilities,
481 module: ModuleId::new(data_type_info.defining_id, data_type_info.module_name),
482 name: data_type_info.type_name,
483 type_arguments: vec![],
484 };
485 Type::Datatype(Rc::new(datatype))
486 }
487 ty @ VRT::Type::DatatypeInstantiation(inst) => {
488 let (_, type_arguments) = &**inst;
489 let type_information = vm
490 .type_information(ty)
491 .map_err(|e| self.convert_vm_error(e))?;
492 let Some(data_type_info) = type_information.datatype_info else {
493 invariant_violation!("Expected datatype info for datatype type {:?}", vm_type);
494 };
495
496 let abilities = type_information.abilities;
497 let module = ModuleId::new(data_type_info.defining_id, data_type_info.module_name);
498 let name = data_type_info.type_name;
499 let type_arguments = type_arguments
500 .iter()
501 .map(|t| self.adapter_type_from_vm_type(vm, t))
502 .collect::<Result<Vec<_>, _>>()?;
503
504 Type::Datatype(Rc::new(Datatype {
505 abilities,
506 module,
507 name,
508 type_arguments,
509 }))
510 }
511
512 VRT::Type::TyParam(_) => {
513 invariant_violation!(
514 "Unexpected type parameter in VM type: {:?}. This should not happen as we should \
515 have resolved all type parameters before this point.",
516 vm_type
517 );
518 }
519 })
520 }
521
522 fn load_vm_type_from_type_input(
526 &self,
527 type_arg_idx: usize,
528 ty: TypeInput,
529 ) -> Result<vm_runtime::Type, Mode::Error> {
530 fn to_type_tag_internal<Mode: ExecutionMode>(
531 env: &Env<Mode>,
532 type_arg_idx: usize,
533 ty: TypeInput,
534 ) -> Result<TypeTag, Mode::Error> {
535 Ok(match ty {
536 TypeInput::Bool => TypeTag::Bool,
537 TypeInput::U8 => TypeTag::U8,
538 TypeInput::U16 => TypeTag::U16,
539 TypeInput::U32 => TypeTag::U32,
540 TypeInput::U64 => TypeTag::U64,
541 TypeInput::U128 => TypeTag::U128,
542 TypeInput::U256 => TypeTag::U256,
543 TypeInput::Address => TypeTag::Address,
544 TypeInput::Signer => TypeTag::Signer,
545 TypeInput::Vector(type_input) => {
546 let inner = to_type_tag_internal(env, type_arg_idx, *type_input)?;
547 TypeTag::Vector(Box::new(inner))
548 }
549 TypeInput::Struct(struct_input) => {
550 let StructInput {
551 address,
552 module,
553 name,
554 type_params,
555 } = *struct_input;
556
557 let pkg = env
558 .linkable_store
559 .get_package(&address.into())
560 .ok()
561 .flatten()
562 .ok_or_else(|| {
563 let argument_idx = match checked_as!(type_arg_idx, u16) {
564 Err(e) => return e.into(),
565 Ok(v) => v,
566 };
567 Mode::Error::from_kind(ExecutionErrorKind::TypeArgumentError {
568 argument_idx,
569 kind: TypeArgumentError::TypeNotFound,
570 })
571 })?;
572 let module = to_identifier(module)?;
573 let name = to_identifier(name)?;
574 let tid = IntraPackageName {
575 module_name: module,
576 type_name: name,
577 };
578 let Some(resolved_address) = pkg.type_origin_table().get(&tid).cloned() else {
579 return Err(Mode::Error::from_kind(
580 ExecutionErrorKind::TypeArgumentError {
581 argument_idx: checked_as!(type_arg_idx, u16)?,
582 kind: TypeArgumentError::TypeNotFound,
583 },
584 ));
585 };
586
587 let tys = type_params
588 .into_iter()
589 .map(|tp| to_type_tag_internal(env, type_arg_idx, tp))
590 .collect::<Result<Vec<_>, _>>()?;
591 TypeTag::Struct(Box::new(StructTag {
592 address: resolved_address,
593 module: tid.module_name,
594 name: tid.type_name,
595 type_params: tys,
596 }))
597 }
598 })
599 }
600 let tag = to_type_tag_internal(self, type_arg_idx, ty)?;
601 self.load_vm_type_from_type_tag(Some(type_arg_idx), &tag)
602 }
603}
604
605fn to_identifier<E: ExecutionErrorTrait>(name: String) -> Result<Identifier, E> {
606 Identifier::new(name)
607 .map_err(|e| E::new_with_source(ExecutionErrorKind::VMInvariantViolation, e.to_string()))
608}
609
610fn convert_vm_error<E: ExecutionErrorTrait>(
611 error: VMError,
612 store: &dyn PackageStore,
613 linkage: Option<&ExecutableLinkage>,
614 _protocol_config: &ProtocolConfig,
615) -> E {
616 use crate::error::convert_vm_error_impl;
617 convert_vm_error_impl(
618 error,
619 &|id| {
620 debug_assert!(
621 linkage.is_some(),
622 "Linkage should be set anywhere where runtime errors may occur in order to resolve abort locations to package IDs"
623 );
624 linkage
625 .and_then(|linkage| {
626 linkage
627 .0
628 .linkage
629 .get(&(*id.address()).into())
630 .map(|new_id| ModuleId::new((*new_id).into(), id.name().to_owned()))
631 })
632 .unwrap_or_else(|| id.clone())
633 },
634 &|id, function| {
636 debug_assert!(
637 linkage.is_some(),
638 "Linkage should be set anywhere where runtime errors may occur in order to resolve abort locations to package IDs"
639 );
640 linkage.and_then(|linkage| {
641 let version_id = linkage
642 .0
643 .linkage
644 .get(&(*id.address()).into())
645 .cloned()
646 .unwrap_or_else(|| ObjectID::from_address(*id.address()));
647 store.get_package(&version_id).ok().flatten().and_then(|p| {
648 p.modules().get(id).map(|module| {
649 let module = module.compiled_module();
650 let fdef = module.function_def_at(function);
651 let fhandle = module.function_handle_at(fdef.function);
652 module.identifier_at(fhandle.name).to_string()
653 })
654 })
655 })
656 },
657 )
658 .into()
659}