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
200#[derive(Eq, PartialEq, Clone, Debug, Serialize, Deserialize)]
201pub enum ObjectOut {
202    /// Same definition as in ObjectIn.
203    NotExist,
204    /// Any written object, including all of mutated, created, unwrapped today.
205    ObjectWrite((ObjectDigest, Owner)),
206    /// Packages writes need to be tracked separately with version because
207    /// we don't use lamport version for package publish and upgrades.
208    PackageWrite(VersionDigest),
209    /// This isn't an object write, but a special write to an accumulator.
210    AccumulatorWriteV1(AccumulatorWriteV1),
211}
212
213#[cfg(test)]
214mod tests {
215    use super::*;
216    use crate::base_types::SuiAddress;
217
218    fn test_accumulator_address() -> AccumulatorAddress {
219        AccumulatorAddress::new(
220            SuiAddress::random_for_testing_only(),
221            "0x2::balance::Balance<0x2::sui::SUI>".parse().unwrap(),
222        )
223    }
224
225    #[test]
226    fn test_merge_single_item() {
227        let addr = test_accumulator_address();
228        let write = AccumulatorWriteV1 {
229            address: addr.clone(),
230            operation: AccumulatorOperation::Merge,
231            value: AccumulatorValue::Integer(100),
232        };
233
234        let result = AccumulatorWriteV1::merge(vec![write.clone()]);
235        assert_eq!(result, write);
236    }
237
238    #[test]
239    fn test_merge_positive_balance() {
240        let addr = test_accumulator_address();
241        let writes = vec![
242            AccumulatorWriteV1 {
243                address: addr.clone(),
244                operation: AccumulatorOperation::Merge,
245                value: AccumulatorValue::Integer(100),
246            },
247            AccumulatorWriteV1 {
248                address: addr.clone(),
249                operation: AccumulatorOperation::Merge,
250                value: AccumulatorValue::Integer(50),
251            },
252            AccumulatorWriteV1 {
253                address: addr.clone(),
254                operation: AccumulatorOperation::Split,
255                value: AccumulatorValue::Integer(30),
256            },
257        ];
258
259        let result = AccumulatorWriteV1::merge(writes);
260        assert_eq!(result.operation, AccumulatorOperation::Merge);
261        assert_eq!(result.value, AccumulatorValue::Integer(120));
262    }
263
264    #[test]
265    fn test_merge_negative_balance() {
266        let addr = test_accumulator_address();
267        let writes = vec![
268            AccumulatorWriteV1 {
269                address: addr.clone(),
270                operation: AccumulatorOperation::Merge,
271                value: AccumulatorValue::Integer(50),
272            },
273            AccumulatorWriteV1 {
274                address: addr.clone(),
275                operation: AccumulatorOperation::Split,
276                value: AccumulatorValue::Integer(100),
277            },
278            AccumulatorWriteV1 {
279                address: addr.clone(),
280                operation: AccumulatorOperation::Split,
281                value: AccumulatorValue::Integer(30),
282            },
283        ];
284
285        let result = AccumulatorWriteV1::merge(writes);
286        assert_eq!(result.operation, AccumulatorOperation::Split);
287        assert_eq!(result.value, AccumulatorValue::Integer(80));
288    }
289
290    #[test]
291    fn test_merge_zero_balance() {
292        let addr = test_accumulator_address();
293        let writes = vec![
294            AccumulatorWriteV1 {
295                address: addr.clone(),
296                operation: AccumulatorOperation::Merge,
297                value: AccumulatorValue::Integer(100),
298            },
299            AccumulatorWriteV1 {
300                address: addr.clone(),
301                operation: AccumulatorOperation::Split,
302                value: AccumulatorValue::Integer(100),
303            },
304        ];
305
306        let result = AccumulatorWriteV1::merge(writes);
307        assert_eq!(result.operation, AccumulatorOperation::Merge);
308        assert_eq!(result.value, AccumulatorValue::Integer(0));
309    }
310
311    #[test]
312    fn test_merge_all_merges() {
313        let addr = test_accumulator_address();
314        let writes = vec![
315            AccumulatorWriteV1 {
316                address: addr.clone(),
317                operation: AccumulatorOperation::Merge,
318                value: AccumulatorValue::Integer(100),
319            },
320            AccumulatorWriteV1 {
321                address: addr.clone(),
322                operation: AccumulatorOperation::Merge,
323                value: AccumulatorValue::Integer(200),
324            },
325            AccumulatorWriteV1 {
326                address: addr.clone(),
327                operation: AccumulatorOperation::Merge,
328                value: AccumulatorValue::Integer(300),
329            },
330        ];
331
332        let result = AccumulatorWriteV1::merge(writes);
333        assert_eq!(result.operation, AccumulatorOperation::Merge);
334        assert_eq!(result.value, AccumulatorValue::Integer(600));
335    }
336
337    #[test]
338    fn test_merge_all_splits() {
339        let addr = test_accumulator_address();
340        let writes = vec![
341            AccumulatorWriteV1 {
342                address: addr.clone(),
343                operation: AccumulatorOperation::Split,
344                value: AccumulatorValue::Integer(100),
345            },
346            AccumulatorWriteV1 {
347                address: addr.clone(),
348                operation: AccumulatorOperation::Split,
349                value: AccumulatorValue::Integer(200),
350            },
351            AccumulatorWriteV1 {
352                address: addr.clone(),
353                operation: AccumulatorOperation::Split,
354                value: AccumulatorValue::Integer(50),
355            },
356        ];
357
358        let result = AccumulatorWriteV1::merge(writes);
359        assert_eq!(result.operation, AccumulatorOperation::Split);
360        assert_eq!(result.value, AccumulatorValue::Integer(350));
361    }
362
363    #[test]
364    fn test_merge_event_digests_single() {
365        let addr = test_accumulator_address();
366        let digest1 = Digest::random();
367        let digest2 = Digest::random();
368
369        let write = AccumulatorWriteV1 {
370            address: addr.clone(),
371            operation: AccumulatorOperation::Merge,
372            value: AccumulatorValue::EventDigest(nonempty![(0, digest1), (1, digest2)]),
373        };
374
375        let result = AccumulatorWriteV1::merge(vec![write.clone()]);
376        assert_eq!(result, write);
377    }
378
379    #[test]
380    fn test_merge_event_digests_multiple() {
381        let addr = test_accumulator_address();
382        let digest1 = Digest::random();
383        let digest2 = Digest::random();
384        let digest3 = Digest::random();
385        let digest4 = Digest::random();
386
387        let writes = vec![
388            AccumulatorWriteV1 {
389                address: addr.clone(),
390                operation: AccumulatorOperation::Merge,
391                value: AccumulatorValue::EventDigest(nonempty![(0, digest1), (1, digest2)]),
392            },
393            AccumulatorWriteV1 {
394                address: addr.clone(),
395                operation: AccumulatorOperation::Merge,
396                value: AccumulatorValue::EventDigest(nonempty![(2, digest3)]),
397            },
398            AccumulatorWriteV1 {
399                address: addr.clone(),
400                operation: AccumulatorOperation::Merge,
401                value: AccumulatorValue::EventDigest(nonempty![(3, digest4)]),
402            },
403        ];
404
405        let result = AccumulatorWriteV1::merge(writes);
406        assert_eq!(result.operation, AccumulatorOperation::Merge);
407        if let AccumulatorValue::EventDigest(digests) = result.value {
408            assert_eq!(digests.len(), 4);
409            assert_eq!(digests[0], (0, digest1));
410            assert_eq!(digests[1], (1, digest2));
411            assert_eq!(digests[2], (2, digest3));
412            assert_eq!(digests[3], (3, digest4));
413        } else {
414            panic!("Expected EventDigest value");
415        }
416    }
417
418    #[test]
419    #[should_panic(expected = "All writes must have the same accumulator address")]
420    fn test_merge_mismatched_addresses() {
421        let addr1 = test_accumulator_address();
422        let addr2 = test_accumulator_address();
423
424        let writes = vec![
425            AccumulatorWriteV1 {
426                address: addr1,
427                operation: AccumulatorOperation::Merge,
428                value: AccumulatorValue::Integer(100),
429            },
430            AccumulatorWriteV1 {
431                address: addr2,
432                operation: AccumulatorOperation::Merge,
433                value: AccumulatorValue::Integer(50),
434            },
435        ];
436
437        AccumulatorWriteV1::merge(writes);
438    }
439
440    #[test]
441    #[should_panic(expected = "All writes must have the same accumulator type")]
442    fn test_merge_mismatched_types() {
443        let addr = SuiAddress::random_for_testing_only();
444        let addr1 = AccumulatorAddress::new(
445            addr,
446            "0x2::balance::Balance<0x2::sui::SUI>".parse().unwrap(),
447        );
448        let addr2 = AccumulatorAddress::new(
449            addr,
450            "0x2::accumulator_settlement::EventStreamHead"
451                .parse()
452                .unwrap(),
453        );
454
455        let writes = vec![
456            AccumulatorWriteV1 {
457                address: addr1,
458                operation: AccumulatorOperation::Merge,
459                value: AccumulatorValue::Integer(100),
460            },
461            AccumulatorWriteV1 {
462                address: addr2,
463                operation: AccumulatorOperation::Merge,
464                value: AccumulatorValue::Integer(50),
465            },
466        ];
467
468        AccumulatorWriteV1::merge(writes);
469    }
470}