sui_rpc/proto/sui/rpc/v2/
effects.rs

1use super::*;
2use crate::field::FieldMaskTree;
3use crate::merge::Merge;
4use crate::proto::TryFromProtoError;
5use tap::Pipe;
6
7//
8// TransactionEffects
9//
10
11impl From<sui_sdk_types::TransactionEffects> for TransactionEffects {
12    fn from(value: sui_sdk_types::TransactionEffects) -> Self {
13        Self::merge_from(&value, &FieldMaskTree::new_wildcard())
14    }
15}
16
17impl Merge<&sui_sdk_types::TransactionEffects> for TransactionEffects {
18    fn merge(&mut self, source: &sui_sdk_types::TransactionEffects, mask: &FieldMaskTree) {
19        if mask.contains(Self::BCS_FIELD.name) {
20            let mut bcs = Bcs::serialize(&source).unwrap();
21            bcs.name = Some("TransactionEffects".to_owned());
22            self.bcs = Some(bcs);
23        }
24
25        if mask.contains(Self::DIGEST_FIELD.name) {
26            self.digest = Some(source.digest().to_string());
27        }
28
29        match source {
30            sui_sdk_types::TransactionEffects::V1(v1) => self.merge(v1.as_ref(), mask),
31            sui_sdk_types::TransactionEffects::V2(v2) => self.merge(v2.as_ref(), mask),
32        }
33    }
34}
35
36impl Merge<&TransactionEffects> for TransactionEffects {
37    fn merge(
38        &mut self,
39        TransactionEffects {
40            bcs,
41            digest,
42            version,
43            status,
44            epoch,
45            gas_used,
46            transaction_digest,
47            gas_object,
48            events_digest,
49            dependencies,
50            lamport_version,
51            changed_objects,
52            unchanged_consensus_objects,
53            auxiliary_data_digest,
54            unchanged_loaded_runtime_objects,
55        }: &TransactionEffects,
56        mask: &FieldMaskTree,
57    ) {
58        if mask.contains(Self::BCS_FIELD.name) {
59            self.bcs = bcs.clone();
60        }
61
62        if mask.contains(Self::DIGEST_FIELD.name) {
63            self.digest = digest.clone();
64        }
65        if mask.contains(Self::VERSION_FIELD.name) {
66            self.version = *version;
67        }
68
69        if mask.contains(Self::STATUS_FIELD.name) {
70            self.status = status.clone();
71        }
72
73        if mask.contains(Self::EPOCH_FIELD.name) {
74            self.epoch = *epoch;
75        }
76
77        if mask.contains(Self::GAS_USED_FIELD.name) {
78            self.gas_used = *gas_used;
79        }
80
81        if mask.contains(Self::TRANSACTION_DIGEST_FIELD.name) {
82            self.transaction_digest = transaction_digest.clone();
83        }
84
85        if mask.contains(Self::GAS_OBJECT_FIELD.name) {
86            self.gas_object = gas_object.clone();
87        }
88
89        if mask.contains(Self::EVENTS_DIGEST_FIELD.name) {
90            self.events_digest = events_digest.clone();
91        }
92
93        if mask.contains(Self::DEPENDENCIES_FIELD.name) {
94            self.dependencies = dependencies.clone();
95        }
96
97        if mask.contains(Self::LAMPORT_VERSION_FIELD.name) {
98            self.lamport_version = *lamport_version;
99        }
100
101        if mask.contains(Self::CHANGED_OBJECTS_FIELD.name) {
102            self.changed_objects = changed_objects.clone();
103        }
104
105        if mask.contains(Self::UNCHANGED_CONSENSUS_OBJECTS_FIELD.name) {
106            self.unchanged_consensus_objects = unchanged_consensus_objects.clone();
107        }
108
109        if mask.contains(Self::AUXILIARY_DATA_DIGEST_FIELD.name) {
110            self.auxiliary_data_digest = auxiliary_data_digest.clone();
111        }
112
113        if mask.contains(Self::UNCHANGED_LOADED_RUNTIME_OBJECTS_FIELD.name) {
114            self.unchanged_loaded_runtime_objects = unchanged_loaded_runtime_objects.clone();
115        }
116    }
117}
118
119impl TryFrom<&TransactionEffects> for sui_sdk_types::TransactionEffects {
120    type Error = TryFromProtoError;
121
122    fn try_from(value: &TransactionEffects) -> Result<Self, Self::Error> {
123        value
124            .bcs
125            .as_ref()
126            .ok_or_else(|| TryFromProtoError::missing("bcs"))?
127            .deserialize()
128            .map_err(|e| TryFromProtoError::invalid(TransactionEffects::BCS_FIELD, e))
129    }
130}
131
132//
133// TransactionEffectsV1
134//
135
136impl Merge<&sui_sdk_types::TransactionEffectsV1> for TransactionEffects {
137    fn merge(
138        &mut self,
139        sui_sdk_types::TransactionEffectsV1 {
140            status,
141            epoch,
142            gas_used,
143            modified_at_versions,
144            consensus_objects,
145            transaction_digest,
146            created,
147            mutated,
148            unwrapped,
149            deleted,
150            unwrapped_then_deleted,
151            wrapped,
152            gas_object,
153            events_digest,
154            dependencies,
155        }: &sui_sdk_types::TransactionEffectsV1,
156        mask: &FieldMaskTree,
157    ) {
158        if mask.contains(Self::VERSION_FIELD.name) {
159            self.version = Some(1);
160        }
161
162        if mask.contains(Self::STATUS_FIELD.name) {
163            self.status = Some(status.clone().into());
164        }
165
166        if mask.contains(Self::EPOCH_FIELD.name) {
167            self.epoch = Some(*epoch);
168        }
169
170        if mask.contains(Self::GAS_USED_FIELD.name) {
171            self.gas_used = Some(gas_used.clone().into());
172        }
173
174        if mask.contains(Self::TRANSACTION_DIGEST_FIELD.name) {
175            self.transaction_digest = Some(transaction_digest.to_string());
176        }
177
178        if mask.contains(Self::EVENTS_DIGEST_FIELD.name) {
179            self.events_digest = events_digest.map(|d| d.to_string());
180        }
181
182        if mask.contains(Self::DEPENDENCIES_FIELD.name) {
183            self.dependencies = dependencies.iter().map(ToString::to_string).collect();
184        }
185
186        if mask.contains(Self::CHANGED_OBJECTS_FIELD.name)
187            || mask.contains(Self::UNCHANGED_CONSENSUS_OBJECTS_FIELD.name)
188            || mask.contains(Self::GAS_OBJECT_FIELD.name)
189        {
190            let mut changed_objects = Vec::new();
191            let mut unchanged_consensus_objects = Vec::new();
192
193            for object in created {
194                let change = ChangedObject {
195                    object_id: Some(object.reference.object_id().to_string()),
196                    input_state: Some(changed_object::InputObjectState::DoesNotExist.into()),
197                    input_version: None,
198                    input_digest: None,
199                    input_owner: None,
200                    output_state: Some(changed_object::OutputObjectState::ObjectWrite.into()),
201                    output_version: Some(object.reference.version()),
202                    output_digest: Some(object.reference.digest().to_string()),
203                    output_owner: Some(object.owner.into()),
204                    id_operation: Some(changed_object::IdOperation::Created.into()),
205                    object_type: None,
206                };
207
208                changed_objects.push(change);
209            }
210
211            for object in mutated {
212                let change = ChangedObject {
213                    object_id: Some(object.reference.object_id().to_string()),
214                    input_state: Some(changed_object::InputObjectState::Exists.into()),
215                    input_version: None,
216                    input_digest: None,
217                    input_owner: None,
218                    output_state: Some(changed_object::OutputObjectState::ObjectWrite.into()),
219                    output_version: Some(object.reference.version()),
220                    output_digest: Some(object.reference.digest().to_string()),
221                    output_owner: Some(object.owner.into()),
222                    id_operation: Some(changed_object::IdOperation::None.into()),
223                    object_type: None,
224                };
225
226                changed_objects.push(change);
227            }
228
229            for object in unwrapped {
230                let change = ChangedObject {
231                    object_id: Some(object.reference.object_id().to_string()),
232                    input_state: Some(changed_object::InputObjectState::DoesNotExist.into()),
233                    input_version: None,
234                    input_digest: None,
235                    input_owner: None,
236                    output_state: Some(changed_object::OutputObjectState::ObjectWrite.into()),
237                    output_version: Some(object.reference.version()),
238                    output_digest: Some(object.reference.digest().to_string()),
239                    output_owner: Some(object.owner.into()),
240                    id_operation: Some(changed_object::IdOperation::None.into()),
241                    object_type: None,
242                };
243
244                changed_objects.push(change);
245            }
246
247            for object in deleted {
248                let change = ChangedObject {
249                    object_id: Some(object.object_id().to_string()),
250                    input_state: Some(changed_object::InputObjectState::Exists.into()),
251                    input_version: None,
252                    input_digest: None,
253                    input_owner: None,
254                    output_state: Some(changed_object::OutputObjectState::DoesNotExist.into()),
255                    output_version: Some(object.version()),
256                    output_digest: Some(object.digest().to_string()),
257                    output_owner: None,
258                    id_operation: Some(changed_object::IdOperation::Deleted.into()),
259                    object_type: None,
260                };
261
262                changed_objects.push(change);
263            }
264
265            for object in unwrapped_then_deleted {
266                let change = ChangedObject {
267                    object_id: Some(object.object_id().to_string()),
268                    input_state: Some(changed_object::InputObjectState::DoesNotExist.into()),
269                    input_version: None,
270                    input_digest: None,
271                    input_owner: None,
272                    output_state: Some(changed_object::OutputObjectState::DoesNotExist.into()),
273                    output_version: Some(object.version()),
274                    output_digest: Some(object.digest().to_string()),
275                    output_owner: None,
276                    id_operation: Some(changed_object::IdOperation::Deleted.into()),
277                    object_type: None,
278                };
279
280                changed_objects.push(change);
281            }
282
283            for object in wrapped {
284                let change = ChangedObject {
285                    object_id: Some(object.object_id().to_string()),
286                    input_state: Some(changed_object::InputObjectState::Exists.into()),
287                    input_version: None,
288                    input_digest: None,
289                    input_owner: None,
290                    output_state: Some(changed_object::OutputObjectState::DoesNotExist.into()),
291                    output_version: Some(object.version()),
292                    output_digest: Some(object.digest().to_string()),
293                    output_owner: None,
294                    id_operation: Some(changed_object::IdOperation::Deleted.into()),
295                    object_type: None,
296                };
297
298                changed_objects.push(change);
299            }
300
301            for modified_at_version in modified_at_versions {
302                let object_id = modified_at_version.object_id.to_string();
303                let version = modified_at_version.version;
304                if let Some(changed_object) = changed_objects
305                    .iter_mut()
306                    .find(|object| object.object_id() == object_id)
307                {
308                    changed_object.input_version = Some(version);
309                }
310            }
311
312            for object in consensus_objects {
313                let object_id = object.object_id().to_string();
314                let version = object.version();
315                let digest = object.digest().to_string();
316
317                if let Some(changed_object) = changed_objects
318                    .iter_mut()
319                    .find(|object| object.object_id() == object_id)
320                {
321                    changed_object.input_version = Some(version);
322                    changed_object.input_digest = Some(digest);
323                } else {
324                    let unchanged_consensus_object = UnchangedConsensusObject {
325                        kind: Some(
326                            unchanged_consensus_object::UnchangedConsensusObjectKind::ReadOnlyRoot
327                                .into(),
328                        ),
329                        object_id: Some(object_id),
330                        version: Some(version),
331                        digest: Some(digest),
332                        object_type: None,
333                    };
334
335                    unchanged_consensus_objects.push(unchanged_consensus_object);
336                }
337            }
338
339            if mask.contains(Self::GAS_OBJECT_FIELD.name) {
340                let gas_object_id = gas_object.reference.object_id().to_string();
341                self.gas_object = changed_objects
342                    .iter()
343                    .find(|object| object.object_id() == gas_object_id)
344                    .cloned();
345            }
346
347            if mask.contains(Self::CHANGED_OBJECTS_FIELD.name) {
348                self.changed_objects = changed_objects;
349            }
350
351            if mask.contains(Self::UNCHANGED_CONSENSUS_OBJECTS_FIELD.name) {
352                self.unchanged_consensus_objects = unchanged_consensus_objects;
353            }
354        }
355    }
356}
357
358//
359// TransactionEffectsV2
360//
361
362impl Merge<&sui_sdk_types::TransactionEffectsV2> for TransactionEffects {
363    fn merge(
364        &mut self,
365        sui_sdk_types::TransactionEffectsV2 {
366            status,
367            epoch,
368            gas_used,
369            transaction_digest,
370            gas_object_index,
371            events_digest,
372            dependencies,
373            lamport_version,
374            changed_objects,
375            unchanged_consensus_objects,
376            auxiliary_data_digest,
377        }: &sui_sdk_types::TransactionEffectsV2,
378        mask: &FieldMaskTree,
379    ) {
380        if mask.contains(Self::VERSION_FIELD.name) {
381            self.version = Some(2);
382        }
383
384        if mask.contains(Self::STATUS_FIELD.name) {
385            self.status = Some(status.clone().into());
386        }
387
388        if mask.contains(Self::EPOCH_FIELD.name) {
389            self.epoch = Some(*epoch);
390        }
391
392        if mask.contains(Self::GAS_USED_FIELD.name) {
393            self.gas_used = Some(gas_used.clone().into());
394        }
395
396        if mask.contains(Self::TRANSACTION_DIGEST_FIELD.name) {
397            self.transaction_digest = Some(transaction_digest.to_string());
398        }
399
400        if mask.contains(Self::GAS_OBJECT_FIELD.name) {
401            self.gas_object = gas_object_index
402                .map(|index| changed_objects.get(index as usize).cloned().map(Into::into))
403                .flatten();
404        }
405
406        if mask.contains(Self::EVENTS_DIGEST_FIELD.name) {
407            self.events_digest = events_digest.map(|d| d.to_string());
408        }
409
410        if mask.contains(Self::DEPENDENCIES_FIELD.name) {
411            self.dependencies = dependencies.iter().map(ToString::to_string).collect();
412        }
413
414        if mask.contains(Self::LAMPORT_VERSION_FIELD.name) {
415            self.lamport_version = Some(*lamport_version);
416        }
417
418        if mask.contains(Self::CHANGED_OBJECTS_FIELD.name) {
419            self.changed_objects = changed_objects
420                .clone()
421                .into_iter()
422                .map(Into::into)
423                .collect();
424        }
425
426        for object in self.changed_objects.iter_mut().chain(&mut self.gas_object) {
427            if object.output_digest.is_some() && object.output_version.is_none() {
428                object.output_version = Some(*lamport_version);
429            }
430        }
431
432        if mask.contains(Self::UNCHANGED_CONSENSUS_OBJECTS_FIELD.name) {
433            self.unchanged_consensus_objects = unchanged_consensus_objects
434                .clone()
435                .into_iter()
436                .map(Into::into)
437                .collect();
438        }
439
440        if mask.contains(Self::AUXILIARY_DATA_DIGEST_FIELD.name) {
441            self.auxiliary_data_digest = auxiliary_data_digest.map(|d| d.to_string());
442        }
443    }
444}
445
446//
447// ChangedObject
448//
449
450impl From<sui_sdk_types::ChangedObject> for ChangedObject {
451    fn from(value: sui_sdk_types::ChangedObject) -> Self {
452        use changed_object::InputObjectState;
453        use changed_object::OutputObjectState;
454
455        let mut message = Self {
456            object_id: Some(value.object_id.to_string()),
457            ..Default::default()
458        };
459
460        // Input State
461        let input_state = match value.input_state {
462            sui_sdk_types::ObjectIn::NotExist => InputObjectState::DoesNotExist,
463            sui_sdk_types::ObjectIn::Exist {
464                version,
465                digest,
466                owner,
467            } => {
468                message.input_version = Some(version);
469                message.input_digest = Some(digest.to_string());
470                message.input_owner = Some(owner.into());
471                InputObjectState::Exists
472            }
473            _ => InputObjectState::Unknown,
474        };
475        message.set_input_state(input_state);
476
477        // Output State
478        let output_state = match value.output_state {
479            sui_sdk_types::ObjectOut::NotExist => OutputObjectState::DoesNotExist,
480            sui_sdk_types::ObjectOut::ObjectWrite { digest, owner } => {
481                message.output_digest = Some(digest.to_string());
482                message.output_owner = Some(owner.into());
483                OutputObjectState::ObjectWrite
484            }
485            sui_sdk_types::ObjectOut::PackageWrite { version, digest } => {
486                message.output_version = Some(version);
487                message.output_digest = Some(digest.to_string());
488                OutputObjectState::PackageWrite
489            }
490            _ => OutputObjectState::Unknown,
491        };
492        message.set_output_state(output_state);
493
494        message.set_id_operation(value.id_operation.into());
495        message
496    }
497}
498
499impl TryFrom<&ChangedObject> for sui_sdk_types::ChangedObject {
500    type Error = TryFromProtoError;
501
502    fn try_from(value: &ChangedObject) -> Result<Self, Self::Error> {
503        use changed_object::InputObjectState;
504        use changed_object::OutputObjectState;
505
506        let object_id = value
507            .object_id
508            .as_ref()
509            .ok_or_else(|| TryFromProtoError::missing("object_id"))?
510            .parse()
511            .map_err(|e| TryFromProtoError::invalid(ChangedObject::OBJECT_ID_FIELD, e))?;
512
513        let input_state = match value.input_state() {
514            InputObjectState::Unknown => {
515                return Err(TryFromProtoError::invalid(
516                    ChangedObject::INPUT_STATE_FIELD,
517                    "unknown InputObjectState",
518                ));
519            }
520            InputObjectState::DoesNotExist => sui_sdk_types::ObjectIn::NotExist,
521            InputObjectState::Exists => sui_sdk_types::ObjectIn::Exist {
522                version: value
523                    .input_version
524                    .ok_or_else(|| TryFromProtoError::missing("version"))?,
525                digest: value
526                    .input_digest
527                    .as_ref()
528                    .ok_or_else(|| TryFromProtoError::missing("digest"))?
529                    .parse()
530                    .map_err(|e| {
531                        TryFromProtoError::invalid(ChangedObject::INPUT_DIGEST_FIELD, e)
532                    })?,
533                owner: value
534                    .input_owner
535                    .as_ref()
536                    .ok_or_else(|| TryFromProtoError::missing("owner"))?
537                    .try_into()?,
538            },
539        };
540
541        let output_state = match value.output_state() {
542            OutputObjectState::Unknown => {
543                return Err(TryFromProtoError::invalid(
544                    ChangedObject::OUTPUT_STATE_FIELD,
545                    "unknown OutputObjectState",
546                ))
547            }
548            OutputObjectState::DoesNotExist => sui_sdk_types::ObjectOut::NotExist,
549            OutputObjectState::ObjectWrite => sui_sdk_types::ObjectOut::ObjectWrite {
550                digest: value
551                    .output_digest
552                    .as_ref()
553                    .ok_or_else(|| TryFromProtoError::missing("digest"))?
554                    .parse()
555                    .map_err(|e| {
556                        TryFromProtoError::invalid(ChangedObject::OUTPUT_DIGEST_FIELD, e)
557                    })?,
558
559                owner: value
560                    .output_owner
561                    .as_ref()
562                    .ok_or_else(|| TryFromProtoError::missing("owner"))?
563                    .try_into()?,
564            },
565            OutputObjectState::PackageWrite => sui_sdk_types::ObjectOut::PackageWrite {
566                version: value
567                    .output_version
568                    .ok_or_else(|| TryFromProtoError::missing("version"))?,
569                digest: value
570                    .output_digest
571                    .as_ref()
572                    .ok_or_else(|| TryFromProtoError::missing("digest"))?
573                    .parse()
574                    .map_err(|e| {
575                        TryFromProtoError::invalid(ChangedObject::OUTPUT_DIGEST_FIELD, e)
576                    })?,
577            },
578        };
579
580        let id_operation = value.id_operation().try_into()?;
581
582        Ok(Self {
583            object_id,
584            input_state,
585            output_state,
586            id_operation,
587        })
588    }
589}
590
591//
592// IdOperation
593//
594
595impl From<sui_sdk_types::IdOperation> for changed_object::IdOperation {
596    fn from(value: sui_sdk_types::IdOperation) -> Self {
597        use sui_sdk_types::IdOperation::*;
598
599        match value {
600            None => Self::None,
601            Created => Self::Created,
602            Deleted => Self::Deleted,
603            _ => Self::Unknown,
604        }
605    }
606}
607
608impl TryFrom<changed_object::IdOperation> for sui_sdk_types::IdOperation {
609    type Error = TryFromProtoError;
610
611    fn try_from(value: changed_object::IdOperation) -> Result<Self, Self::Error> {
612        use changed_object::IdOperation;
613
614        match value {
615            IdOperation::Unknown => {
616                return Err(TryFromProtoError::invalid(
617                    "id_operation",
618                    "unknown IdOperation",
619                ))
620            }
621            IdOperation::None => Self::None,
622            IdOperation::Created => Self::Created,
623            IdOperation::Deleted => Self::Deleted,
624        }
625        .pipe(Ok)
626    }
627}
628
629//
630// UnchangedConsensusObject
631//
632
633impl From<sui_sdk_types::UnchangedConsensusObject> for UnchangedConsensusObject {
634    fn from(value: sui_sdk_types::UnchangedConsensusObject) -> Self {
635        use sui_sdk_types::UnchangedConsensusKind::*;
636        use unchanged_consensus_object::UnchangedConsensusObjectKind;
637
638        let mut message = Self {
639            object_id: Some(value.object_id.to_string()),
640            ..Default::default()
641        };
642
643        let kind = match value.kind {
644            ReadOnlyRoot { version, digest } => {
645                message.version = Some(version);
646                message.digest = Some(digest.to_string());
647                UnchangedConsensusObjectKind::ReadOnlyRoot
648            }
649            MutateDeleted { version } => {
650                message.version = Some(version);
651                UnchangedConsensusObjectKind::MutateConsensusStreamEnded
652            }
653            ReadDeleted { version } => {
654                message.version = Some(version);
655                UnchangedConsensusObjectKind::ReadConsensusStreamEnded
656            }
657            Canceled { version } => {
658                message.version = Some(version);
659                UnchangedConsensusObjectKind::Canceled
660            }
661            PerEpochConfig => UnchangedConsensusObjectKind::PerEpochConfig,
662            PerEpochConfigWithSequenceNumber { version } => {
663                message.version = Some(version);
664                UnchangedConsensusObjectKind::PerEpochConfig
665            }
666            _ => UnchangedConsensusObjectKind::Unknown,
667        };
668
669        message.set_kind(kind);
670        message
671    }
672}
673
674impl TryFrom<&UnchangedConsensusObject> for sui_sdk_types::UnchangedConsensusObject {
675    type Error = TryFromProtoError;
676
677    fn try_from(value: &UnchangedConsensusObject) -> Result<Self, Self::Error> {
678        use sui_sdk_types::UnchangedConsensusKind;
679        use unchanged_consensus_object::UnchangedConsensusObjectKind;
680
681        let object_id = value
682            .object_id
683            .as_ref()
684            .ok_or_else(|| TryFromProtoError::missing("object_id"))?
685            .parse()
686            .map_err(|e| {
687                TryFromProtoError::invalid(UnchangedConsensusObject::OBJECT_ID_FIELD, e)
688            })?;
689
690        let kind = match value.kind() {
691            UnchangedConsensusObjectKind::Unknown => {
692                return Err(TryFromProtoError::invalid(
693                    UnchangedConsensusObject::KIND_FIELD,
694                    "unknown InputKind",
695                ))
696            }
697
698            UnchangedConsensusObjectKind::ReadOnlyRoot => UnchangedConsensusKind::ReadOnlyRoot {
699                version: value
700                    .version
701                    .ok_or_else(|| TryFromProtoError::missing("version"))?,
702
703                digest: value
704                    .digest
705                    .as_ref()
706                    .ok_or_else(|| TryFromProtoError::missing("digest"))?
707                    .parse()
708                    .map_err(|e| {
709                        TryFromProtoError::invalid(UnchangedConsensusObject::DIGEST_FIELD, e)
710                    })?,
711            },
712            UnchangedConsensusObjectKind::MutateConsensusStreamEnded => {
713                UnchangedConsensusKind::MutateDeleted {
714                    version: value
715                        .version
716                        .ok_or_else(|| TryFromProtoError::missing("version"))?,
717                }
718            }
719            UnchangedConsensusObjectKind::ReadConsensusStreamEnded => {
720                UnchangedConsensusKind::ReadDeleted {
721                    version: value
722                        .version
723                        .ok_or_else(|| TryFromProtoError::missing("version"))?,
724                }
725            }
726            UnchangedConsensusObjectKind::Canceled => UnchangedConsensusKind::Canceled {
727                version: value
728                    .version
729                    .ok_or_else(|| TryFromProtoError::missing("version"))?,
730            },
731            UnchangedConsensusObjectKind::PerEpochConfig => {
732                if let Some(version) = value.version {
733                    UnchangedConsensusKind::PerEpochConfigWithSequenceNumber { version }
734                } else {
735                    UnchangedConsensusKind::PerEpochConfig
736                }
737            }
738        };
739
740        Ok(Self { object_id, kind })
741    }
742}