1use std::collections::BTreeMap;
6
7use move_binary_format::errors::{PartialVMError, PartialVMResult};
8use move_core_types::{
9 gas_algebra::{AbstractMemorySize, InternalGas, NumArgs, NumBytes},
10 language_storage::ModuleId,
11 vm_status::StatusCode,
12};
13use move_vm_profiler::GasProfiler;
14use move_vm_types::{
15 gas::{GasMeter, SimpleInstruction},
16 loaded_data::runtime_types::Type,
17 views::{TypeView, ValueView},
18};
19use once_cell::sync::Lazy;
20
21use super::gas_predicates::native_function_threshold_exceeded;
22use crate::gas_model::units_types::{CostTable, Gas, GasCost};
23
24pub const VM_FLAT_FEE: Gas = Gas::new(8_000);
26
27pub const CONST_SIZE: AbstractMemorySize = AbstractMemorySize::new(16);
29
30pub const REFERENCE_SIZE: AbstractMemorySize = AbstractMemorySize::new(8);
32
33pub const STRUCT_SIZE: AbstractMemorySize = AbstractMemorySize::new(2);
35
36pub const VEC_SIZE: AbstractMemorySize = AbstractMemorySize::new(8);
38
39pub const MIN_EXISTS_DATA_SIZE: AbstractMemorySize = AbstractMemorySize::new(100);
42
43pub static ZERO_COST_SCHEDULE: Lazy<CostTable> = Lazy::new(zero_cost_schedule);
44
45pub static INITIAL_COST_SCHEDULE: Lazy<CostTable> = Lazy::new(initial_cost_schedule_v1);
46
47#[derive(Debug)]
54pub struct GasStatus {
55 pub gas_model_version: u64,
56 cost_table: CostTable,
57 gas_left: InternalGas,
58 gas_price: u64,
59 initial_budget: InternalGas,
60 charge: bool,
61
62 stack_height_high_water_mark: u64,
64 stack_height_current: u64,
65 stack_height_next_tier_start: Option<u64>,
66 stack_height_current_tier_mult: u64,
67
68 stack_size_high_water_mark: u64,
70 stack_size_current: u64,
71 stack_size_next_tier_start: Option<u64>,
72 stack_size_current_tier_mult: u64,
73
74 instructions_executed: u64,
76 instructions_next_tier_start: Option<u64>,
77 instructions_current_tier_mult: u64,
78
79 profiler: Option<GasProfiler>,
80 num_native_calls: u64,
81}
82
83impl GasStatus {
84 pub fn new(cost_table: CostTable, budget: u64, gas_price: u64, gas_model_version: u64) -> Self {
90 assert!(gas_price > 0, "gas price cannot be 0");
91 let budget_in_unit = budget / gas_price;
92 let gas_left = Self::to_internal_units(budget_in_unit);
93 let (stack_height_current_tier_mult, stack_height_next_tier_start) =
94 cost_table.stack_height_tier(0);
95 let (stack_size_current_tier_mult, stack_size_next_tier_start) =
96 cost_table.stack_size_tier(0);
97 let (instructions_current_tier_mult, instructions_next_tier_start) =
98 cost_table.instruction_tier(0);
99 Self {
100 gas_model_version,
101 gas_left,
102 gas_price,
103 initial_budget: gas_left,
104 cost_table,
105 charge: true,
106 stack_height_high_water_mark: 0,
107 stack_height_current: 0,
108 stack_size_high_water_mark: 0,
109 stack_size_current: 0,
110 instructions_executed: 0,
111 stack_height_current_tier_mult,
112 stack_size_current_tier_mult,
113 instructions_current_tier_mult,
114 stack_height_next_tier_start,
115 stack_size_next_tier_start,
116 instructions_next_tier_start,
117 profiler: None,
118 num_native_calls: 0,
119 }
120 }
121
122 pub fn new_unmetered() -> Self {
127 Self {
128 gas_model_version: 1,
129 gas_left: InternalGas::new(0),
130 gas_price: 1,
131 initial_budget: InternalGas::new(0),
132 cost_table: ZERO_COST_SCHEDULE.clone(),
133 charge: false,
134 stack_height_high_water_mark: 0,
135 stack_height_current: 0,
136 stack_size_high_water_mark: 0,
137 stack_size_current: 0,
138 instructions_executed: 0,
139 stack_height_current_tier_mult: 0,
140 stack_size_current_tier_mult: 0,
141 instructions_current_tier_mult: 0,
142 stack_height_next_tier_start: None,
143 stack_size_next_tier_start: None,
144 instructions_next_tier_start: None,
145 profiler: None,
146 num_native_calls: 0,
147 }
148 }
149
150 const INTERNAL_UNIT_MULTIPLIER: u64 = 1000;
151
152 fn to_internal_units(val: u64) -> InternalGas {
153 InternalGas::new(val * Self::INTERNAL_UNIT_MULTIPLIER)
154 }
155
156 #[expect(dead_code)]
157 fn to_nanos(&self, val: InternalGas) -> u64 {
158 let gas: Gas = InternalGas::to_unit_round_down(val);
159 u64::from(gas) * self.gas_price
160 }
161
162 pub fn push_stack(&mut self, pushes: u64) -> PartialVMResult<()> {
163 match self.stack_height_current.checked_add(pushes) {
164 None => return Err(PartialVMError::new(StatusCode::ARITHMETIC_OVERFLOW)),
166 Some(new_height) => {
167 if new_height > self.stack_height_high_water_mark {
168 self.stack_height_high_water_mark = new_height;
169 }
170 self.stack_height_current = new_height;
171 }
172 }
173
174 if let Some(stack_height_tier_next) = self.stack_height_next_tier_start {
175 if self.stack_height_current > stack_height_tier_next {
176 let (next_mul, next_tier) =
177 self.cost_table.stack_height_tier(self.stack_height_current);
178 self.stack_height_current_tier_mult = next_mul;
179 self.stack_height_next_tier_start = next_tier;
180 }
181 }
182
183 Ok(())
184 }
185
186 pub fn pop_stack(&mut self, pops: u64) {
187 self.stack_height_current = self.stack_height_current.saturating_sub(pops);
188 }
189
190 pub fn increase_instruction_count(&mut self, amount: u64) -> PartialVMResult<()> {
191 match self.instructions_executed.checked_add(amount) {
192 None => return Err(PartialVMError::new(StatusCode::PC_OVERFLOW)),
193 Some(new_pc) => {
194 self.instructions_executed = new_pc;
195 }
196 }
197
198 if let Some(instr_tier_next) = self.instructions_next_tier_start {
199 if self.instructions_executed > instr_tier_next {
200 let (instr_cost, next_tier) =
201 self.cost_table.instruction_tier(self.instructions_executed);
202 self.instructions_current_tier_mult = instr_cost;
203 self.instructions_next_tier_start = next_tier;
204 }
205 }
206
207 Ok(())
208 }
209
210 pub fn increase_stack_size(&mut self, size_amount: u64) -> PartialVMResult<()> {
211 match self.stack_size_current.checked_add(size_amount) {
212 None => return Err(PartialVMError::new(StatusCode::ARITHMETIC_OVERFLOW)),
213 Some(new_size) => {
214 if new_size > self.stack_size_high_water_mark {
215 self.stack_size_high_water_mark = new_size;
216 }
217 self.stack_size_current = new_size;
218 }
219 }
220
221 if let Some(stack_size_tier_next) = self.stack_size_next_tier_start {
222 if self.stack_size_current > stack_size_tier_next {
223 let (next_mul, next_tier) =
224 self.cost_table.stack_size_tier(self.stack_size_current);
225 self.stack_size_current_tier_mult = next_mul;
226 self.stack_size_next_tier_start = next_tier;
227 }
228 }
229
230 Ok(())
231 }
232
233 pub fn decrease_stack_size(&mut self, size_amount: u64) {
234 let new_size = self.stack_size_current.saturating_sub(size_amount);
235 if new_size > self.stack_size_high_water_mark {
236 self.stack_size_high_water_mark = new_size;
237 }
238 self.stack_size_current = new_size;
239 }
240
241 pub fn charge(
244 &mut self,
245 num_instructions: u64,
246 pushes: u64,
247 pops: u64,
248 incr_size: u64,
249 _decr_size: u64,
250 ) -> PartialVMResult<()> {
251 self.push_stack(pushes)?;
252 self.increase_instruction_count(num_instructions)?;
253 self.increase_stack_size(incr_size)?;
254
255 self.deduct_gas(
256 GasCost::new(
257 self.instructions_current_tier_mult
258 .checked_mul(num_instructions)
259 .ok_or_else(|| PartialVMError::new(StatusCode::ARITHMETIC_OVERFLOW))?,
260 self.stack_size_current_tier_mult
261 .checked_mul(incr_size)
262 .ok_or_else(|| PartialVMError::new(StatusCode::ARITHMETIC_OVERFLOW))?,
263 self.stack_height_current_tier_mult
264 .checked_mul(pushes)
265 .ok_or_else(|| PartialVMError::new(StatusCode::ARITHMETIC_OVERFLOW))?,
266 )
267 .total_internal(),
268 )?;
269
270 self.pop_stack(pops);
272 Ok(())
273 }
274
275 pub fn cost_table(&self) -> &CostTable {
277 &self.cost_table
278 }
279
280 pub fn remaining_gas(&self) -> Gas {
282 self.gas_left.to_unit_round_down()
283 }
284
285 pub fn deduct_gas(&mut self, amount: InternalGas) -> PartialVMResult<()> {
287 if !self.charge {
288 return Ok(());
289 }
290
291 match self.gas_left.checked_sub(amount) {
292 Some(gas_left) => {
293 self.gas_left = gas_left;
294 Ok(())
295 }
296 None => {
297 self.gas_left = InternalGas::new(0);
298 Err(PartialVMError::new(StatusCode::OUT_OF_GAS))
299 }
300 }
301 }
302
303 fn deduct_units(&mut self, amount: u64) -> PartialVMResult<()> {
305 self.deduct_gas(InternalGas::new(amount))
306 }
307
308 pub fn set_metering(&mut self, enabled: bool) {
309 self.charge = enabled
310 }
311
312 pub fn gas_used_pre_gas_price(&self) -> u64 {
315 let gas: Gas = match self.initial_budget.checked_sub(self.gas_left) {
316 Some(val) => InternalGas::to_unit_round_down(val),
317 None => InternalGas::to_unit_round_down(self.initial_budget),
318 };
319 u64::from(gas)
320 }
321
322 pub fn charge_bytes(&mut self, size: usize, cost_per_byte: u64) -> PartialVMResult<()> {
326 let computation_cost = size as u64 * cost_per_byte;
327 self.deduct_units(computation_cost)
328 }
329
330 fn abstract_memory_size(&self, val: impl ValueView) -> AbstractMemorySize {
331 val.abstract_memory_size()
332 }
333
334 pub fn gas_price(&self) -> u64 {
335 self.gas_price
336 }
337
338 pub fn stack_height_high_water_mark(&self) -> u64 {
339 self.stack_height_high_water_mark
340 }
341
342 pub fn stack_size_high_water_mark(&self) -> u64 {
343 self.stack_size_high_water_mark
344 }
345
346 pub fn instructions_executed(&self) -> u64 {
347 self.instructions_executed
348 }
349}
350
351fn get_simple_instruction_stack_change(
354 instr: SimpleInstruction,
355) -> (u64, u64, AbstractMemorySize, AbstractMemorySize) {
356 use SimpleInstruction::*;
357
358 match instr {
359 Nop | Ret => (0, 0, 0.into(), 0.into()),
361 BrTrue | BrFalse => (1, 0, Type::Bool.size(), 0.into()),
362 Branch => (0, 0, 0.into(), 0.into()),
363 LdU8 => (0, 1, 0.into(), Type::U8.size()),
364 LdU16 => (0, 1, 0.into(), Type::U16.size()),
365 LdU32 => (0, 1, 0.into(), Type::U32.size()),
366 LdU64 => (0, 1, 0.into(), Type::U64.size()),
367 LdU128 => (0, 1, 0.into(), Type::U128.size()),
368 LdU256 => (0, 1, 0.into(), Type::U256.size()),
369 LdTrue | LdFalse => (0, 1, 0.into(), Type::Bool.size()),
370 FreezeRef => (1, 1, REFERENCE_SIZE, REFERENCE_SIZE),
371 ImmBorrowLoc | MutBorrowLoc => (0, 1, 0.into(), REFERENCE_SIZE),
372 ImmBorrowField | MutBorrowField | ImmBorrowFieldGeneric | MutBorrowFieldGeneric => {
373 (1, 1, REFERENCE_SIZE, REFERENCE_SIZE)
374 }
375 CastU8 => (1, 1, Type::U8.size(), Type::U8.size()),
378 CastU16 => (1, 1, Type::U8.size(), Type::U16.size()),
379 CastU32 => (1, 1, Type::U8.size(), Type::U32.size()),
380 CastU64 => (1, 1, Type::U8.size(), Type::U64.size()),
381 CastU128 => (1, 1, Type::U8.size(), Type::U128.size()),
382 CastU256 => (1, 1, Type::U8.size(), Type::U256.size()),
383 Add | Sub | Mul | Mod | Div => (2, 1, Type::U8.size() + Type::U8.size(), Type::U256.size()),
386 BitOr | BitAnd | Xor => (2, 1, Type::U8.size() + Type::U8.size(), Type::U256.size()),
387 Shl | Shr => (2, 1, Type::U8.size() + Type::U8.size(), Type::U256.size()),
388 Or | And => (
389 2,
390 1,
391 Type::Bool.size() + Type::Bool.size(),
392 Type::Bool.size(),
393 ),
394 Lt | Gt | Le | Ge => (2, 1, Type::U8.size() + Type::U8.size(), Type::Bool.size()),
395 Not => (1, 1, Type::Bool.size(), Type::Bool.size()),
396 Abort => (1, 0, Type::U64.size(), 0.into()),
397 }
398}
399
400impl GasMeter for GasStatus {
401 fn charge_simple_instr(&mut self, instr: SimpleInstruction) -> PartialVMResult<()> {
403 let (pops, pushes, pop_size, push_size) = get_simple_instruction_stack_change(instr);
404 self.charge(1, pushes, pops, push_size.into(), pop_size.into())
405 }
406
407 fn charge_pop(&mut self, popped_val: impl ValueView) -> PartialVMResult<()> {
408 self.charge(1, 0, 1, 0, self.abstract_memory_size(popped_val).into())
409 }
410
411 fn charge_native_function(
412 &mut self,
413 amount: InternalGas,
414 ret_vals: Option<impl ExactSizeIterator<Item = impl ValueView>>,
415 ) -> PartialVMResult<()> {
416 let pushes = ret_vals
419 .as_ref()
420 .map(|ret_vals| ret_vals.len())
421 .unwrap_or(0) as u64;
422 let size_increase = ret_vals
424 .map(|ret_vals| {
425 ret_vals.fold(AbstractMemorySize::zero(), |acc, elem| {
426 acc + self.abstract_memory_size(elem)
427 })
428 })
429 .unwrap_or_else(AbstractMemorySize::zero);
430 self.num_native_calls = self.num_native_calls.saturating_add(1);
431 if native_function_threshold_exceeded(self.gas_model_version, self.num_native_calls) {
432 self.charge(amount.into(), pushes, 0, size_increase.into(), 0)
440 } else {
441 self.charge(0, pushes, 0, size_increase.into(), 0)?;
445 self.deduct_gas(amount)
447 }
448 }
449
450 fn charge_native_function_before_execution(
451 &mut self,
452 _ty_args: impl ExactSizeIterator<Item = impl TypeView>,
453 args: impl ExactSizeIterator<Item = impl ValueView>,
454 ) -> PartialVMResult<()> {
455 let pops = args.len() as u64;
458 let stack_reduction_size = args.fold(AbstractMemorySize::new(pops), |acc, elem| {
460 acc + self.abstract_memory_size(elem)
461 });
462 self.charge(1, 0, pops, 0, stack_reduction_size.into())
466 }
467
468 fn charge_call(
469 &mut self,
470 _module_id: &ModuleId,
471 _func_name: &str,
472 args: impl ExactSizeIterator<Item = impl ValueView>,
473 _num_locals: NumArgs,
474 ) -> PartialVMResult<()> {
475 let pops = args.len() as u64;
477 let stack_reduction_size = args.fold(AbstractMemorySize::new(0), |acc, elem| {
481 acc + self.abstract_memory_size(elem)
482 });
483 self.charge(1, 0, pops, 0, stack_reduction_size.into())
484 }
485
486 fn charge_call_generic(
487 &mut self,
488 _module_id: &ModuleId,
489 _func_name: &str,
490 _ty_args: impl ExactSizeIterator<Item = impl TypeView>,
491 args: impl ExactSizeIterator<Item = impl ValueView>,
492 _num_locals: NumArgs,
493 ) -> PartialVMResult<()> {
494 let pops = args.len() as u64;
497 let stack_reduction_size = args.fold(AbstractMemorySize::new(0), |acc, elem| {
499 acc + self.abstract_memory_size(elem)
500 });
501 self.charge(1, 0, pops, 0, stack_reduction_size.into())
505 }
506
507 fn charge_ld_const(&mut self, size: NumBytes) -> PartialVMResult<()> {
508 self.charge(1, 1, 0, u64::from(size), 0)
510 }
511
512 fn charge_ld_const_after_deserialization(
513 &mut self,
514 _val: impl ValueView,
515 ) -> PartialVMResult<()> {
516 Ok(())
519 }
520
521 fn charge_copy_loc(&mut self, val: impl ValueView) -> PartialVMResult<()> {
522 self.charge(1, 1, 0, self.abstract_memory_size(val).into(), 0)
524 }
525
526 fn charge_move_loc(&mut self, val: impl ValueView) -> PartialVMResult<()> {
527 self.charge(1, 1, 0, self.abstract_memory_size(val).into(), 0)
531 }
532
533 fn charge_store_loc(&mut self, val: impl ValueView) -> PartialVMResult<()> {
534 self.charge(1, 0, 1, 0, self.abstract_memory_size(val).into())
538 }
539
540 fn charge_pack(
541 &mut self,
542 _is_generic: bool,
543 args: impl ExactSizeIterator<Item = impl ValueView>,
544 ) -> PartialVMResult<()> {
545 let num_fields = args.len() as u64;
547 self.charge(1, 1, num_fields, STRUCT_SIZE.into(), 0)
551 }
552
553 fn charge_unpack(
554 &mut self,
555 _is_generic: bool,
556 args: impl ExactSizeIterator<Item = impl ValueView>,
557 ) -> PartialVMResult<()> {
558 let num_fields = args.len() as u64;
560 self.charge(1, num_fields, 1, 0, STRUCT_SIZE.into())
561 }
562
563 fn charge_variant_switch(&mut self, val: impl ValueView) -> PartialVMResult<()> {
564 self.charge(1, 0, 1, 0, self.abstract_memory_size(val).into())
566 }
567
568 fn charge_read_ref(&mut self, ref_val: impl ValueView) -> PartialVMResult<()> {
569 self.charge(
573 1,
574 1,
575 1,
576 self.abstract_memory_size(ref_val).into(),
577 REFERENCE_SIZE.into(),
578 )
579 }
580
581 fn charge_write_ref(
582 &mut self,
583 new_val: impl ValueView,
584 old_val: impl ValueView,
585 ) -> PartialVMResult<()> {
586 self.charge(
590 1,
591 1,
592 2,
593 self.abstract_memory_size(new_val).into(),
594 self.abstract_memory_size(old_val).into(),
595 )
596 }
597
598 fn charge_eq(&mut self, lhs: impl ValueView, rhs: impl ValueView) -> PartialVMResult<()> {
599 let size_reduction = self.abstract_memory_size(lhs) + self.abstract_memory_size(rhs);
600 self.charge(
601 1,
602 1,
603 2,
604 (Type::Bool.size() + size_reduction).into(),
605 size_reduction.into(),
606 )
607 }
608
609 fn charge_neq(&mut self, lhs: impl ValueView, rhs: impl ValueView) -> PartialVMResult<()> {
610 let size_reduction = self.abstract_memory_size(lhs) + self.abstract_memory_size(rhs);
611 self.charge(1, 1, 2, Type::Bool.size().into(), size_reduction.into())
612 }
613
614 fn charge_vec_pack<'a>(
615 &mut self,
616 _ty: impl TypeView + 'a,
617 args: impl ExactSizeIterator<Item = impl ValueView>,
618 ) -> PartialVMResult<()> {
619 let num_args = args.len() as u64;
621 self.charge(1, 1, num_args, VEC_SIZE.into(), 0)
624 }
625
626 fn charge_vec_len(&mut self, _ty: impl TypeView) -> PartialVMResult<()> {
627 self.charge(1, 1, 1, Type::U64.size().into(), REFERENCE_SIZE.into())
628 }
629
630 fn charge_vec_borrow(
631 &mut self,
632 _is_mut: bool,
633 _ty: impl TypeView,
634 _is_success: bool,
635 ) -> PartialVMResult<()> {
636 self.charge(
637 1,
638 1,
639 2,
640 REFERENCE_SIZE.into(),
641 (REFERENCE_SIZE + Type::U64.size()).into(),
642 )
643 }
644
645 fn charge_vec_push_back(
646 &mut self,
647 _ty: impl TypeView,
648 _val: impl ValueView,
649 ) -> PartialVMResult<()> {
650 self.charge(1, 0, 2, 0, REFERENCE_SIZE.into())
653 }
654
655 fn charge_vec_pop_back(
656 &mut self,
657 _ty: impl TypeView,
658 _val: Option<impl ValueView>,
659 ) -> PartialVMResult<()> {
660 self.charge(1, 1, 1, 0, REFERENCE_SIZE.into())
661 }
662
663 fn charge_vec_unpack(
664 &mut self,
665 _ty: impl TypeView,
666 expect_num_elements: NumArgs,
667 _elems: impl ExactSizeIterator<Item = impl ValueView>,
668 ) -> PartialVMResult<()> {
669 let pushes = u64::from(expect_num_elements);
671 self.charge(1, pushes, 1, 0, VEC_SIZE.into())
673 }
674
675 fn charge_vec_swap(&mut self, _ty: impl TypeView) -> PartialVMResult<()> {
676 let size_decrease = REFERENCE_SIZE + Type::U64.size() + Type::U64.size();
677 self.charge(1, 1, 1, 0, size_decrease.into())
678 }
679
680 fn charge_drop_frame(
681 &mut self,
682 _locals: impl Iterator<Item = impl ValueView>,
683 ) -> PartialVMResult<()> {
684 Ok(())
685 }
686
687 fn remaining_gas(&self) -> InternalGas {
688 if !self.charge {
689 return InternalGas::new(u64::MAX);
690 }
691 self.gas_left
692 }
693
694 fn get_profiler_mut(&mut self) -> Option<&mut GasProfiler> {
695 self.profiler.as_mut()
696 }
697
698 fn set_profiler(&mut self, profiler: GasProfiler) {
699 self.profiler = Some(profiler);
700 }
701}
702
703pub fn zero_cost_schedule() -> CostTable {
704 let mut zero_tier = BTreeMap::new();
705 zero_tier.insert(0, 0);
706 CostTable {
707 instruction_tiers: zero_tier.clone(),
708 stack_size_tiers: zero_tier.clone(),
709 stack_height_tiers: zero_tier,
710 }
711}
712
713pub fn unit_cost_schedule() -> CostTable {
714 let mut unit_tier = BTreeMap::new();
715 unit_tier.insert(0, 1);
716 CostTable {
717 instruction_tiers: unit_tier.clone(),
718 stack_size_tiers: unit_tier.clone(),
719 stack_height_tiers: unit_tier,
720 }
721}
722
723pub fn initial_cost_schedule_v1() -> CostTable {
724 let instruction_tiers: BTreeMap<u64, u64> = vec![
725 (0, 1),
726 (20_000, 2),
727 (50_000, 10),
728 (100_000, 50),
729 (200_000, 100),
730 (10_000_000, 1000),
731 ]
732 .into_iter()
733 .collect();
734
735 let stack_height_tiers: BTreeMap<u64, u64> =
736 vec![(0, 1), (1_000, 2), (10_000, 10)].into_iter().collect();
737
738 let stack_size_tiers: BTreeMap<u64, u64> = vec![
739 (0, 1),
740 (100_000, 2), (500_000, 5), (1_000_000, 100), (100_000_000, 1000), ]
745 .into_iter()
746 .collect();
747
748 CostTable {
749 instruction_tiers,
750 stack_size_tiers,
751 stack_height_tiers,
752 }
753}
754
755pub fn initial_cost_schedule_for_unit_tests() -> move_vm_test_utils::gas_schedule::CostTable {
761 let table = initial_cost_schedule_v1();
762 move_vm_test_utils::gas_schedule::CostTable {
763 instruction_tiers: table.instruction_tiers,
764 stack_height_tiers: table.stack_height_tiers,
765 stack_size_tiers: table.stack_size_tiers,
766 }
767}