sui_types/effects/
object_change.rs

1// Copyright (c) Mysten Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4use crate::{
5    base_types::{SuiAddress, VersionDigest},
6    digests::{Digest, ObjectDigest},
7    object::{Object, Owner},
8};
9use move_core_types::language_storage::TypeTag;
10use nonempty::NonEmpty;
11use serde::{Deserialize, Serialize};
12
13#[cfg(test)]
14use nonempty::nonempty;
15
16use super::IDOperation;
17
18#[derive(Eq, PartialEq, Clone, Debug, Serialize, Deserialize)]
19pub struct EffectsObjectChange {
20    // input_state and output_state are the core fields that's required by
21    // the protocol as it tells how an object changes on-chain.
22    /// State of the object in the store prior to this transaction.
23    pub(crate) input_state: ObjectIn,
24    /// State of the object in the store after this transaction.
25    pub(crate) output_state: ObjectOut,
26
27    /// Whether this object ID is created or deleted in this transaction.
28    /// This information isn't required by the protocol but is useful for providing more detailed
29    /// semantics on object changes.
30    pub(crate) id_operation: IDOperation,
31}
32
33impl EffectsObjectChange {
34    pub fn new(
35        modified_at: Option<(VersionDigest, Owner)>,
36        written: Option<&Object>,
37        id_created: bool,
38        id_deleted: bool,
39    ) -> Self {
40        debug_assert!(
41            !id_created || !id_deleted,
42            "Object ID can't be created and deleted at the same time."
43        );
44        Self {
45            input_state: modified_at.map_or(ObjectIn::NotExist, ObjectIn::Exist),
46            output_state: written.map_or(ObjectOut::NotExist, |o| {
47                if o.is_package() {
48                    ObjectOut::PackageWrite((o.version(), o.digest()))
49                } else {
50                    ObjectOut::ObjectWrite((o.digest(), o.owner.clone()))
51                }
52            }),
53            id_operation: if id_created {
54                IDOperation::Created
55            } else if id_deleted {
56                IDOperation::Deleted
57            } else {
58                IDOperation::None
59            },
60        }
61    }
62
63    pub fn new_from_accumulator_write(write: AccumulatorWriteV1) -> Self {
64        Self {
65            input_state: ObjectIn::NotExist,
66            output_state: ObjectOut::AccumulatorWriteV1(write),
67            id_operation: IDOperation::None,
68        }
69    }
70}
71
72/// If an object exists (at root-level) in the store prior to this transaction,
73/// it should be Exist, otherwise it's NonExist, e.g. wrapped objects should be
74/// NotExist.
75#[derive(Eq, PartialEq, Clone, Debug, Serialize, Deserialize)]
76pub enum ObjectIn {
77    NotExist,
78    /// The old version, digest and owner.
79    Exist((VersionDigest, Owner)),
80}
81
82#[derive(Eq, PartialEq, Clone, Debug, Serialize, Deserialize)]
83pub enum AccumulatorOperation {
84    /// Merge the value into the accumulator.
85    Merge,
86    /// Split the value from the accumulator.
87    Split,
88}
89
90#[derive(Eq, PartialEq, Clone, Debug, Serialize, Deserialize)]
91pub enum AccumulatorValue {
92    Integer(u64),
93    IntegerTuple(u64, u64),
94    EventDigest(NonEmpty<(u64 /* event index in the transaction */, Digest)>),
95}
96
97/// Accumulator objects are named by an address (can be an account address or a UID)
98/// and a type tag.
99#[derive(Eq, PartialEq, Clone, Debug, Serialize, Deserialize)]
100pub struct AccumulatorAddress {
101    pub address: SuiAddress,
102    pub ty: TypeTag,
103}
104
105impl AccumulatorAddress {
106    pub fn new(address: SuiAddress, ty: TypeTag) -> Self {
107        Self { address, ty }
108    }
109}
110
111#[derive(Eq, PartialEq, Clone, Debug, Serialize, Deserialize)]
112pub struct AccumulatorWriteV1 {
113    pub address: AccumulatorAddress,
114    /// The operation to be applied to the accumulator.
115    pub operation: AccumulatorOperation,
116    /// The value to be merged into or split from the accumulator.
117    pub value: AccumulatorValue,
118}
119
120impl AccumulatorWriteV1 {
121    pub fn merge(mut writes: Vec<Self>) -> Self {
122        if writes.len() == 1 {
123            return writes.pop().unwrap();
124        }
125
126        let address = writes[0].address.clone();
127
128        if mysten_common::in_test_configuration() {
129            for write in &writes[1..] {
130                if write.address.address != address.address {
131                    mysten_common::debug_fatal!(
132                        "All writes must have the same accumulator address: {} != {}",
133                        write.address.address,
134                        address.address
135                    );
136                }
137                if write.address.ty != address.ty {
138                    mysten_common::debug_fatal!(
139                        "All writes must have the same accumulator type: {:?} != {:?}",
140                        write.address.ty,
141                        address.ty
142                    );
143                }
144            }
145        }
146        let (merged_value, net_operation) = match &writes[0].value {
147            AccumulatorValue::Integer(_) => {
148                let (merge_amount, split_amount) =
149                    writes.iter().fold((0u64, 0u64), |(merge, split), w| {
150                        if let AccumulatorValue::Integer(v) = w.value {
151                            match w.operation {
152                                AccumulatorOperation::Merge => (
153                                    merge.checked_add(v).expect("validated in object runtime"),
154                                    split,
155                                ),
156                                AccumulatorOperation::Split => (
157                                    merge,
158                                    split.checked_add(v).expect("validated in object runtime"),
159                                ),
160                            }
161                        } else {
162                            mysten_common::fatal!(
163                                "mismatched accumulator value types for same object"
164                            );
165                        }
166                    });
167                let (amount, operation) = if merge_amount >= split_amount {
168                    (merge_amount - split_amount, AccumulatorOperation::Merge)
169                } else {
170                    (split_amount - merge_amount, AccumulatorOperation::Split)
171                };
172                (AccumulatorValue::Integer(amount), operation)
173            }
174            AccumulatorValue::IntegerTuple(_, _) => {
175                todo!("IntegerTuple netting-out logic not yet implemented")
176            }
177            AccumulatorValue::EventDigest(first_digests) => {
178                let mut event_digests = first_digests.clone();
179                for write in &writes[1..] {
180                    if let AccumulatorValue::EventDigest(digests) = &write.value {
181                        event_digests.extend(digests.iter().copied());
182                    } else {
183                        mysten_common::fatal!("mismatched accumulator value types for same object");
184                    }
185                }
186                (
187                    AccumulatorValue::EventDigest(event_digests),
188                    AccumulatorOperation::Merge,
189                )
190            }
191        };
192        AccumulatorWriteV1 {
193            address,
194            operation: net_operation,
195            value: merged_value,
196        }
197    }
198
199    pub fn get_fund_withdraw_amount(&self) -> Option<u128> {
200        if let (&AccumulatorOperation::Split, &AccumulatorValue::Integer(amount)) =
201            (&self.operation, &self.value)
202        {
203            Some(amount as u128)
204        } else {
205            None
206        }
207    }
208}
209
210#[derive(Eq, PartialEq, Clone, Debug, Serialize, Deserialize)]
211pub enum ObjectOut {
212    /// Same definition as in ObjectIn.
213    NotExist,
214    /// Any written object, including all of mutated, created, unwrapped today.
215    ObjectWrite((ObjectDigest, Owner)),
216    /// Packages writes need to be tracked separately with version because
217    /// we don't use lamport version for package publish and upgrades.
218    PackageWrite(VersionDigest),
219    /// This isn't an object write, but a special write to an accumulator.
220    AccumulatorWriteV1(AccumulatorWriteV1),
221}
222
223#[cfg(test)]
224mod tests {
225    use super::*;
226    use crate::base_types::SuiAddress;
227
228    fn test_accumulator_address() -> AccumulatorAddress {
229        AccumulatorAddress::new(
230            SuiAddress::random_for_testing_only(),
231            "0x2::balance::Balance<0x2::sui::SUI>".parse().unwrap(),
232        )
233    }
234
235    #[test]
236    fn test_merge_single_item() {
237        let addr = test_accumulator_address();
238        let write = AccumulatorWriteV1 {
239            address: addr.clone(),
240            operation: AccumulatorOperation::Merge,
241            value: AccumulatorValue::Integer(100),
242        };
243
244        let result = AccumulatorWriteV1::merge(vec![write.clone()]);
245        assert_eq!(result, write);
246    }
247
248    #[test]
249    fn test_merge_positive_balance() {
250        let addr = test_accumulator_address();
251        let writes = vec![
252            AccumulatorWriteV1 {
253                address: addr.clone(),
254                operation: AccumulatorOperation::Merge,
255                value: AccumulatorValue::Integer(100),
256            },
257            AccumulatorWriteV1 {
258                address: addr.clone(),
259                operation: AccumulatorOperation::Merge,
260                value: AccumulatorValue::Integer(50),
261            },
262            AccumulatorWriteV1 {
263                address: addr.clone(),
264                operation: AccumulatorOperation::Split,
265                value: AccumulatorValue::Integer(30),
266            },
267        ];
268
269        let result = AccumulatorWriteV1::merge(writes);
270        assert_eq!(result.operation, AccumulatorOperation::Merge);
271        assert_eq!(result.value, AccumulatorValue::Integer(120));
272    }
273
274    #[test]
275    fn test_merge_negative_balance() {
276        let addr = test_accumulator_address();
277        let writes = vec![
278            AccumulatorWriteV1 {
279                address: addr.clone(),
280                operation: AccumulatorOperation::Merge,
281                value: AccumulatorValue::Integer(50),
282            },
283            AccumulatorWriteV1 {
284                address: addr.clone(),
285                operation: AccumulatorOperation::Split,
286                value: AccumulatorValue::Integer(100),
287            },
288            AccumulatorWriteV1 {
289                address: addr.clone(),
290                operation: AccumulatorOperation::Split,
291                value: AccumulatorValue::Integer(30),
292            },
293        ];
294
295        let result = AccumulatorWriteV1::merge(writes);
296        assert_eq!(result.operation, AccumulatorOperation::Split);
297        assert_eq!(result.value, AccumulatorValue::Integer(80));
298    }
299
300    #[test]
301    fn test_merge_zero_balance() {
302        let addr = test_accumulator_address();
303        let writes = vec![
304            AccumulatorWriteV1 {
305                address: addr.clone(),
306                operation: AccumulatorOperation::Merge,
307                value: AccumulatorValue::Integer(100),
308            },
309            AccumulatorWriteV1 {
310                address: addr.clone(),
311                operation: AccumulatorOperation::Split,
312                value: AccumulatorValue::Integer(100),
313            },
314        ];
315
316        let result = AccumulatorWriteV1::merge(writes);
317        assert_eq!(result.operation, AccumulatorOperation::Merge);
318        assert_eq!(result.value, AccumulatorValue::Integer(0));
319    }
320
321    #[test]
322    fn test_merge_all_merges() {
323        let addr = test_accumulator_address();
324        let writes = vec![
325            AccumulatorWriteV1 {
326                address: addr.clone(),
327                operation: AccumulatorOperation::Merge,
328                value: AccumulatorValue::Integer(100),
329            },
330            AccumulatorWriteV1 {
331                address: addr.clone(),
332                operation: AccumulatorOperation::Merge,
333                value: AccumulatorValue::Integer(200),
334            },
335            AccumulatorWriteV1 {
336                address: addr.clone(),
337                operation: AccumulatorOperation::Merge,
338                value: AccumulatorValue::Integer(300),
339            },
340        ];
341
342        let result = AccumulatorWriteV1::merge(writes);
343        assert_eq!(result.operation, AccumulatorOperation::Merge);
344        assert_eq!(result.value, AccumulatorValue::Integer(600));
345    }
346
347    #[test]
348    fn test_merge_all_splits() {
349        let addr = test_accumulator_address();
350        let writes = vec![
351            AccumulatorWriteV1 {
352                address: addr.clone(),
353                operation: AccumulatorOperation::Split,
354                value: AccumulatorValue::Integer(100),
355            },
356            AccumulatorWriteV1 {
357                address: addr.clone(),
358                operation: AccumulatorOperation::Split,
359                value: AccumulatorValue::Integer(200),
360            },
361            AccumulatorWriteV1 {
362                address: addr.clone(),
363                operation: AccumulatorOperation::Split,
364                value: AccumulatorValue::Integer(50),
365            },
366        ];
367
368        let result = AccumulatorWriteV1::merge(writes);
369        assert_eq!(result.operation, AccumulatorOperation::Split);
370        assert_eq!(result.value, AccumulatorValue::Integer(350));
371    }
372
373    #[test]
374    fn test_merge_event_digests_single() {
375        let addr = test_accumulator_address();
376        let digest1 = Digest::random();
377        let digest2 = Digest::random();
378
379        let write = AccumulatorWriteV1 {
380            address: addr.clone(),
381            operation: AccumulatorOperation::Merge,
382            value: AccumulatorValue::EventDigest(nonempty![(0, digest1), (1, digest2)]),
383        };
384
385        let result = AccumulatorWriteV1::merge(vec![write.clone()]);
386        assert_eq!(result, write);
387    }
388
389    #[test]
390    fn test_merge_event_digests_multiple() {
391        let addr = test_accumulator_address();
392        let digest1 = Digest::random();
393        let digest2 = Digest::random();
394        let digest3 = Digest::random();
395        let digest4 = Digest::random();
396
397        let writes = vec![
398            AccumulatorWriteV1 {
399                address: addr.clone(),
400                operation: AccumulatorOperation::Merge,
401                value: AccumulatorValue::EventDigest(nonempty![(0, digest1), (1, digest2)]),
402            },
403            AccumulatorWriteV1 {
404                address: addr.clone(),
405                operation: AccumulatorOperation::Merge,
406                value: AccumulatorValue::EventDigest(nonempty![(2, digest3)]),
407            },
408            AccumulatorWriteV1 {
409                address: addr.clone(),
410                operation: AccumulatorOperation::Merge,
411                value: AccumulatorValue::EventDigest(nonempty![(3, digest4)]),
412            },
413        ];
414
415        let result = AccumulatorWriteV1::merge(writes);
416        assert_eq!(result.operation, AccumulatorOperation::Merge);
417        if let AccumulatorValue::EventDigest(digests) = result.value {
418            assert_eq!(digests.len(), 4);
419            assert_eq!(digests[0], (0, digest1));
420            assert_eq!(digests[1], (1, digest2));
421            assert_eq!(digests[2], (2, digest3));
422            assert_eq!(digests[3], (3, digest4));
423        } else {
424            panic!("Expected EventDigest value");
425        }
426    }
427
428    #[test]
429    #[should_panic(expected = "All writes must have the same accumulator address")]
430    fn test_merge_mismatched_addresses() {
431        let addr1 = test_accumulator_address();
432        let addr2 = test_accumulator_address();
433
434        let writes = vec![
435            AccumulatorWriteV1 {
436                address: addr1,
437                operation: AccumulatorOperation::Merge,
438                value: AccumulatorValue::Integer(100),
439            },
440            AccumulatorWriteV1 {
441                address: addr2,
442                operation: AccumulatorOperation::Merge,
443                value: AccumulatorValue::Integer(50),
444            },
445        ];
446
447        AccumulatorWriteV1::merge(writes);
448    }
449
450    #[test]
451    #[should_panic(expected = "All writes must have the same accumulator type")]
452    fn test_merge_mismatched_types() {
453        let addr = SuiAddress::random_for_testing_only();
454        let addr1 = AccumulatorAddress::new(
455            addr,
456            "0x2::balance::Balance<0x2::sui::SUI>".parse().unwrap(),
457        );
458        let addr2 = AccumulatorAddress::new(
459            addr,
460            "0x2::accumulator_settlement::EventStreamHead"
461                .parse()
462                .unwrap(),
463        );
464
465        let writes = vec![
466            AccumulatorWriteV1 {
467                address: addr1,
468                operation: AccumulatorOperation::Merge,
469                value: AccumulatorValue::Integer(100),
470            },
471            AccumulatorWriteV1 {
472                address: addr2,
473                operation: AccumulatorOperation::Merge,
474                value: AccumulatorValue::Integer(50),
475            },
476        ];
477
478        AccumulatorWriteV1::merge(writes);
479    }
480}