massa_deferred_calls/
lib.rs

1use call::{DeferredCallDeserializer, DeferredCallSerializer};
2use config::DeferredCallsConfig;
3use macros::{
4    CALL_FIELD_CANCELED, CALL_FIELD_COINS, CALL_FIELD_FEE, CALL_FIELD_MAX_GAS,
5    CALL_FIELD_PARAMETERS, CALL_FIELD_SENDER_ADDRESS, CALL_FIELD_TARGET_ADDRESS,
6    CALL_FIELD_TARGET_FUNCTION, CALL_FIELD_TARGET_SLOT,
7};
8use massa_db_exports::{
9    DBBatch, ShareableMassaDBController, CRUD_ERROR, DEFERRED_CALLS_PREFIX,
10    DEFERRED_CALL_DESER_ERROR, DEFERRED_CALL_SER_ERROR, DEFERRED_CALL_TOTAL_GAS, KEY_DESER_ERROR,
11    STATE_CF,
12};
13use massa_models::{address::Address, slot::SlotDeserializer};
14use massa_serialization::{
15    buf_to_array_ctr, DeserializeError, Deserializer, Serializer, U128VarIntDeserializer,
16    U128VarIntSerializer,
17};
18use registry_changes::DeferredCallRegistryChanges;
19
20/// This module implements a new version of the Autonomous Smart Contracts. (ASC)
21/// This new version allow asynchronous calls to be registered for a specific slot and ensure his execution.
22mod call;
23pub mod config;
24pub mod registry_changes;
25pub mod slot_changes;
26
27#[cfg(test)]
28mod tests;
29
30#[macro_use]
31mod macros;
32
33pub use call::DeferredCall;
34use massa_models::types::{SetOrDelete, SetOrKeep, SetOrKeepDeserializer, SetOrKeepSerializer};
35use massa_models::{
36    amount::Amount,
37    deferred_calls::{DeferredCallId, DeferredCallIdDeserializer, DeferredCallIdSerializer},
38    slot::Slot,
39};
40use std::collections::{BTreeMap, HashSet};
41use std::ops::Bound;
42
43// #[derive(Debug)]
44pub struct DeferredCallRegistry {
45    db: ShareableMassaDBController,
46    call_serializer: DeferredCallSerializer,
47    call_id_serializer: DeferredCallIdSerializer,
48    call_deserializer: DeferredCallDeserializer,
49    call_id_deserializer: DeferredCallIdDeserializer,
50    slot_deserializer: SlotDeserializer,
51    effective_total_gas_deserializer: SetOrKeepDeserializer<u128, U128VarIntDeserializer>,
52    effective_total_gas_serializer: SetOrKeepSerializer<u128, U128VarIntSerializer>,
53    min_gas_cost: u64,
54}
55
56impl DeferredCallRegistry {
57    /*
58     DB layout:
59        [DEFERRED_CALL_TOTAL_GAS] -> u128 // total currently booked gas
60        [DEFERRED_CALLS_PREFIX][slot][SLOT_TOTAL_GAS] -> u64 // total gas booked for a slot (optional, default 0, deleted when set to 0)
61        [DEFERRED_CALLS_PREFIX][slot][SLOT_BASE_FEE] -> u64 // deleted when set to 0
62        [DEFERRED_CALLS_PREFIX][slot][CALLS_TAG][id][CALL_FIELD_X_TAG] -> DeferredCalls.x // call data
63    */
64
65    pub fn new(db: ShareableMassaDBController, config: DeferredCallsConfig) -> Self {
66        Self {
67            db,
68            call_serializer: DeferredCallSerializer::new(),
69            call_id_serializer: DeferredCallIdSerializer::new(),
70            call_deserializer: DeferredCallDeserializer::new(config),
71            call_id_deserializer: DeferredCallIdDeserializer::new(),
72            slot_deserializer: SlotDeserializer::new(
73                (Bound::Included(0), Bound::Included(u64::MAX)),
74                (Bound::Included(0), Bound::Excluded(config.thread_count)),
75            ),
76            effective_total_gas_deserializer: SetOrKeepDeserializer::new(
77                U128VarIntDeserializer::new(Bound::Included(u128::MIN), Bound::Included(u128::MAX)),
78            ),
79            effective_total_gas_serializer: SetOrKeepSerializer::new(U128VarIntSerializer::new()),
80            min_gas_cost: config.min_gas_cost,
81        }
82    }
83
84    /// Returns the DeferredSlotCalls for a given slot
85    pub fn get_slot_calls(&self, slot: Slot) -> DeferredSlotCalls {
86        let mut to_return = DeferredSlotCalls::new(slot);
87        let key = deferred_slot_call_prefix_key!(slot.to_bytes_key());
88
89        let mut temp = HashSet::new();
90
91        for (serialized_key, _serialized_value) in self.db.read().prefix_iterator_cf(STATE_CF, &key)
92        {
93            if !serialized_key.starts_with(&key) {
94                break;
95            }
96
97            let rest_key = &serialized_key[key.len()..];
98
99            let (_rest, call_id) = self
100                .call_id_deserializer
101                .deserialize::<DeserializeError>(rest_key)
102                .expect(KEY_DESER_ERROR);
103
104            if !temp.insert(call_id.clone()) {
105                continue;
106            }
107
108            if let Some(call) = self.get_call(&slot, &call_id) {
109                to_return.slot_calls.insert(call_id, call);
110            }
111        }
112
113        to_return.slot_base_fee = self.get_slot_base_fee(&slot);
114        to_return.effective_slot_gas = self.get_slot_gas(&slot);
115
116        to_return
117    }
118
119    /// Returns the DeferredCall for a given slot and id
120    pub fn get_call(&self, slot: &Slot, id: &DeferredCallId) -> Option<DeferredCall> {
121        let mut buf_id = Vec::new();
122        self.call_id_serializer
123            .serialize(id, &mut buf_id)
124            .expect(DEFERRED_CALL_SER_ERROR);
125        let key = deferred_call_prefix_key!(buf_id, slot.to_bytes_key());
126
127        let mut serialized_call: Vec<u8> = Vec::new();
128        for (serialized_key, serialized_value) in self.db.read().prefix_iterator_cf(STATE_CF, &key)
129        {
130            if !serialized_key.starts_with(&key) {
131                break;
132            }
133
134            serialized_call.extend(serialized_value.iter());
135        }
136
137        match self
138            .call_deserializer
139            .deserialize::<DeserializeError>(&serialized_call)
140        {
141            Ok((_rest, call)) => Some(call),
142            Err(_) => None,
143        }
144    }
145
146    /// Returns the total effective amount of gas booked for a slot
147    pub fn get_slot_gas(&self, slot: &Slot) -> u64 {
148        // By default, if it is absent, it is 0
149        let key = deferred_call_slot_total_gas_key!(slot.to_bytes_key());
150        match self.db.read().get_cf(STATE_CF, key) {
151            Ok(Some(v)) => {
152                let result = self
153                    .call_deserializer
154                    .u64_var_int_deserializer
155                    .deserialize::<DeserializeError>(&v)
156                    .expect(DEFERRED_CALL_DESER_ERROR)
157                    .1;
158                result
159            }
160            _ => 0,
161        }
162    }
163
164    /// Returns the base fee for a slot.
165    /// Uninitialized slots (no entry, or zero) fall back to `min_gas_cost`: this is exactly
166    /// what `advance_slot` would have written for them from an empty registry (0 booked gas
167    /// puts the controller in its "decrease" branch, which floors at `min_gas_cost`), so the
168    /// fallback is the missing initialization, not a different pricing rule. On a registry
169    /// that has been advancing for more than `max_future_slots` this never fires.
170    pub fn get_slot_base_fee(&self, slot: &Slot) -> Amount {
171        let key = deferred_call_slot_base_fee_key!(slot.to_bytes_key());
172        let base_fee = match self.db.read().get_cf(STATE_CF, key) {
173            Ok(Some(v)) => {
174                self.call_deserializer
175                    .amount_deserializer
176                    .deserialize::<DeserializeError>(&v)
177                    .expect(DEFERRED_CALL_DESER_ERROR)
178                    .1
179            }
180            _ => Amount::zero(),
181        };
182
183        if base_fee.is_zero() {
184            Amount::from_raw(self.min_gas_cost)
185        } else {
186            base_fee
187        }
188    }
189
190    /// Returns the total amount of effective gas booked
191    pub fn get_total_gas(&self) -> u128 {
192        match self
193            .db
194            .read()
195            .get_cf(STATE_CF, DEFERRED_CALL_TOTAL_GAS.as_bytes().to_vec())
196            .expect(CRUD_ERROR)
197        {
198            Some(v) => {
199                let result = self
200                    .effective_total_gas_deserializer
201                    .deserialize::<DeserializeError>(&v)
202                    .expect(DEFERRED_CALL_DESER_ERROR)
203                    .1;
204
205                match result {
206                    DeferredRegistryGasChange::Set(v) => v,
207                    DeferredRegistryGasChange::Keep => 0,
208                }
209            }
210            None => 0,
211        }
212    }
213
214    pub fn put_entry(
215        &self,
216        slot: &Slot,
217        call_id: &DeferredCallId,
218        call: &DeferredCall,
219        batch: &mut DBBatch,
220    ) {
221        let mut buffer_id = Vec::new();
222        self.call_id_serializer
223            .serialize(call_id, &mut buffer_id)
224            .expect(DEFERRED_CALL_SER_ERROR);
225
226        let slot_bytes = slot.to_bytes_key();
227
228        let db = self.db.read();
229
230        {
231            // sender address
232            let mut buffer = Vec::new();
233            self.call_serializer
234                .address_serializer
235                .serialize(&call.sender_address, &mut buffer)
236                .expect(DEFERRED_CALL_SER_ERROR);
237            db.put_or_update_entry_value(
238                batch,
239                sender_address_key!(buffer_id, slot_bytes),
240                &buffer,
241            );
242        }
243
244        {
245            // target slot
246            let mut buffer = Vec::new();
247            self.call_serializer
248                .slot_serializer
249                .serialize(&call.target_slot, &mut buffer)
250                .expect(DEFERRED_CALL_SER_ERROR);
251            db.put_or_update_entry_value(batch, target_slot_key!(buffer_id, slot_bytes), &buffer);
252        }
253
254        {
255            // target address
256            let mut buffer = Vec::new();
257            self.call_serializer
258                .address_serializer
259                .serialize(&call.target_address, &mut buffer)
260                .expect(DEFERRED_CALL_SER_ERROR);
261            db.put_or_update_entry_value(
262                batch,
263                target_address_key!(buffer_id, slot_bytes),
264                &buffer,
265            );
266        }
267
268        {
269            // target function
270            let mut buffer = Vec::new();
271            self.call_serializer
272                .string_serializer
273                .serialize(&call.target_function, &mut buffer)
274                .expect(DEFERRED_CALL_SER_ERROR);
275            db.put_or_update_entry_value(
276                batch,
277                target_function_key!(buffer_id, slot_bytes),
278                &buffer,
279            );
280        }
281
282        {
283            // parameters
284            let mut buffer = Vec::new();
285            self.call_serializer
286                .vec_u8_serializer
287                .serialize(&call.parameters, &mut buffer)
288                .expect(DEFERRED_CALL_SER_ERROR);
289            db.put_or_update_entry_value(batch, parameters_key!(buffer_id, slot_bytes), &buffer);
290        }
291
292        {
293            // coins
294            let mut buffer = Vec::new();
295            self.call_serializer
296                .amount_serializer
297                .serialize(&call.coins, &mut buffer)
298                .expect(DEFERRED_CALL_SER_ERROR);
299            db.put_or_update_entry_value(batch, coins_key!(buffer_id, slot_bytes), &buffer);
300        }
301
302        {
303            // max gas
304            let mut buffer = Vec::new();
305            self.call_serializer
306                .u64_var_int_serializer
307                .serialize(&call.max_gas, &mut buffer)
308                .expect(DEFERRED_CALL_SER_ERROR);
309            db.put_or_update_entry_value(batch, max_gas_key!(buffer_id, slot_bytes), &buffer);
310        }
311
312        {
313            // fee
314            let mut buffer = Vec::new();
315            self.call_serializer
316                .amount_serializer
317                .serialize(&call.fee, &mut buffer)
318                .expect(DEFERRED_CALL_SER_ERROR);
319            db.put_or_update_entry_value(batch, fee_key!(buffer_id, slot_bytes), &buffer);
320        }
321
322        // cancelled
323        let mut buffer = Vec::new();
324        self.call_serializer
325            .bool_serializer
326            .serialize(&call.cancelled, &mut buffer)
327            .expect(DEFERRED_CALL_SER_ERROR);
328        db.put_or_update_entry_value(batch, cancelled_key!(buffer_id, slot_bytes), &buffer);
329    }
330
331    fn delete_entry(&self, id: &DeferredCallId, slot: &Slot, batch: &mut DBBatch) {
332        let mut buffer_id = Vec::new();
333        self.call_id_serializer
334            .serialize(id, &mut buffer_id)
335            .expect(DEFERRED_CALL_SER_ERROR);
336
337        let slot_bytes = slot.to_bytes_key();
338
339        let db = self.db.read();
340
341        db.delete_key(batch, sender_address_key!(buffer_id, slot_bytes));
342        db.delete_key(batch, target_slot_key!(buffer_id, slot_bytes));
343        db.delete_key(batch, target_address_key!(buffer_id, slot_bytes));
344        db.delete_key(batch, target_function_key!(buffer_id, slot_bytes));
345        db.delete_key(batch, parameters_key!(buffer_id, slot_bytes));
346        db.delete_key(batch, coins_key!(buffer_id, slot_bytes));
347        db.delete_key(batch, max_gas_key!(buffer_id, slot_bytes));
348        db.delete_key(batch, fee_key!(buffer_id, slot_bytes));
349        db.delete_key(batch, cancelled_key!(buffer_id, slot_bytes));
350    }
351
352    pub fn apply_changes_to_batch(
353        &self,
354        changes: DeferredCallRegistryChanges,
355        batch: &mut DBBatch,
356    ) {
357        //Note: if a slot gas is zet to 0, delete the slot gas entry
358        // same for base fee
359
360        for change in changes.slots_change.iter() {
361            let slot = change.0;
362            let slot_changes = change.1;
363            for (id, call_change) in slot_changes.calls.iter() {
364                match call_change {
365                    DeferredRegistryCallChange::Set(call) => {
366                        if call.cancelled {
367                            massa_metrics::inc_deferred_calls_cancelled();
368                        } else {
369                            massa_metrics::inc_deferred_calls_registered();
370                        }
371                        self.put_entry(slot, id, call, batch);
372                    }
373                    DeferredRegistryCallChange::Delete => {
374                        self.delete_entry(id, slot, batch);
375                    }
376                }
377            }
378            match slot_changes.effective_slot_gas {
379                DeferredRegistryGasChange::Set(v) => {
380                    let key = deferred_call_slot_total_gas_key!(slot.to_bytes_key());
381                    //Note: if a slot gas is zet to 0, delete the slot gas entry
382                    if v.eq(&0) {
383                        self.db.read().delete_key(batch, key);
384                    } else {
385                        let mut value_ser = Vec::new();
386                        self.call_serializer
387                            .u64_var_int_serializer
388                            .serialize(&v, &mut value_ser)
389                            .expect(DEFERRED_CALL_SER_ERROR);
390                        self.db
391                            .read()
392                            .put_or_update_entry_value(batch, key, &value_ser);
393                    }
394                }
395                DeferredRegistryGasChange::Keep => {}
396            }
397            match slot_changes.base_fee {
398                DeferredRegistryBaseFeeChange::Set(v) => {
399                    let key = deferred_call_slot_base_fee_key!(slot.to_bytes_key());
400                    //Note: if a base fee is zet to 0, delete the base fee entry
401                    if v.eq(&Amount::zero()) {
402                        self.db.read().delete_key(batch, key);
403                    } else {
404                        let mut value_ser = Vec::new();
405                        self.call_serializer
406                            .amount_serializer
407                            .serialize(&v, &mut value_ser)
408                            .expect(DEFERRED_CALL_SER_ERROR);
409                        self.db
410                            .read()
411                            .put_or_update_entry_value(batch, key, &value_ser);
412                    }
413                }
414                DeferredRegistryBaseFeeChange::Keep => {}
415            }
416        }
417
418        match changes.effective_total_gas {
419            DeferredRegistryGasChange::Set(val) => {
420                massa_metrics::set_deferred_calls_total_gas(val);
421                let key = DEFERRED_CALL_TOTAL_GAS.as_bytes().to_vec();
422                let mut value_ser = Vec::new();
423                self.effective_total_gas_serializer
424                    .serialize(&changes.effective_total_gas, &mut value_ser)
425                    .expect(DEFERRED_CALL_SER_ERROR);
426                self.db
427                    .read()
428                    .put_or_update_entry_value(batch, key, &value_ser);
429            }
430            DeferredRegistryGasChange::Keep => {}
431        }
432
433        if changes.exec_stats.0 > 0 {
434            // deferred calls that has been executed successfully
435            massa_metrics::inc_deferred_calls_executed_by(changes.exec_stats.0);
436        }
437        if changes.exec_stats.1 > 0 {
438            // deferred calls that has failed
439            massa_metrics::inc_deferred_calls_failed_by(changes.exec_stats.1);
440        }
441        if changes.exec_stats.2 > 0 {
442            // deferred calls that has been cancelled
443            massa_metrics::dec_deferred_calls_cancelled_by(changes.exec_stats.2);
444        }
445        let total_no_more_registered = changes
446            .exec_stats
447            .0
448            .saturating_add(changes.exec_stats.1)
449            .saturating_add(changes.exec_stats.2);
450        if total_no_more_registered > 0 {
451            // update the number of deferred calls registered
452            massa_metrics::dec_deferred_calls_registered_by(total_no_more_registered);
453        }
454    }
455
456    pub fn is_key_value_valid(&self, serialized_key: &[u8], serialized_value: &[u8]) -> bool {
457        if serialized_key.starts_with(DEFERRED_CALLS_PREFIX.as_bytes()) {
458            // check for  [DEFERRED_CALLS_PREFIX][slot]
459            if let Some((_rest, slot)) = buf_to_array_ctr(
460                &serialized_key[DEFERRED_CALLS_PREFIX.len()..],
461                Slot::from_bytes_key,
462            ) {
463                // check for  [DEFERRED_CALLS_PREFIX][slot][SLOT_TOTAL_GAS]
464                if serialized_key
465                    .starts_with(&deferred_call_slot_total_gas_key!(&slot.to_bytes_key()))
466                {
467                    return self
468                        .call_deserializer
469                        .u64_var_int_deserializer
470                        .deserialize::<DeserializeError>(serialized_value)
471                        .is_ok();
472                } else if serialized_key
473                    .starts_with(&deferred_call_slot_base_fee_key!(&slot.to_bytes_key()))
474                {
475                    // check for  [DEFERRED_CALLS_PREFIX][slot][SLOT_BASE_FEE]
476                    return self
477                        .call_deserializer
478                        .amount_deserializer
479                        .deserialize::<DeserializeError>(serialized_value)
480                        .is_ok();
481                } else {
482                    // check for  [DEFERRED_CALLS_PREFIX][slot][CALLS_TAG][id][CALL_FIELD_X_TAG]
483
484                    // [DEFERRED_CALLS_PREFIX][slot][CALLS_TAG]
485                    let k = deferred_slot_call_prefix_key!(&slot.to_bytes_key());
486                    // Malformed keys must return false, not panic (bootstrap / final-state verification).
487                    if !serialized_key.starts_with(&k) {
488                        return false;
489                    }
490                    let rest_key = &serialized_key[k.len()..];
491
492                    if let Ok((rest, _id)) = self
493                        .call_id_deserializer
494                        .deserialize::<DeserializeError>(rest_key)
495                    {
496                        // Require exactly one field-tag byte after the call id.
497                        if rest.len() != 1 {
498                            return false;
499                        }
500                        match rest[0] {
501                            CALL_FIELD_SENDER_ADDRESS => {
502                                let res: Result<(&[u8], Address), nom::Err<DeserializeError<'_>>> =
503                                    self.call_deserializer
504                                        .address_deserializer
505                                        .deserialize::<DeserializeError>(serialized_value);
506
507                                return res.is_ok();
508                            }
509                            CALL_FIELD_TARGET_SLOT => {
510                                return self
511                                    .slot_deserializer
512                                    .deserialize::<DeserializeError>(serialized_value)
513                                    .is_ok()
514                            }
515                            CALL_FIELD_TARGET_ADDRESS => {
516                                let res: Result<(&[u8], Address), nom::Err<DeserializeError<'_>>> =
517                                    self.call_deserializer
518                                        .address_deserializer
519                                        .deserialize::<DeserializeError>(serialized_value);
520                                return res.is_ok();
521                            }
522                            CALL_FIELD_TARGET_FUNCTION => {
523                                return self
524                                    .call_deserializer
525                                    .string_deserializer
526                                    .deserialize::<DeserializeError>(serialized_value)
527                                    .is_ok()
528                            }
529                            CALL_FIELD_PARAMETERS => {
530                                return self
531                                    .call_deserializer
532                                    .vec_u8_deserializer
533                                    .deserialize::<DeserializeError>(serialized_value)
534                                    .is_ok()
535                            }
536                            CALL_FIELD_MAX_GAS => {
537                                return self
538                                    .call_deserializer
539                                    .u64_var_int_deserializer
540                                    .deserialize::<DeserializeError>(serialized_value)
541                                    .is_ok()
542                            }
543                            CALL_FIELD_FEE => {
544                                return self
545                                    .call_deserializer
546                                    .amount_deserializer
547                                    .deserialize::<DeserializeError>(serialized_value)
548                                    .is_ok()
549                            }
550                            CALL_FIELD_CANCELED => {
551                                return self
552                                    .call_deserializer
553                                    .bool_deserializer
554                                    .deserialize::<DeserializeError>(serialized_value)
555                                    .is_ok();
556                            }
557                            CALL_FIELD_COINS => {
558                                return self
559                                    .call_deserializer
560                                    .amount_deserializer
561                                    .deserialize::<DeserializeError>(serialized_value)
562                                    .is_ok();
563                            }
564                            _ => {}
565                        }
566                    }
567                }
568            }
569        } else if serialized_key.eq(DEFERRED_CALL_TOTAL_GAS.as_bytes()) {
570            return self
571                .effective_total_gas_deserializer
572                .deserialize::<DeserializeError>(serialized_value)
573                .is_ok();
574        }
575        false
576    }
577}
578
579pub type DeferredRegistryCallChange = SetOrDelete<DeferredCall>;
580pub type DeferredRegistryGasChange<V> = SetOrKeep<V>;
581pub type DeferredRegistryBaseFeeChange = SetOrKeep<Amount>;
582
583/// Per-slot deferred-call view (calls, slot gas, base fee).
584///
585/// Registry-wide booked gas is not stored here — use
586/// DeferredCallRegistry::get_total_gas /
587/// SpeculativeDeferredCallRegistry::get_effective_total_gas.
588#[derive(Debug, Clone)]
589pub struct DeferredSlotCalls {
590    pub slot: Slot,
591    pub slot_calls: BTreeMap<DeferredCallId, DeferredCall>,
592
593    // calls gas + gas_alloc_cost
594    // effective_slot_gas doesn't include gas of cancelled calls
595    pub effective_slot_gas: u64,
596
597    pub slot_base_fee: Amount,
598}
599
600impl DeferredSlotCalls {
601    pub fn new(slot: Slot) -> Self {
602        Self {
603            slot,
604            slot_calls: BTreeMap::new(),
605            effective_slot_gas: 0,
606            slot_base_fee: Amount::zero(),
607        }
608    }
609
610    /// Apply slot-local registry changes onto this in-memory view.
611    ///
612    /// Only fields for [Self::slot] are applied. Registry-wide ffective_total_gas
613    /// is intentionally ignored; callers that need the global total must read it from
614    /// the registry / speculative layer (F33: avoid a second, desyncable copy here).
615    pub fn apply_changes(&mut self, changes: &DeferredCallRegistryChanges) {
616        let Some(slot_changes) = changes.slots_change.get(&self.slot) else {
617            return;
618        };
619        for (id, change) in &slot_changes.calls {
620            match change {
621                DeferredRegistryCallChange::Set(call) => {
622                    self.slot_calls.insert(id.clone(), call.clone());
623                }
624                DeferredRegistryCallChange::Delete => {
625                    self.slot_calls.remove(id);
626                }
627            }
628        }
629        match slot_changes.effective_slot_gas {
630            DeferredRegistryGasChange::Set(v) => self.effective_slot_gas = v,
631            DeferredRegistryGasChange::Keep => {}
632        }
633        match slot_changes.base_fee {
634            DeferredRegistryGasChange::Set(v) => self.slot_base_fee = v,
635            DeferredRegistryGasChange::Keep => {}
636        }
637    }
638}
639
640#[cfg(test)]
641mod deferred_slot_calls_tests {
642    use super::*;
643    use massa_models::amount::Amount;
644
645    /// A change set with only registry-wide total gas (and/or another slot) must not
646    /// disturb this slot's local fields.
647    #[test]
648    fn apply_changes_ignores_standalone_effective_total_gas() {
649        let slot = Slot::new(1, 0);
650        let other_slot = Slot::new(2, 0);
651
652        let mut view = DeferredSlotCalls::new(slot);
653        view.effective_slot_gas = 3;
654        view.slot_base_fee = Amount::from_raw(7);
655
656        let mut changes = DeferredCallRegistryChanges::default();
657        changes.set_effective_total_gas(42);
658        changes.set_effective_slot_gas(other_slot, 99);
659
660        view.apply_changes(&changes);
661
662        assert_eq!(view.effective_slot_gas, 3);
663        assert_eq!(view.slot_base_fee, Amount::from_raw(7));
664        assert!(view.slot_calls.is_empty());
665    }
666
667    #[test]
668    fn apply_changes_applies_slot_local_fields() {
669        let slot = Slot::new(1, 0);
670        let mut view = DeferredSlotCalls::new(slot);
671
672        let mut changes = DeferredCallRegistryChanges::default();
673        changes.set_effective_slot_gas(slot, 55);
674        changes.set_slot_base_fee(slot, Amount::from_raw(9));
675        changes.set_effective_total_gas(100); // global: ignored on this view
676
677        view.apply_changes(&changes);
678
679        assert_eq!(view.effective_slot_gas, 55);
680        assert_eq!(view.slot_base_fee, Amount::from_raw(9));
681    }
682}