1use 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 pub(crate) input_state: ObjectIn,
24 pub(crate) output_state: ObjectOut,
26
27 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#[derive(Eq, PartialEq, Clone, Debug, Serialize, Deserialize)]
76pub enum ObjectIn {
77 NotExist,
78 Exist((VersionDigest, Owner)),
80}
81
82#[derive(Eq, PartialEq, Clone, Debug, Serialize, Deserialize)]
83pub enum AccumulatorOperation {
84 Merge,
86 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 , Digest)>),
95}
96
97#[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 pub operation: AccumulatorOperation,
116 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 NotExist,
204 ObjectWrite((ObjectDigest, Owner)),
206 PackageWrite(VersionDigest),
209 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}