1use super::{SUI_BRIDGE_OBJECT_ID, base_types::*, error::*};
6use crate::accumulator_root::{AccumulatorObjId, AccumulatorValue};
7use crate::authenticator_state::ActiveJwk;
8use crate::balance::Balance;
9use crate::committee::{Committee, EpochId, ProtocolVersion};
10use crate::crypto::{
11 AuthoritySignInfo, AuthoritySignInfoTrait, AuthoritySignature, AuthorityStrongQuorumSignInfo,
12 DefaultHash, Ed25519SuiSignature, EmptySignInfo, RandomnessRound, Signature, Signer,
13 SuiSignatureInner, ToFromBytes, default_hash,
14};
15use crate::digests::{AdditionalConsensusStateDigest, CertificateDigest, SenderSignedDataDigest};
16use crate::digests::{ChainIdentifier, ConsensusCommitDigest, ZKLoginInputsDigest};
17use crate::execution::{ExecutionTimeObservationKey, SharedInput};
18use crate::gas_coin::GAS;
19use crate::message_envelope::{Envelope, Message, TrustedEnvelope, VerifiedEnvelope};
20use crate::messages_checkpoint::CheckpointTimestamp;
21use crate::messages_consensus::{
22 ConsensusCommitPrologue, ConsensusCommitPrologueV2, ConsensusCommitPrologueV3,
23 ConsensusCommitPrologueV4, ConsensusDeterminedVersionAssignments,
24};
25use crate::object::{MoveObject, Object, Owner};
26use crate::programmable_transaction_builder::ProgrammableTransactionBuilder;
27use crate::signature::{GenericSignature, VerifyParams};
28use crate::signature_verification::{
29 VerifiedDigestCache, verify_sender_signed_data_message_signatures,
30};
31use crate::type_input::TypeInput;
32use crate::{
33 SUI_AUTHENTICATOR_STATE_OBJECT_ID, SUI_CLOCK_OBJECT_ID, SUI_CLOCK_OBJECT_SHARED_VERSION,
34 SUI_FRAMEWORK_PACKAGE_ID, SUI_RANDOMNESS_STATE_OBJECT_ID, SUI_SYSTEM_STATE_OBJECT_ID,
35 SUI_SYSTEM_STATE_OBJECT_SHARED_VERSION,
36};
37use enum_dispatch::enum_dispatch;
38use fastcrypto::{encoding::Base64, hash::HashFunction};
39use itertools::{Either, Itertools};
40use move_core_types::{ident_str, identifier};
41use move_core_types::{identifier::Identifier, language_storage::TypeTag};
42use nonempty::{NonEmpty, nonempty};
43use serde::{Deserialize, Serialize};
44use shared_crypto::intent::{Intent, IntentMessage, IntentScope};
45use std::collections::btree_map::Entry;
46use std::fmt::Write;
47use std::fmt::{Debug, Display, Formatter};
48use std::iter::once;
49use std::sync::Arc;
50use std::time::Duration;
51use std::{
52 collections::{BTreeMap, BTreeSet, HashSet},
53 hash::Hash,
54 iter,
55};
56use strum::IntoStaticStr;
57use sui_protocol_config::{PerObjectCongestionControlMode, ProtocolConfig};
58use tap::Pipe;
59use tracing::trace;
60
61#[cfg(test)]
62#[path = "unit_tests/transaction_serialization_tests.rs"]
63mod transaction_serialization_tests;
64
65pub const TEST_ONLY_GAS_UNIT_FOR_TRANSFER: u64 = 10_000;
66pub const TEST_ONLY_GAS_UNIT_FOR_OBJECT_BASICS: u64 = 50_000;
67pub const TEST_ONLY_GAS_UNIT_FOR_PUBLISH: u64 = 70_000;
68pub const TEST_ONLY_GAS_UNIT_FOR_STAKING: u64 = 50_000;
69pub const TEST_ONLY_GAS_UNIT_FOR_GENERIC: u64 = 50_000;
70pub const TEST_ONLY_GAS_UNIT_FOR_SPLIT_COIN: u64 = 10_000;
71pub const TEST_ONLY_GAS_UNIT_FOR_HEAVY_COMPUTATION_STORAGE: u64 = 5_000_000;
76
77pub const GAS_PRICE_FOR_SYSTEM_TX: u64 = 1;
78
79pub const DEFAULT_VALIDATOR_GAS_PRICE: u64 = 1000;
80
81const BLOCKED_MOVE_FUNCTIONS: [(ObjectID, &str, &str); 0] = [];
82
83#[cfg(test)]
84#[path = "unit_tests/messages_tests.rs"]
85mod messages_tests;
86
87#[cfg(test)]
88#[path = "unit_tests/balance_withdraw_tests.rs"]
89mod balance_withdraw_tests;
90
91#[cfg(test)]
92#[path = "unit_tests/address_balance_gas_tests.rs"]
93mod address_balance_gas_tests;
94
95#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)]
96pub enum CallArg {
97 Pure(Vec<u8>),
99 Object(ObjectArg),
101 FundsWithdrawal(FundsWithdrawalArg),
105}
106
107impl CallArg {
108 pub const SUI_SYSTEM_MUT: Self = Self::Object(ObjectArg::SUI_SYSTEM_MUT);
109 pub const CLOCK_IMM: Self = Self::Object(ObjectArg::SharedObject {
110 id: SUI_CLOCK_OBJECT_ID,
111 initial_shared_version: SUI_CLOCK_OBJECT_SHARED_VERSION,
112 mutability: SharedObjectMutability::Immutable,
113 });
114 pub const CLOCK_MUT: Self = Self::Object(ObjectArg::SharedObject {
115 id: SUI_CLOCK_OBJECT_ID,
116 initial_shared_version: SUI_CLOCK_OBJECT_SHARED_VERSION,
117 mutability: SharedObjectMutability::Mutable,
118 });
119}
120
121#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy, Serialize, Deserialize)]
122pub enum ObjectArg {
123 ImmOrOwnedObject(ObjectRef),
125 SharedObject {
128 id: ObjectID,
129 initial_shared_version: SequenceNumber,
130 mutability: SharedObjectMutability,
133 },
134 Receiving(ObjectRef),
136}
137
138#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)]
139pub enum Reservation {
140 EntireBalance,
143 MaxAmountU64(u64),
145}
146
147#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)]
148pub enum WithdrawalTypeArg {
149 Balance(TypeInput),
150}
151
152impl WithdrawalTypeArg {
153 pub fn to_type_tag(&self) -> UserInputResult<TypeTag> {
156 match self {
157 WithdrawalTypeArg::Balance(type_param) => {
158 Ok(Balance::type_tag(type_param.to_type_tag().map_err(
159 |e| UserInputError::InvalidWithdrawReservation {
160 error: e.to_string(),
161 },
162 )?))
163 }
164 }
165 }
166
167 pub fn get_balance_type_param(&self) -> anyhow::Result<Option<TypeTag>> {
171 match self {
172 WithdrawalTypeArg::Balance(type_param) => type_param.to_type_tag().map(Some),
173 }
174 }
175}
176
177#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)]
179pub struct FundsWithdrawalArg {
180 pub reservation: Reservation,
182 pub type_arg: WithdrawalTypeArg,
184 pub withdraw_from: WithdrawFrom,
186}
187
188#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)]
189pub enum WithdrawFrom {
190 Sender,
192 Sponsor,
194 }
196
197impl FundsWithdrawalArg {
198 pub fn balance_from_sender(amount: u64, balance_type: TypeInput) -> Self {
200 Self {
201 reservation: Reservation::MaxAmountU64(amount),
202 type_arg: WithdrawalTypeArg::Balance(balance_type),
203 withdraw_from: WithdrawFrom::Sender,
204 }
205 }
206
207 pub fn balance_from_sponsor(amount: u64, balance_type: TypeInput) -> Self {
209 Self {
210 reservation: Reservation::MaxAmountU64(amount),
211 type_arg: WithdrawalTypeArg::Balance(balance_type),
212 withdraw_from: WithdrawFrom::Sponsor,
213 }
214 }
215}
216
217fn type_input_validity_check(
218 tag: &TypeInput,
219 config: &ProtocolConfig,
220 starting_count: &mut usize,
221) -> UserInputResult<()> {
222 let mut stack = vec![(tag, 1)];
223 while let Some((tag, depth)) = stack.pop() {
224 *starting_count += 1;
225 fp_ensure!(
226 *starting_count < config.max_type_arguments() as usize,
227 UserInputError::SizeLimitExceeded {
228 limit: "maximum type arguments in a call transaction".to_string(),
229 value: config.max_type_arguments().to_string()
230 }
231 );
232 fp_ensure!(
233 depth < config.max_type_argument_depth(),
234 UserInputError::SizeLimitExceeded {
235 limit: "maximum type argument depth in a call transaction".to_string(),
236 value: config.max_type_argument_depth().to_string()
237 }
238 );
239 match tag {
240 TypeInput::Bool
241 | TypeInput::U8
242 | TypeInput::U64
243 | TypeInput::U128
244 | TypeInput::Address
245 | TypeInput::Signer
246 | TypeInput::U16
247 | TypeInput::U32
248 | TypeInput::U256 => (),
249 TypeInput::Vector(t) => {
250 stack.push((t, depth + 1));
251 }
252 TypeInput::Struct(s) => {
253 let next_depth = depth + 1;
254 if config.validate_identifier_inputs() {
255 fp_ensure!(
256 identifier::is_valid(&s.module),
257 UserInputError::InvalidIdentifier {
258 error: s.module.clone()
259 }
260 );
261 fp_ensure!(
262 identifier::is_valid(&s.name),
263 UserInputError::InvalidIdentifier {
264 error: s.name.clone()
265 }
266 );
267 }
268 stack.extend(s.type_params.iter().map(|t| (t, next_depth)));
269 }
270 }
271 }
272 Ok(())
273}
274
275#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)]
276pub struct ChangeEpoch {
277 pub epoch: EpochId,
279 pub protocol_version: ProtocolVersion,
281 pub storage_charge: u64,
283 pub computation_charge: u64,
285 pub storage_rebate: u64,
287 pub non_refundable_storage_fee: u64,
289 pub epoch_start_timestamp_ms: u64,
291 pub system_packages: Vec<(SequenceNumber, Vec<Vec<u8>>, Vec<ObjectID>)>,
297}
298
299#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)]
300pub struct GenesisTransaction {
301 pub objects: Vec<GenesisObject>,
302}
303
304#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)]
305pub enum GenesisObject {
306 RawObject {
307 data: crate::object::Data,
308 owner: crate::object::Owner,
309 },
310}
311
312impl GenesisObject {
313 pub fn id(&self) -> ObjectID {
314 match self {
315 GenesisObject::RawObject { data, .. } => data.id(),
316 }
317 }
318}
319
320#[derive(Debug, Hash, PartialEq, Eq, Clone, Serialize, Deserialize)]
321pub struct AuthenticatorStateExpire {
322 pub min_epoch: u64,
324 pub authenticator_obj_initial_shared_version: SequenceNumber,
326}
327
328impl AuthenticatorStateExpire {
329 pub fn authenticator_obj_initial_shared_version(&self) -> SequenceNumber {
330 self.authenticator_obj_initial_shared_version
331 }
332}
333
334#[derive(Debug, Hash, PartialEq, Eq, Clone, Serialize, Deserialize)]
335pub enum StoredExecutionTimeObservations {
336 V1(Vec<(ExecutionTimeObservationKey, Vec<(AuthorityName, Duration)>)>),
337}
338
339impl StoredExecutionTimeObservations {
340 pub fn unwrap_v1(self) -> Vec<(ExecutionTimeObservationKey, Vec<(AuthorityName, Duration)>)> {
341 match self {
342 Self::V1(observations) => observations,
343 }
344 }
345
346 pub fn filter_and_sort_v1<P>(&self, predicate: P, limit: usize) -> Self
347 where
348 P: FnMut(&&(ExecutionTimeObservationKey, Vec<(AuthorityName, Duration)>)) -> bool,
349 {
350 match self {
351 Self::V1(observations) => Self::V1(
352 observations
353 .iter()
354 .filter(predicate)
355 .sorted_by_key(|(key, _)| key)
356 .take(limit)
357 .cloned()
358 .collect(),
359 ),
360 }
361 }
362
363 pub fn chunk_observations(&self, chunk_size: usize) -> Vec<Self> {
366 match self {
367 Self::V1(observations) => {
368 if chunk_size == 0 {
369 return vec![];
370 }
371 observations
372 .chunks(chunk_size)
373 .map(|chunk| Self::V1(chunk.to_vec()))
374 .collect()
375 }
376 }
377 }
378
379 pub fn merge_sorted_chunks(chunks: Vec<Self>) -> Self {
382 let mut all_observations = Vec::new();
383
384 for chunk in chunks {
385 match chunk {
386 Self::V1(observations) => {
387 all_observations.extend(observations);
388 }
389 }
390 }
391
392 Self::V1(all_observations)
393 }
394}
395
396#[derive(Debug, Hash, PartialEq, Eq, Clone, Serialize, Deserialize)]
397pub struct AuthenticatorStateUpdate {
398 pub epoch: u64,
400 pub round: u64,
402 pub new_active_jwks: Vec<ActiveJwk>,
404 pub authenticator_obj_initial_shared_version: SequenceNumber,
406 }
409
410impl AuthenticatorStateUpdate {
411 pub fn authenticator_obj_initial_shared_version(&self) -> SequenceNumber {
412 self.authenticator_obj_initial_shared_version
413 }
414}
415
416#[derive(Debug, Hash, PartialEq, Eq, Clone, Serialize, Deserialize)]
417pub struct RandomnessStateUpdate {
418 pub epoch: u64,
420 pub randomness_round: RandomnessRound,
422 pub random_bytes: Vec<u8>,
424 pub randomness_obj_initial_shared_version: SequenceNumber,
426 }
429
430impl RandomnessStateUpdate {
431 pub fn randomness_obj_initial_shared_version(&self) -> SequenceNumber {
432 self.randomness_obj_initial_shared_version
433 }
434}
435
436#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize, IntoStaticStr)]
437pub enum TransactionKind {
438 ProgrammableTransaction(ProgrammableTransaction),
440 ChangeEpoch(ChangeEpoch),
452 Genesis(GenesisTransaction),
453 ConsensusCommitPrologue(ConsensusCommitPrologue),
454 AuthenticatorStateUpdate(AuthenticatorStateUpdate),
455
456 EndOfEpochTransaction(Vec<EndOfEpochTransactionKind>),
459
460 RandomnessStateUpdate(RandomnessStateUpdate),
461 ConsensusCommitPrologueV2(ConsensusCommitPrologueV2),
463
464 ConsensusCommitPrologueV3(ConsensusCommitPrologueV3),
465 ConsensusCommitPrologueV4(ConsensusCommitPrologueV4),
466
467 ProgrammableSystemTransaction(ProgrammableTransaction),
469 }
471
472#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize, IntoStaticStr)]
474pub enum EndOfEpochTransactionKind {
475 ChangeEpoch(ChangeEpoch),
476 AuthenticatorStateCreate,
477 AuthenticatorStateExpire(AuthenticatorStateExpire),
478 RandomnessStateCreate,
479 DenyListStateCreate,
480 BridgeStateCreate(ChainIdentifier),
481 BridgeCommitteeInit(SequenceNumber),
482 StoreExecutionTimeObservations(StoredExecutionTimeObservations),
483 AccumulatorRootCreate,
484 CoinRegistryCreate,
485 DisplayRegistryCreate,
486}
487
488impl EndOfEpochTransactionKind {
489 pub fn new_change_epoch(
490 next_epoch: EpochId,
491 protocol_version: ProtocolVersion,
492 storage_charge: u64,
493 computation_charge: u64,
494 storage_rebate: u64,
495 non_refundable_storage_fee: u64,
496 epoch_start_timestamp_ms: u64,
497 system_packages: Vec<(SequenceNumber, Vec<Vec<u8>>, Vec<ObjectID>)>,
498 ) -> Self {
499 Self::ChangeEpoch(ChangeEpoch {
500 epoch: next_epoch,
501 protocol_version,
502 storage_charge,
503 computation_charge,
504 storage_rebate,
505 non_refundable_storage_fee,
506 epoch_start_timestamp_ms,
507 system_packages,
508 })
509 }
510
511 pub fn new_authenticator_state_expire(
512 min_epoch: u64,
513 authenticator_obj_initial_shared_version: SequenceNumber,
514 ) -> Self {
515 Self::AuthenticatorStateExpire(AuthenticatorStateExpire {
516 min_epoch,
517 authenticator_obj_initial_shared_version,
518 })
519 }
520
521 pub fn new_authenticator_state_create() -> Self {
522 Self::AuthenticatorStateCreate
523 }
524
525 pub fn new_randomness_state_create() -> Self {
526 Self::RandomnessStateCreate
527 }
528
529 pub fn new_accumulator_root_create() -> Self {
530 Self::AccumulatorRootCreate
531 }
532
533 pub fn new_coin_registry_create() -> Self {
534 Self::CoinRegistryCreate
535 }
536
537 pub fn new_display_registry_create() -> Self {
538 Self::DisplayRegistryCreate
539 }
540
541 pub fn new_deny_list_state_create() -> Self {
542 Self::DenyListStateCreate
543 }
544
545 pub fn new_bridge_create(chain_identifier: ChainIdentifier) -> Self {
546 Self::BridgeStateCreate(chain_identifier)
547 }
548
549 pub fn init_bridge_committee(bridge_shared_version: SequenceNumber) -> Self {
550 Self::BridgeCommitteeInit(bridge_shared_version)
551 }
552
553 pub fn new_store_execution_time_observations(
554 estimates: StoredExecutionTimeObservations,
555 ) -> Self {
556 Self::StoreExecutionTimeObservations(estimates)
557 }
558
559 fn input_objects(&self) -> Vec<InputObjectKind> {
560 match self {
561 Self::ChangeEpoch(_) => {
562 vec![InputObjectKind::SharedMoveObject {
563 id: SUI_SYSTEM_STATE_OBJECT_ID,
564 initial_shared_version: SUI_SYSTEM_STATE_OBJECT_SHARED_VERSION,
565 mutability: SharedObjectMutability::Mutable,
566 }]
567 }
568 Self::AuthenticatorStateCreate => vec![],
569 Self::AuthenticatorStateExpire(expire) => {
570 vec![InputObjectKind::SharedMoveObject {
571 id: SUI_AUTHENTICATOR_STATE_OBJECT_ID,
572 initial_shared_version: expire.authenticator_obj_initial_shared_version(),
573 mutability: SharedObjectMutability::Mutable,
574 }]
575 }
576 Self::RandomnessStateCreate => vec![],
577 Self::DenyListStateCreate => vec![],
578 Self::BridgeStateCreate(_) => vec![],
579 Self::BridgeCommitteeInit(bridge_version) => vec![
580 InputObjectKind::SharedMoveObject {
581 id: SUI_BRIDGE_OBJECT_ID,
582 initial_shared_version: *bridge_version,
583 mutability: SharedObjectMutability::Mutable,
584 },
585 InputObjectKind::SharedMoveObject {
586 id: SUI_SYSTEM_STATE_OBJECT_ID,
587 initial_shared_version: SUI_SYSTEM_STATE_OBJECT_SHARED_VERSION,
588 mutability: SharedObjectMutability::Mutable,
589 },
590 ],
591 Self::StoreExecutionTimeObservations(_) => {
592 vec![InputObjectKind::SharedMoveObject {
593 id: SUI_SYSTEM_STATE_OBJECT_ID,
594 initial_shared_version: SUI_SYSTEM_STATE_OBJECT_SHARED_VERSION,
595 mutability: SharedObjectMutability::Mutable,
596 }]
597 }
598 Self::AccumulatorRootCreate => vec![],
599 Self::CoinRegistryCreate => vec![],
600 Self::DisplayRegistryCreate => vec![],
601 }
602 }
603
604 fn shared_input_objects(&self) -> impl Iterator<Item = SharedInputObject> + '_ {
605 match self {
606 Self::ChangeEpoch(_) => {
607 Either::Left(vec![SharedInputObject::SUI_SYSTEM_OBJ].into_iter())
608 }
609 Self::AuthenticatorStateExpire(expire) => Either::Left(
610 vec![SharedInputObject {
611 id: SUI_AUTHENTICATOR_STATE_OBJECT_ID,
612 initial_shared_version: expire.authenticator_obj_initial_shared_version(),
613 mutability: SharedObjectMutability::Mutable,
614 }]
615 .into_iter(),
616 ),
617 Self::AuthenticatorStateCreate => Either::Right(iter::empty()),
618 Self::RandomnessStateCreate => Either::Right(iter::empty()),
619 Self::DenyListStateCreate => Either::Right(iter::empty()),
620 Self::BridgeStateCreate(_) => Either::Right(iter::empty()),
621 Self::BridgeCommitteeInit(bridge_version) => Either::Left(
622 vec![
623 SharedInputObject {
624 id: SUI_BRIDGE_OBJECT_ID,
625 initial_shared_version: *bridge_version,
626 mutability: SharedObjectMutability::Mutable,
627 },
628 SharedInputObject::SUI_SYSTEM_OBJ,
629 ]
630 .into_iter(),
631 ),
632 Self::StoreExecutionTimeObservations(_) => {
633 Either::Left(vec![SharedInputObject::SUI_SYSTEM_OBJ].into_iter())
634 }
635 Self::AccumulatorRootCreate => Either::Right(iter::empty()),
636 Self::CoinRegistryCreate => Either::Right(iter::empty()),
637 Self::DisplayRegistryCreate => Either::Right(iter::empty()),
638 }
639 }
640
641 fn validity_check(&self, config: &ProtocolConfig) -> UserInputResult {
642 match self {
643 Self::ChangeEpoch(_) => (),
644 Self::AuthenticatorStateCreate | Self::AuthenticatorStateExpire(_) => {
645 if !config.enable_jwk_consensus_updates() {
646 return Err(UserInputError::Unsupported(
647 "authenticator state updates not enabled".to_string(),
648 ));
649 }
650 }
651 Self::RandomnessStateCreate => {
652 if !config.random_beacon() {
653 return Err(UserInputError::Unsupported(
654 "random beacon not enabled".to_string(),
655 ));
656 }
657 }
658 Self::DenyListStateCreate => {
659 if !config.enable_coin_deny_list_v1() {
660 return Err(UserInputError::Unsupported(
661 "coin deny list not enabled".to_string(),
662 ));
663 }
664 }
665 Self::BridgeStateCreate(_) => {
666 if !config.enable_bridge() {
667 return Err(UserInputError::Unsupported(
668 "bridge not enabled".to_string(),
669 ));
670 }
671 }
672 Self::BridgeCommitteeInit(_) => {
673 if !config.enable_bridge() {
674 return Err(UserInputError::Unsupported(
675 "bridge not enabled".to_string(),
676 ));
677 }
678 if !config.should_try_to_finalize_bridge_committee() {
679 return Err(UserInputError::Unsupported(
680 "should not try to finalize committee yet".to_string(),
681 ));
682 }
683 }
684 Self::StoreExecutionTimeObservations(_) => {
685 if !matches!(
686 config.per_object_congestion_control_mode(),
687 PerObjectCongestionControlMode::ExecutionTimeEstimate(_)
688 ) {
689 return Err(UserInputError::Unsupported(
690 "execution time estimation not enabled".to_string(),
691 ));
692 }
693 }
694 Self::AccumulatorRootCreate => {
695 if !config.create_root_accumulator_object() {
696 return Err(UserInputError::Unsupported(
697 "accumulators not enabled".to_string(),
698 ));
699 }
700 }
701 Self::CoinRegistryCreate => {
702 if !config.enable_coin_registry() {
703 return Err(UserInputError::Unsupported(
704 "coin registry not enabled".to_string(),
705 ));
706 }
707 }
708 Self::DisplayRegistryCreate => {
709 if !config.enable_display_registry() {
710 return Err(UserInputError::Unsupported(
711 "display registry not enabled".to_string(),
712 ));
713 }
714 }
715 }
716 Ok(())
717 }
718}
719
720impl CallArg {
721 fn input_objects(&self) -> Vec<InputObjectKind> {
722 match self {
723 CallArg::Pure(_) => vec![],
724 CallArg::Object(ObjectArg::ImmOrOwnedObject(object_ref)) => {
725 vec![InputObjectKind::ImmOrOwnedMoveObject(*object_ref)]
726 }
727 CallArg::Object(ObjectArg::SharedObject {
728 id,
729 initial_shared_version,
730 mutability,
731 }) => vec![InputObjectKind::SharedMoveObject {
732 id: *id,
733 initial_shared_version: *initial_shared_version,
734 mutability: *mutability,
735 }],
736 CallArg::Object(ObjectArg::Receiving(_)) => vec![],
738 CallArg::FundsWithdrawal(_) => vec![],
742 }
743 }
744
745 fn receiving_objects(&self) -> Vec<ObjectRef> {
746 match self {
747 CallArg::Pure(_) => vec![],
748 CallArg::Object(o) => match o {
749 ObjectArg::ImmOrOwnedObject(_) => vec![],
750 ObjectArg::SharedObject { .. } => vec![],
751 ObjectArg::Receiving(obj_ref) => vec![*obj_ref],
752 },
753 CallArg::FundsWithdrawal(_) => vec![],
754 }
755 }
756
757 pub fn validity_check(&self, config: &ProtocolConfig) -> UserInputResult {
758 match self {
759 CallArg::Pure(p) => {
760 fp_ensure!(
761 p.len() < config.max_pure_argument_size() as usize,
762 UserInputError::SizeLimitExceeded {
763 limit: "maximum pure argument size".to_string(),
764 value: config.max_pure_argument_size().to_string()
765 }
766 );
767 }
768 CallArg::Object(o) => match o {
769 ObjectArg::ImmOrOwnedObject(_) => (),
770 ObjectArg::SharedObject { mutability, .. } => match mutability {
771 SharedObjectMutability::Mutable | SharedObjectMutability::Immutable => (),
772 SharedObjectMutability::NonExclusiveWrite => {
773 if !config.enable_non_exclusive_writes() {
774 return Err(UserInputError::Unsupported(
775 "User transactions cannot use SharedObjectMutability::NonExclusiveWrite".to_string(),
776 ));
777 }
778 }
779 },
780
781 ObjectArg::Receiving(_) => {
782 if !config.receiving_objects_supported() {
783 return Err(UserInputError::Unsupported(format!(
784 "receiving objects is not supported at {:?}",
785 config.version
786 )));
787 }
788 }
789 },
790 CallArg::FundsWithdrawal(_) => {}
791 }
792 Ok(())
793 }
794}
795
796impl From<bool> for CallArg {
797 fn from(b: bool) -> Self {
798 CallArg::Pure(bcs::to_bytes(&b).unwrap())
800 }
801}
802
803impl From<u8> for CallArg {
804 fn from(n: u8) -> Self {
805 CallArg::Pure(bcs::to_bytes(&n).unwrap())
807 }
808}
809
810impl From<u16> for CallArg {
811 fn from(n: u16) -> Self {
812 CallArg::Pure(bcs::to_bytes(&n).unwrap())
814 }
815}
816
817impl From<u32> for CallArg {
818 fn from(n: u32) -> Self {
819 CallArg::Pure(bcs::to_bytes(&n).unwrap())
821 }
822}
823
824impl From<u64> for CallArg {
825 fn from(n: u64) -> Self {
826 CallArg::Pure(bcs::to_bytes(&n).unwrap())
828 }
829}
830
831impl From<u128> for CallArg {
832 fn from(n: u128) -> Self {
833 CallArg::Pure(bcs::to_bytes(&n).unwrap())
835 }
836}
837
838impl From<&Vec<u8>> for CallArg {
839 fn from(v: &Vec<u8>) -> Self {
840 CallArg::Pure(bcs::to_bytes(v).unwrap())
842 }
843}
844
845impl From<ObjectRef> for CallArg {
846 fn from(obj: ObjectRef) -> Self {
847 CallArg::Object(ObjectArg::ImmOrOwnedObject(obj))
848 }
849}
850
851impl ObjectArg {
852 pub const SUI_SYSTEM_MUT: Self = Self::SharedObject {
853 id: SUI_SYSTEM_STATE_OBJECT_ID,
854 initial_shared_version: SUI_SYSTEM_STATE_OBJECT_SHARED_VERSION,
855 mutability: SharedObjectMutability::Mutable,
856 };
857
858 pub fn id(&self) -> ObjectID {
859 match self {
860 ObjectArg::Receiving((id, _, _))
861 | ObjectArg::ImmOrOwnedObject((id, _, _))
862 | ObjectArg::SharedObject { id, .. } => *id,
863 }
864 }
865}
866
867fn add_type_input_packages(packages: &mut BTreeSet<ObjectID>, type_argument: &TypeInput) {
869 let mut stack = vec![type_argument];
870 while let Some(cur) = stack.pop() {
871 match cur {
872 TypeInput::Bool
873 | TypeInput::U8
874 | TypeInput::U64
875 | TypeInput::U128
876 | TypeInput::Address
877 | TypeInput::Signer
878 | TypeInput::U16
879 | TypeInput::U32
880 | TypeInput::U256 => (),
881 TypeInput::Vector(inner) => stack.push(inner),
882 TypeInput::Struct(struct_tag) => {
883 packages.insert(struct_tag.address.into());
884 stack.extend(struct_tag.type_params.iter())
885 }
886 }
887 }
888}
889
890#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)]
893pub struct ProgrammableTransaction {
894 pub inputs: Vec<CallArg>,
896 pub commands: Vec<Command>,
899}
900
901impl ProgrammableTransaction {
902 pub fn has_shared_inputs(&self) -> bool {
903 self.inputs
904 .iter()
905 .any(|input| matches!(input, CallArg::Object(ObjectArg::SharedObject { .. })))
906 }
907}
908
909#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)]
911pub enum Command {
912 MoveCall(Box<ProgrammableMoveCall>),
914 TransferObjects(Vec<Argument>, Argument),
919 SplitCoins(Argument, Vec<Argument>),
922 MergeCoins(Argument, Vec<Argument>),
925 Publish(Vec<Vec<u8>>, Vec<ObjectID>),
928 MakeMoveVec(Option<TypeInput>, Vec<Argument>),
932 Upgrade(Vec<Vec<u8>>, Vec<ObjectID>, ObjectID, Argument),
940}
941
942#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy, Serialize, Deserialize)]
944pub enum Argument {
945 GasCoin,
948 Input(u16),
951 Result(u16),
953 NestedResult(u16, u16),
956}
957
958#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)]
961pub struct ProgrammableMoveCall {
962 pub package: ObjectID,
964 pub module: String,
966 pub function: String,
968 pub type_arguments: Vec<TypeInput>,
970 pub arguments: Vec<Argument>,
972}
973
974impl ProgrammableMoveCall {
975 fn input_objects(&self) -> Vec<InputObjectKind> {
976 let ProgrammableMoveCall {
977 package,
978 type_arguments,
979 ..
980 } = self;
981 let mut packages = BTreeSet::from([*package]);
982 for type_argument in type_arguments {
983 add_type_input_packages(&mut packages, type_argument)
984 }
985 packages
986 .into_iter()
987 .map(InputObjectKind::MovePackage)
988 .collect()
989 }
990
991 pub fn validity_check(&self, config: &ProtocolConfig) -> UserInputResult {
992 let is_blocked = BLOCKED_MOVE_FUNCTIONS.contains(&(
993 self.package,
994 self.module.as_str(),
995 self.function.as_str(),
996 ));
997 fp_ensure!(!is_blocked, UserInputError::BlockedMoveFunction);
998 let mut type_arguments_count = 0;
999 for tag in &self.type_arguments {
1000 type_input_validity_check(tag, config, &mut type_arguments_count)?;
1001 }
1002 fp_ensure!(
1003 self.arguments.len() < config.max_arguments() as usize,
1004 UserInputError::SizeLimitExceeded {
1005 limit: "maximum arguments in a move call".to_string(),
1006 value: config.max_arguments().to_string()
1007 }
1008 );
1009 if config.validate_identifier_inputs() {
1010 fp_ensure!(
1011 identifier::is_valid(&self.module),
1012 UserInputError::InvalidIdentifier {
1013 error: self.module.clone()
1014 }
1015 );
1016 fp_ensure!(
1017 identifier::is_valid(&self.function),
1018 UserInputError::InvalidIdentifier {
1019 error: self.module.clone()
1020 }
1021 );
1022 }
1023 Ok(())
1024 }
1025
1026 fn is_input_arg_used(&self, arg: u16) -> bool {
1027 self.arguments
1028 .iter()
1029 .any(|a| matches!(a, Argument::Input(inp) if *inp == arg))
1030 }
1031}
1032
1033impl Command {
1034 pub fn move_call(
1035 package: ObjectID,
1036 module: Identifier,
1037 function: Identifier,
1038 type_arguments: Vec<TypeTag>,
1039 arguments: Vec<Argument>,
1040 ) -> Self {
1041 let module = module.to_string();
1042 let function = function.to_string();
1043 let type_arguments = type_arguments.into_iter().map(TypeInput::from).collect();
1044 Command::MoveCall(Box::new(ProgrammableMoveCall {
1045 package,
1046 module,
1047 function,
1048 type_arguments,
1049 arguments,
1050 }))
1051 }
1052
1053 pub fn make_move_vec(ty: Option<TypeTag>, args: Vec<Argument>) -> Self {
1054 Command::MakeMoveVec(ty.map(TypeInput::from), args)
1055 }
1056
1057 fn input_objects(&self) -> Vec<InputObjectKind> {
1058 match self {
1059 Command::Upgrade(_, deps, package_id, _) => deps
1060 .iter()
1061 .map(|id| InputObjectKind::MovePackage(*id))
1062 .chain(Some(InputObjectKind::MovePackage(*package_id)))
1063 .collect(),
1064 Command::Publish(_, deps) => deps
1065 .iter()
1066 .map(|id| InputObjectKind::MovePackage(*id))
1067 .collect(),
1068 Command::MoveCall(c) => c.input_objects(),
1069 Command::MakeMoveVec(Some(t), _) => {
1070 let mut packages = BTreeSet::new();
1071 add_type_input_packages(&mut packages, t);
1072 packages
1073 .into_iter()
1074 .map(InputObjectKind::MovePackage)
1075 .collect()
1076 }
1077 Command::MakeMoveVec(None, _)
1078 | Command::TransferObjects(_, _)
1079 | Command::SplitCoins(_, _)
1080 | Command::MergeCoins(_, _) => vec![],
1081 }
1082 }
1083
1084 fn non_system_packages_to_be_published(&self) -> Option<&Vec<Vec<u8>>> {
1085 match self {
1086 Command::Upgrade(v, _, _, _) => Some(v),
1087 Command::Publish(v, _) => Some(v),
1088 Command::MoveCall(_)
1089 | Command::TransferObjects(_, _)
1090 | Command::SplitCoins(_, _)
1091 | Command::MergeCoins(_, _)
1092 | Command::MakeMoveVec(_, _) => None,
1093 }
1094 }
1095
1096 fn validity_check(&self, config: &ProtocolConfig) -> UserInputResult {
1097 match self {
1098 Command::MoveCall(call) => call.validity_check(config)?,
1099 Command::TransferObjects(args, _)
1100 | Command::MergeCoins(_, args)
1101 | Command::SplitCoins(_, args) => {
1102 fp_ensure!(!args.is_empty(), UserInputError::EmptyCommandInput);
1103 fp_ensure!(
1104 args.len() < config.max_arguments() as usize,
1105 UserInputError::SizeLimitExceeded {
1106 limit: "maximum arguments in a programmable transaction command"
1107 .to_string(),
1108 value: config.max_arguments().to_string()
1109 }
1110 );
1111 }
1112 Command::MakeMoveVec(ty_opt, args) => {
1113 fp_ensure!(
1115 ty_opt.is_some() || !args.is_empty(),
1116 UserInputError::EmptyCommandInput
1117 );
1118 if let Some(ty) = ty_opt {
1119 let mut type_arguments_count = 0;
1120 type_input_validity_check(ty, config, &mut type_arguments_count)?;
1121 }
1122 fp_ensure!(
1123 args.len() < config.max_arguments() as usize,
1124 UserInputError::SizeLimitExceeded {
1125 limit: "maximum arguments in a programmable transaction command"
1126 .to_string(),
1127 value: config.max_arguments().to_string()
1128 }
1129 );
1130 }
1131 Command::Publish(modules, deps) | Command::Upgrade(modules, deps, _, _) => {
1132 fp_ensure!(!modules.is_empty(), UserInputError::EmptyCommandInput);
1133 fp_ensure!(
1134 modules.len() < config.max_modules_in_publish() as usize,
1135 UserInputError::SizeLimitExceeded {
1136 limit: "maximum modules in a programmable transaction upgrade command"
1137 .to_string(),
1138 value: config.max_modules_in_publish().to_string()
1139 }
1140 );
1141 if let Some(max_package_dependencies) = config.max_package_dependencies_as_option()
1142 {
1143 fp_ensure!(
1144 deps.len() < max_package_dependencies as usize,
1145 UserInputError::SizeLimitExceeded {
1146 limit: "maximum package dependencies".to_string(),
1147 value: max_package_dependencies.to_string()
1148 }
1149 );
1150 };
1151 }
1152 };
1153 Ok(())
1154 }
1155
1156 fn is_input_arg_used(&self, input_arg: u16) -> bool {
1157 match self {
1158 Command::MoveCall(c) => c.is_input_arg_used(input_arg),
1159 Command::TransferObjects(args, arg)
1160 | Command::MergeCoins(arg, args)
1161 | Command::SplitCoins(arg, args) => args
1162 .iter()
1163 .chain(once(arg))
1164 .any(|a| matches!(a, Argument::Input(inp) if *inp == input_arg)),
1165 Command::MakeMoveVec(_, args) => args
1166 .iter()
1167 .any(|a| matches!(a, Argument::Input(inp) if *inp == input_arg)),
1168 Command::Upgrade(_, _, _, arg) => {
1169 matches!(arg, Argument::Input(inp) if *inp == input_arg)
1170 }
1171 Command::Publish(_, _) => false,
1172 }
1173 }
1174}
1175
1176pub fn write_sep<T: Display>(
1177 f: &mut Formatter<'_>,
1178 items: impl IntoIterator<Item = T>,
1179 sep: &str,
1180) -> std::fmt::Result {
1181 let mut xs = items.into_iter();
1182 let Some(x) = xs.next() else {
1183 return Ok(());
1184 };
1185 write!(f, "{x}")?;
1186 for x in xs {
1187 write!(f, "{sep}{x}")?;
1188 }
1189 Ok(())
1190}
1191
1192impl ProgrammableTransaction {
1193 pub fn input_objects(&self) -> UserInputResult<Vec<InputObjectKind>> {
1194 let ProgrammableTransaction { inputs, commands } = self;
1195 let input_arg_objects = inputs
1196 .iter()
1197 .flat_map(|arg| arg.input_objects())
1198 .collect::<Vec<_>>();
1199 let mut used = HashSet::new();
1201 if !input_arg_objects.iter().all(|o| used.insert(o.object_id())) {
1202 return Err(UserInputError::DuplicateObjectRefInput);
1203 }
1204 let command_input_objects: BTreeSet<InputObjectKind> = commands
1206 .iter()
1207 .flat_map(|command| command.input_objects())
1208 .collect();
1209 Ok(input_arg_objects
1210 .into_iter()
1211 .chain(command_input_objects)
1212 .collect())
1213 }
1214
1215 fn receiving_objects(&self) -> Vec<ObjectRef> {
1216 let ProgrammableTransaction { inputs, .. } = self;
1217 inputs
1218 .iter()
1219 .flat_map(|arg| arg.receiving_objects())
1220 .collect()
1221 }
1222
1223 fn validity_check(&self, config: &ProtocolConfig) -> UserInputResult {
1224 let ProgrammableTransaction { inputs, commands } = self;
1225 fp_ensure!(
1226 commands.len() < config.max_programmable_tx_commands() as usize,
1227 UserInputError::SizeLimitExceeded {
1228 limit: "maximum commands in a programmable transaction".to_string(),
1229 value: config.max_programmable_tx_commands().to_string()
1230 }
1231 );
1232 let total_inputs = self.input_objects()?.len() + self.receiving_objects().len();
1233 fp_ensure!(
1234 total_inputs <= config.max_input_objects() as usize,
1235 UserInputError::SizeLimitExceeded {
1236 limit: "maximum input + receiving objects in a transaction".to_string(),
1237 value: config.max_input_objects().to_string()
1238 }
1239 );
1240 for input in inputs {
1241 input.validity_check(config)?
1242 }
1243 if let Some(max_publish_commands) = config.max_publish_or_upgrade_per_ptb_as_option() {
1244 let publish_count = commands
1245 .iter()
1246 .filter(|c| matches!(c, Command::Publish(_, _) | Command::Upgrade(_, _, _, _)))
1247 .count() as u64;
1248 fp_ensure!(
1249 publish_count <= max_publish_commands,
1250 UserInputError::MaxPublishCountExceeded {
1251 max_publish_commands,
1252 publish_count,
1253 }
1254 );
1255 }
1256 for command in commands {
1257 command.validity_check(config)?;
1258 }
1259
1260 if let Some(random_index) = inputs.iter().position(|obj| {
1263 matches!(
1264 obj,
1265 CallArg::Object(ObjectArg::SharedObject { id, .. }) if *id == SUI_RANDOMNESS_STATE_OBJECT_ID
1266 )
1267 }) {
1268 fp_ensure!(
1269 config.random_beacon(),
1270 UserInputError::Unsupported(
1271 "randomness is not enabled on this network".to_string(),
1272 )
1273 );
1274 let mut used_random_object = false;
1275 let random_index = random_index.try_into().unwrap();
1276 for command in commands {
1277 if !used_random_object {
1278 used_random_object = command.is_input_arg_used(random_index);
1279 } else {
1280 fp_ensure!(
1281 matches!(
1282 command,
1283 Command::TransferObjects(_, _) | Command::MergeCoins(_, _)
1284 ),
1285 UserInputError::PostRandomCommandRestrictions
1286 );
1287 }
1288 }
1289 }
1290
1291 Ok(())
1292 }
1293
1294 pub fn shared_input_objects(&self) -> impl Iterator<Item = SharedInputObject> + '_ {
1295 self.inputs.iter().filter_map(|arg| match arg {
1296 CallArg::Pure(_)
1297 | CallArg::Object(ObjectArg::Receiving(_))
1298 | CallArg::Object(ObjectArg::ImmOrOwnedObject(_))
1299 | CallArg::FundsWithdrawal(_) => None,
1300 CallArg::Object(ObjectArg::SharedObject {
1301 id,
1302 initial_shared_version,
1303 mutability,
1304 }) => Some(SharedInputObject {
1305 id: *id,
1306 initial_shared_version: *initial_shared_version,
1307 mutability: *mutability,
1308 }),
1309 })
1310 }
1311
1312 fn move_calls(&self) -> Vec<(&ObjectID, &str, &str)> {
1313 self.commands
1314 .iter()
1315 .filter_map(|command| match command {
1316 Command::MoveCall(m) => Some((&m.package, m.module.as_str(), m.function.as_str())),
1317 _ => None,
1318 })
1319 .collect()
1320 }
1321
1322 pub fn non_system_packages_to_be_published(&self) -> impl Iterator<Item = &Vec<Vec<u8>>> + '_ {
1323 self.commands
1324 .iter()
1325 .filter_map(|q| q.non_system_packages_to_be_published())
1326 }
1327}
1328
1329impl Display for Argument {
1330 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
1331 match self {
1332 Argument::GasCoin => write!(f, "GasCoin"),
1333 Argument::Input(i) => write!(f, "Input({i})"),
1334 Argument::Result(i) => write!(f, "Result({i})"),
1335 Argument::NestedResult(i, j) => write!(f, "NestedResult({i},{j})"),
1336 }
1337 }
1338}
1339
1340impl Display for ProgrammableMoveCall {
1341 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
1342 let ProgrammableMoveCall {
1343 package,
1344 module,
1345 function,
1346 type_arguments,
1347 arguments,
1348 } = self;
1349 write!(f, "{package}::{module}::{function}")?;
1350 if !type_arguments.is_empty() {
1351 write!(f, "<")?;
1352 write_sep(f, type_arguments, ",")?;
1353 write!(f, ">")?;
1354 }
1355 write!(f, "(")?;
1356 write_sep(f, arguments, ",")?;
1357 write!(f, ")")
1358 }
1359}
1360
1361impl Display for Command {
1362 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
1363 match self {
1364 Command::MoveCall(p) => {
1365 write!(f, "MoveCall({p})")
1366 }
1367 Command::MakeMoveVec(ty_opt, elems) => {
1368 write!(f, "MakeMoveVec(")?;
1369 if let Some(ty) = ty_opt {
1370 write!(f, "Some{ty}")?;
1371 } else {
1372 write!(f, "None")?;
1373 }
1374 write!(f, ",[")?;
1375 write_sep(f, elems, ",")?;
1376 write!(f, "])")
1377 }
1378 Command::TransferObjects(objs, addr) => {
1379 write!(f, "TransferObjects([")?;
1380 write_sep(f, objs, ",")?;
1381 write!(f, "],{addr})")
1382 }
1383 Command::SplitCoins(coin, amounts) => {
1384 write!(f, "SplitCoins({coin}")?;
1385 write_sep(f, amounts, ",")?;
1386 write!(f, ")")
1387 }
1388 Command::MergeCoins(target, coins) => {
1389 write!(f, "MergeCoins({target},")?;
1390 write_sep(f, coins, ",")?;
1391 write!(f, ")")
1392 }
1393 Command::Publish(_bytes, deps) => {
1394 write!(f, "Publish(_,")?;
1395 write_sep(f, deps, ",")?;
1396 write!(f, ")")
1397 }
1398 Command::Upgrade(_bytes, deps, current_package_id, ticket) => {
1399 write!(f, "Upgrade(_,")?;
1400 write_sep(f, deps, ",")?;
1401 write!(f, ", {current_package_id}")?;
1402 write!(f, ", {ticket}")?;
1403 write!(f, ")")
1404 }
1405 }
1406 }
1407}
1408
1409impl Display for ProgrammableTransaction {
1410 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
1411 let ProgrammableTransaction { inputs, commands } = self;
1412 writeln!(f, "Inputs: {inputs:?}")?;
1413 writeln!(f, "Commands: [")?;
1414 for c in commands {
1415 writeln!(f, " {c},")?;
1416 }
1417 writeln!(f, "]")
1418 }
1419}
1420
1421#[derive(Debug, PartialEq, Eq)]
1422pub struct SharedInputObject {
1423 pub id: ObjectID,
1424 pub initial_shared_version: SequenceNumber,
1425 pub mutability: SharedObjectMutability,
1426}
1427
1428impl SharedInputObject {
1429 pub const SUI_SYSTEM_OBJ: Self = Self {
1430 id: SUI_SYSTEM_STATE_OBJECT_ID,
1431 initial_shared_version: SUI_SYSTEM_STATE_OBJECT_SHARED_VERSION,
1432 mutability: SharedObjectMutability::Mutable,
1433 };
1434
1435 pub fn id(&self) -> ObjectID {
1436 self.id
1437 }
1438
1439 pub fn id_and_version(&self) -> (ObjectID, SequenceNumber) {
1440 (self.id, self.initial_shared_version)
1441 }
1442
1443 pub fn into_id_and_version(self) -> (ObjectID, SequenceNumber) {
1444 (self.id, self.initial_shared_version)
1445 }
1446
1447 pub fn is_accessed_exclusively(&self) -> bool {
1448 self.mutability.is_exclusive()
1449 }
1450}
1451
1452impl TransactionKind {
1453 pub fn programmable(pt: ProgrammableTransaction) -> Self {
1456 TransactionKind::ProgrammableTransaction(pt)
1457 }
1458
1459 pub fn is_system_tx(&self) -> bool {
1460 match self {
1462 TransactionKind::ChangeEpoch(_)
1463 | TransactionKind::Genesis(_)
1464 | TransactionKind::ConsensusCommitPrologue(_)
1465 | TransactionKind::ConsensusCommitPrologueV2(_)
1466 | TransactionKind::ConsensusCommitPrologueV3(_)
1467 | TransactionKind::ConsensusCommitPrologueV4(_)
1468 | TransactionKind::AuthenticatorStateUpdate(_)
1469 | TransactionKind::RandomnessStateUpdate(_)
1470 | TransactionKind::EndOfEpochTransaction(_)
1471 | TransactionKind::ProgrammableSystemTransaction(_) => true,
1472 TransactionKind::ProgrammableTransaction(_) => false,
1473 }
1474 }
1475
1476 pub fn is_end_of_epoch_tx(&self) -> bool {
1477 matches!(
1478 self,
1479 TransactionKind::EndOfEpochTransaction(_) | TransactionKind::ChangeEpoch(_)
1480 )
1481 }
1482
1483 pub fn get_advance_epoch_tx_gas_summary(&self) -> Option<(u64, u64)> {
1487 let e = match self {
1488 Self::ChangeEpoch(e) => e,
1489 Self::EndOfEpochTransaction(txns) => {
1490 if let EndOfEpochTransactionKind::ChangeEpoch(e) =
1491 txns.last().expect("at least one end-of-epoch txn required")
1492 {
1493 e
1494 } else {
1495 panic!("final end-of-epoch txn must be ChangeEpoch")
1496 }
1497 }
1498 _ => return None,
1499 };
1500
1501 Some((e.computation_charge + e.storage_charge, e.storage_rebate))
1502 }
1503
1504 pub fn shared_input_objects(&self) -> impl Iterator<Item = SharedInputObject> + '_ {
1507 match &self {
1508 Self::ChangeEpoch(_) => {
1509 Either::Left(Either::Left(iter::once(SharedInputObject::SUI_SYSTEM_OBJ)))
1510 }
1511
1512 Self::ConsensusCommitPrologue(_)
1513 | Self::ConsensusCommitPrologueV2(_)
1514 | Self::ConsensusCommitPrologueV3(_)
1515 | Self::ConsensusCommitPrologueV4(_) => {
1516 Either::Left(Either::Left(iter::once(SharedInputObject {
1517 id: SUI_CLOCK_OBJECT_ID,
1518 initial_shared_version: SUI_CLOCK_OBJECT_SHARED_VERSION,
1519 mutability: SharedObjectMutability::Mutable,
1520 })))
1521 }
1522 Self::AuthenticatorStateUpdate(update) => {
1523 Either::Left(Either::Left(iter::once(SharedInputObject {
1524 id: SUI_AUTHENTICATOR_STATE_OBJECT_ID,
1525 initial_shared_version: update.authenticator_obj_initial_shared_version,
1526 mutability: SharedObjectMutability::Mutable,
1527 })))
1528 }
1529 Self::RandomnessStateUpdate(update) => {
1530 Either::Left(Either::Left(iter::once(SharedInputObject {
1531 id: SUI_RANDOMNESS_STATE_OBJECT_ID,
1532 initial_shared_version: update.randomness_obj_initial_shared_version,
1533 mutability: SharedObjectMutability::Mutable,
1534 })))
1535 }
1536 Self::EndOfEpochTransaction(txns) => Either::Left(Either::Right(
1537 txns.iter().flat_map(|txn| txn.shared_input_objects()),
1538 )),
1539 Self::ProgrammableTransaction(pt) | Self::ProgrammableSystemTransaction(pt) => {
1540 Either::Right(Either::Left(pt.shared_input_objects()))
1541 }
1542 Self::Genesis(_) => Either::Right(Either::Right(iter::empty())),
1543 }
1544 }
1545
1546 fn move_calls(&self) -> Vec<(&ObjectID, &str, &str)> {
1547 match &self {
1548 Self::ProgrammableTransaction(pt) => pt.move_calls(),
1549 _ => vec![],
1550 }
1551 }
1552
1553 pub fn receiving_objects(&self) -> Vec<ObjectRef> {
1554 match &self {
1555 TransactionKind::ChangeEpoch(_)
1556 | TransactionKind::Genesis(_)
1557 | TransactionKind::ConsensusCommitPrologue(_)
1558 | TransactionKind::ConsensusCommitPrologueV2(_)
1559 | TransactionKind::ConsensusCommitPrologueV3(_)
1560 | TransactionKind::ConsensusCommitPrologueV4(_)
1561 | TransactionKind::AuthenticatorStateUpdate(_)
1562 | TransactionKind::RandomnessStateUpdate(_)
1563 | TransactionKind::EndOfEpochTransaction(_)
1564 | TransactionKind::ProgrammableSystemTransaction(_) => vec![],
1565 TransactionKind::ProgrammableTransaction(pt) => pt.receiving_objects(),
1566 }
1567 }
1568
1569 pub fn input_objects(&self) -> UserInputResult<Vec<InputObjectKind>> {
1574 let input_objects = match &self {
1575 Self::ChangeEpoch(_) => {
1576 vec![InputObjectKind::SharedMoveObject {
1577 id: SUI_SYSTEM_STATE_OBJECT_ID,
1578 initial_shared_version: SUI_SYSTEM_STATE_OBJECT_SHARED_VERSION,
1579 mutability: SharedObjectMutability::Mutable,
1580 }]
1581 }
1582 Self::Genesis(_) => {
1583 vec![]
1584 }
1585 Self::ConsensusCommitPrologue(_)
1586 | Self::ConsensusCommitPrologueV2(_)
1587 | Self::ConsensusCommitPrologueV3(_)
1588 | Self::ConsensusCommitPrologueV4(_) => {
1589 vec![InputObjectKind::SharedMoveObject {
1590 id: SUI_CLOCK_OBJECT_ID,
1591 initial_shared_version: SUI_CLOCK_OBJECT_SHARED_VERSION,
1592 mutability: SharedObjectMutability::Mutable,
1593 }]
1594 }
1595 Self::AuthenticatorStateUpdate(update) => {
1596 vec![InputObjectKind::SharedMoveObject {
1597 id: SUI_AUTHENTICATOR_STATE_OBJECT_ID,
1598 initial_shared_version: update.authenticator_obj_initial_shared_version(),
1599 mutability: SharedObjectMutability::Mutable,
1600 }]
1601 }
1602 Self::RandomnessStateUpdate(update) => {
1603 vec![InputObjectKind::SharedMoveObject {
1604 id: SUI_RANDOMNESS_STATE_OBJECT_ID,
1605 initial_shared_version: update.randomness_obj_initial_shared_version(),
1606 mutability: SharedObjectMutability::Mutable,
1607 }]
1608 }
1609 Self::EndOfEpochTransaction(txns) => {
1610 let before_dedup: Vec<_> =
1613 txns.iter().flat_map(|txn| txn.input_objects()).collect();
1614 let mut has_seen = HashSet::new();
1615 let mut after_dedup = vec![];
1616 for obj in before_dedup {
1617 if has_seen.insert(obj) {
1618 after_dedup.push(obj);
1619 }
1620 }
1621 after_dedup
1622 }
1623 Self::ProgrammableTransaction(p) | Self::ProgrammableSystemTransaction(p) => {
1624 return p.input_objects();
1625 }
1626 };
1627 let mut used = HashSet::new();
1635 if !input_objects.iter().all(|o| used.insert(o.object_id())) {
1636 return Err(UserInputError::DuplicateObjectRefInput);
1637 }
1638 Ok(input_objects)
1639 }
1640
1641 pub fn validity_check(&self, config: &ProtocolConfig) -> UserInputResult {
1642 match self {
1643 TransactionKind::ProgrammableTransaction(p) => p.validity_check(config)?,
1644 TransactionKind::ChangeEpoch(_)
1647 | TransactionKind::Genesis(_)
1648 | TransactionKind::ConsensusCommitPrologue(_) => (),
1649 TransactionKind::ConsensusCommitPrologueV2(_) => {
1650 if !config.include_consensus_digest_in_prologue() {
1651 return Err(UserInputError::Unsupported(
1652 "ConsensusCommitPrologueV2 is not supported".to_string(),
1653 ));
1654 }
1655 }
1656 TransactionKind::ConsensusCommitPrologueV3(_) => {
1657 if !config.record_consensus_determined_version_assignments_in_prologue() {
1658 return Err(UserInputError::Unsupported(
1659 "ConsensusCommitPrologueV3 is not supported".to_string(),
1660 ));
1661 }
1662 }
1663 TransactionKind::ConsensusCommitPrologueV4(_) => {
1664 if !config.record_additional_state_digest_in_prologue() {
1665 return Err(UserInputError::Unsupported(
1666 "ConsensusCommitPrologueV4 is not supported".to_string(),
1667 ));
1668 }
1669 }
1670 TransactionKind::EndOfEpochTransaction(txns) => {
1671 if !config.end_of_epoch_transaction_supported() {
1672 return Err(UserInputError::Unsupported(
1673 "EndOfEpochTransaction is not supported".to_string(),
1674 ));
1675 }
1676
1677 for tx in txns {
1678 tx.validity_check(config)?;
1679 }
1680 }
1681
1682 TransactionKind::AuthenticatorStateUpdate(_) => {
1683 if !config.enable_jwk_consensus_updates() {
1684 return Err(UserInputError::Unsupported(
1685 "authenticator state updates not enabled".to_string(),
1686 ));
1687 }
1688 }
1689 TransactionKind::RandomnessStateUpdate(_) => {
1690 if !config.random_beacon() {
1691 return Err(UserInputError::Unsupported(
1692 "randomness state updates not enabled".to_string(),
1693 ));
1694 }
1695 }
1696 TransactionKind::ProgrammableSystemTransaction(_) => {
1697 if !config.enable_accumulators() {
1698 return Err(UserInputError::Unsupported(
1699 "accumulators not enabled".to_string(),
1700 ));
1701 }
1702 }
1703 };
1704 Ok(())
1705 }
1706
1707 pub fn num_commands(&self) -> usize {
1709 match self {
1710 TransactionKind::ProgrammableTransaction(pt) => pt.commands.len(),
1711 _ => 0,
1712 }
1713 }
1714
1715 pub fn iter_commands(&self) -> impl Iterator<Item = &Command> {
1716 match self {
1717 TransactionKind::ProgrammableTransaction(pt) => pt.commands.iter(),
1718 _ => [].iter(),
1719 }
1720 }
1721
1722 pub fn tx_count(&self) -> usize {
1724 match self {
1725 TransactionKind::ProgrammableTransaction(pt) => pt.commands.len(),
1726 _ => 1,
1727 }
1728 }
1729
1730 pub fn name(&self) -> &'static str {
1731 match self {
1732 Self::ChangeEpoch(_) => "ChangeEpoch",
1733 Self::Genesis(_) => "Genesis",
1734 Self::ConsensusCommitPrologue(_) => "ConsensusCommitPrologue",
1735 Self::ConsensusCommitPrologueV2(_) => "ConsensusCommitPrologueV2",
1736 Self::ConsensusCommitPrologueV3(_) => "ConsensusCommitPrologueV3",
1737 Self::ConsensusCommitPrologueV4(_) => "ConsensusCommitPrologueV4",
1738 Self::ProgrammableTransaction(_) => "ProgrammableTransaction",
1739 Self::ProgrammableSystemTransaction(_) => "ProgrammableSystemTransaction",
1740 Self::AuthenticatorStateUpdate(_) => "AuthenticatorStateUpdate",
1741 Self::RandomnessStateUpdate(_) => "RandomnessStateUpdate",
1742 Self::EndOfEpochTransaction(_) => "EndOfEpochTransaction",
1743 }
1744 }
1745}
1746
1747impl Display for TransactionKind {
1748 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
1749 let mut writer = String::new();
1750 match &self {
1751 Self::ChangeEpoch(e) => {
1752 writeln!(writer, "Transaction Kind : Epoch Change")?;
1753 writeln!(writer, "New epoch ID : {}", e.epoch)?;
1754 writeln!(writer, "Storage gas reward : {}", e.storage_charge)?;
1755 writeln!(writer, "Computation gas reward : {}", e.computation_charge)?;
1756 writeln!(writer, "Storage rebate : {}", e.storage_rebate)?;
1757 writeln!(writer, "Timestamp : {}", e.epoch_start_timestamp_ms)?;
1758 }
1759 Self::Genesis(_) => {
1760 writeln!(writer, "Transaction Kind : Genesis")?;
1761 }
1762 Self::ConsensusCommitPrologue(p) => {
1763 writeln!(writer, "Transaction Kind : Consensus Commit Prologue")?;
1764 writeln!(writer, "Timestamp : {}", p.commit_timestamp_ms)?;
1765 }
1766 Self::ConsensusCommitPrologueV2(p) => {
1767 writeln!(writer, "Transaction Kind : Consensus Commit Prologue V2")?;
1768 writeln!(writer, "Timestamp : {}", p.commit_timestamp_ms)?;
1769 writeln!(writer, "Consensus Digest: {}", p.consensus_commit_digest)?;
1770 }
1771 Self::ConsensusCommitPrologueV3(p) => {
1772 writeln!(writer, "Transaction Kind : Consensus Commit Prologue V3")?;
1773 writeln!(writer, "Timestamp : {}", p.commit_timestamp_ms)?;
1774 writeln!(writer, "Consensus Digest: {}", p.consensus_commit_digest)?;
1775 writeln!(
1776 writer,
1777 "Consensus determined version assignment: {:?}",
1778 p.consensus_determined_version_assignments
1779 )?;
1780 }
1781 Self::ConsensusCommitPrologueV4(p) => {
1782 writeln!(writer, "Transaction Kind : Consensus Commit Prologue V4")?;
1783 writeln!(writer, "Timestamp : {}", p.commit_timestamp_ms)?;
1784 writeln!(writer, "Consensus Digest: {}", p.consensus_commit_digest)?;
1785 writeln!(
1786 writer,
1787 "Consensus determined version assignment: {:?}",
1788 p.consensus_determined_version_assignments
1789 )?;
1790 writeln!(
1791 writer,
1792 "Additional State Digest: {}",
1793 p.additional_state_digest
1794 )?;
1795 }
1796 Self::ProgrammableTransaction(p) => {
1797 writeln!(writer, "Transaction Kind : Programmable")?;
1798 write!(writer, "{p}")?;
1799 }
1800 Self::ProgrammableSystemTransaction(p) => {
1801 writeln!(writer, "Transaction Kind : Programmable System")?;
1802 write!(writer, "{p}")?;
1803 }
1804 Self::AuthenticatorStateUpdate(_) => {
1805 writeln!(writer, "Transaction Kind : Authenticator State Update")?;
1806 }
1807 Self::RandomnessStateUpdate(_) => {
1808 writeln!(writer, "Transaction Kind : Randomness State Update")?;
1809 }
1810 Self::EndOfEpochTransaction(_) => {
1811 writeln!(writer, "Transaction Kind : End of Epoch Transaction")?;
1812 }
1813 }
1814 write!(f, "{}", writer)
1815 }
1816}
1817
1818#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)]
1819pub struct GasData {
1820 pub payment: Vec<ObjectRef>,
1821 pub owner: SuiAddress,
1822 pub price: u64,
1823 pub budget: u64,
1824}
1825
1826pub fn is_gas_paid_from_address_balance(
1827 gas_data: &GasData,
1828 transaction_kind: &TransactionKind,
1829) -> bool {
1830 gas_data.payment.is_empty()
1831 && matches!(
1832 transaction_kind,
1833 TransactionKind::ProgrammableTransaction(_)
1834 )
1835}
1836
1837#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy, Serialize, Deserialize)]
1838pub enum TransactionExpiration {
1839 None,
1841 Epoch(EpochId),
1844 ValidDuring {
1854 min_epoch: Option<EpochId>,
1856 max_epoch: Option<EpochId>,
1858 min_timestamp_seconds: Option<u64>,
1860 max_timestamp_seconds: Option<u64>,
1862 chain: ChainIdentifier,
1864 nonce: u32,
1866 },
1867}
1868
1869#[enum_dispatch(TransactionDataAPI)]
1870#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)]
1871pub enum TransactionData {
1872 V1(TransactionDataV1),
1873 }
1876
1877#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)]
1878pub struct TransactionDataV1 {
1879 pub kind: TransactionKind,
1880 pub sender: SuiAddress,
1881 pub gas_data: GasData,
1882 pub expiration: TransactionExpiration,
1883}
1884
1885impl TransactionData {
1886 fn new_system_transaction(kind: TransactionKind) -> Self {
1887 assert!(kind.is_system_tx());
1889 let sender = SuiAddress::default();
1890 TransactionData::V1(TransactionDataV1 {
1891 kind,
1892 sender,
1893 gas_data: GasData {
1894 price: GAS_PRICE_FOR_SYSTEM_TX,
1895 owner: sender,
1896 payment: vec![(ObjectID::ZERO, SequenceNumber::default(), ObjectDigest::MIN)],
1897 budget: 0,
1898 },
1899 expiration: TransactionExpiration::None,
1900 })
1901 }
1902
1903 pub fn new(
1904 kind: TransactionKind,
1905 sender: SuiAddress,
1906 gas_payment: ObjectRef,
1907 gas_budget: u64,
1908 gas_price: u64,
1909 ) -> Self {
1910 TransactionData::V1(TransactionDataV1 {
1911 kind,
1912 sender,
1913 gas_data: GasData {
1914 price: gas_price,
1915 owner: sender,
1916 payment: vec![gas_payment],
1917 budget: gas_budget,
1918 },
1919 expiration: TransactionExpiration::None,
1920 })
1921 }
1922
1923 pub fn new_with_gas_coins(
1924 kind: TransactionKind,
1925 sender: SuiAddress,
1926 gas_payment: Vec<ObjectRef>,
1927 gas_budget: u64,
1928 gas_price: u64,
1929 ) -> Self {
1930 Self::new_with_gas_coins_allow_sponsor(
1931 kind,
1932 sender,
1933 gas_payment,
1934 gas_budget,
1935 gas_price,
1936 sender,
1937 )
1938 }
1939
1940 pub fn new_with_gas_coins_allow_sponsor(
1941 kind: TransactionKind,
1942 sender: SuiAddress,
1943 gas_payment: Vec<ObjectRef>,
1944 gas_budget: u64,
1945 gas_price: u64,
1946 gas_sponsor: SuiAddress,
1947 ) -> Self {
1948 TransactionData::V1(TransactionDataV1 {
1949 kind,
1950 sender,
1951 gas_data: GasData {
1952 price: gas_price,
1953 owner: gas_sponsor,
1954 payment: gas_payment,
1955 budget: gas_budget,
1956 },
1957 expiration: TransactionExpiration::None,
1958 })
1959 }
1960
1961 pub fn new_with_gas_data(kind: TransactionKind, sender: SuiAddress, gas_data: GasData) -> Self {
1962 TransactionData::V1(TransactionDataV1 {
1963 kind,
1964 sender,
1965 gas_data,
1966 expiration: TransactionExpiration::None,
1967 })
1968 }
1969
1970 pub fn new_move_call(
1971 sender: SuiAddress,
1972 package: ObjectID,
1973 module: Identifier,
1974 function: Identifier,
1975 type_arguments: Vec<TypeTag>,
1976 gas_payment: ObjectRef,
1977 arguments: Vec<CallArg>,
1978 gas_budget: u64,
1979 gas_price: u64,
1980 ) -> anyhow::Result<Self> {
1981 Self::new_move_call_with_gas_coins(
1982 sender,
1983 package,
1984 module,
1985 function,
1986 type_arguments,
1987 vec![gas_payment],
1988 arguments,
1989 gas_budget,
1990 gas_price,
1991 )
1992 }
1993
1994 pub fn new_move_call_with_gas_coins(
1995 sender: SuiAddress,
1996 package: ObjectID,
1997 module: Identifier,
1998 function: Identifier,
1999 type_arguments: Vec<TypeTag>,
2000 gas_payment: Vec<ObjectRef>,
2001 arguments: Vec<CallArg>,
2002 gas_budget: u64,
2003 gas_price: u64,
2004 ) -> anyhow::Result<Self> {
2005 let pt = {
2006 let mut builder = ProgrammableTransactionBuilder::new();
2007 builder.move_call(package, module, function, type_arguments, arguments)?;
2008 builder.finish()
2009 };
2010 Ok(Self::new_programmable(
2011 sender,
2012 gas_payment,
2013 pt,
2014 gas_budget,
2015 gas_price,
2016 ))
2017 }
2018
2019 pub fn new_transfer(
2020 recipient: SuiAddress,
2021 full_object_ref: FullObjectRef,
2022 sender: SuiAddress,
2023 gas_payment: ObjectRef,
2024 gas_budget: u64,
2025 gas_price: u64,
2026 ) -> Self {
2027 let pt = {
2028 let mut builder = ProgrammableTransactionBuilder::new();
2029 builder.transfer_object(recipient, full_object_ref).unwrap();
2030 builder.finish()
2031 };
2032 Self::new_programmable(sender, vec![gas_payment], pt, gas_budget, gas_price)
2033 }
2034
2035 pub fn new_transfer_sui(
2036 recipient: SuiAddress,
2037 sender: SuiAddress,
2038 amount: Option<u64>,
2039 gas_payment: ObjectRef,
2040 gas_budget: u64,
2041 gas_price: u64,
2042 ) -> Self {
2043 Self::new_transfer_sui_allow_sponsor(
2044 recipient,
2045 sender,
2046 amount,
2047 gas_payment,
2048 gas_budget,
2049 gas_price,
2050 sender,
2051 )
2052 }
2053
2054 pub fn new_transfer_sui_allow_sponsor(
2055 recipient: SuiAddress,
2056 sender: SuiAddress,
2057 amount: Option<u64>,
2058 gas_payment: ObjectRef,
2059 gas_budget: u64,
2060 gas_price: u64,
2061 gas_sponsor: SuiAddress,
2062 ) -> Self {
2063 let pt = {
2064 let mut builder = ProgrammableTransactionBuilder::new();
2065 builder.transfer_sui(recipient, amount);
2066 builder.finish()
2067 };
2068 Self::new_programmable_allow_sponsor(
2069 sender,
2070 vec![gas_payment],
2071 pt,
2072 gas_budget,
2073 gas_price,
2074 gas_sponsor,
2075 )
2076 }
2077
2078 pub fn new_pay(
2079 sender: SuiAddress,
2080 coins: Vec<ObjectRef>,
2081 recipients: Vec<SuiAddress>,
2082 amounts: Vec<u64>,
2083 gas_payment: ObjectRef,
2084 gas_budget: u64,
2085 gas_price: u64,
2086 ) -> anyhow::Result<Self> {
2087 let pt = {
2088 let mut builder = ProgrammableTransactionBuilder::new();
2089 builder.pay(coins, recipients, amounts)?;
2090 builder.finish()
2091 };
2092 Ok(Self::new_programmable(
2093 sender,
2094 vec![gas_payment],
2095 pt,
2096 gas_budget,
2097 gas_price,
2098 ))
2099 }
2100
2101 pub fn new_pay_sui(
2102 sender: SuiAddress,
2103 mut coins: Vec<ObjectRef>,
2104 recipients: Vec<SuiAddress>,
2105 amounts: Vec<u64>,
2106 gas_payment: ObjectRef,
2107 gas_budget: u64,
2108 gas_price: u64,
2109 ) -> anyhow::Result<Self> {
2110 coins.insert(0, gas_payment);
2111 let pt = {
2112 let mut builder = ProgrammableTransactionBuilder::new();
2113 builder.pay_sui(recipients, amounts)?;
2114 builder.finish()
2115 };
2116 Ok(Self::new_programmable(
2117 sender, coins, pt, gas_budget, gas_price,
2118 ))
2119 }
2120
2121 pub fn new_pay_all_sui(
2122 sender: SuiAddress,
2123 mut coins: Vec<ObjectRef>,
2124 recipient: SuiAddress,
2125 gas_payment: ObjectRef,
2126 gas_budget: u64,
2127 gas_price: u64,
2128 ) -> Self {
2129 coins.insert(0, gas_payment);
2130 let pt = {
2131 let mut builder = ProgrammableTransactionBuilder::new();
2132 builder.pay_all_sui(recipient);
2133 builder.finish()
2134 };
2135 Self::new_programmable(sender, coins, pt, gas_budget, gas_price)
2136 }
2137
2138 pub fn new_split_coin(
2139 sender: SuiAddress,
2140 coin: ObjectRef,
2141 amounts: Vec<u64>,
2142 gas_payment: ObjectRef,
2143 gas_budget: u64,
2144 gas_price: u64,
2145 ) -> Self {
2146 let pt = {
2147 let mut builder = ProgrammableTransactionBuilder::new();
2148 builder.split_coin(sender, coin, amounts);
2149 builder.finish()
2150 };
2151 Self::new_programmable(sender, vec![gas_payment], pt, gas_budget, gas_price)
2152 }
2153
2154 pub fn new_module(
2155 sender: SuiAddress,
2156 gas_payment: ObjectRef,
2157 modules: Vec<Vec<u8>>,
2158 dep_ids: Vec<ObjectID>,
2159 gas_budget: u64,
2160 gas_price: u64,
2161 ) -> Self {
2162 let pt = {
2163 let mut builder = ProgrammableTransactionBuilder::new();
2164 let upgrade_cap = builder.publish_upgradeable(modules, dep_ids);
2165 builder.transfer_arg(sender, upgrade_cap);
2166 builder.finish()
2167 };
2168 Self::new_programmable(sender, vec![gas_payment], pt, gas_budget, gas_price)
2169 }
2170
2171 pub fn new_upgrade(
2172 sender: SuiAddress,
2173 gas_payment: ObjectRef,
2174 package_id: ObjectID,
2175 modules: Vec<Vec<u8>>,
2176 dep_ids: Vec<ObjectID>,
2177 (upgrade_capability, capability_owner): (ObjectRef, Owner),
2178 upgrade_policy: u8,
2179 digest: Vec<u8>,
2180 gas_budget: u64,
2181 gas_price: u64,
2182 ) -> anyhow::Result<Self> {
2183 let pt = {
2184 let mut builder = ProgrammableTransactionBuilder::new();
2185 let capability_arg = match capability_owner {
2186 Owner::AddressOwner(_) => ObjectArg::ImmOrOwnedObject(upgrade_capability),
2187 Owner::Shared {
2188 initial_shared_version,
2189 }
2190 | Owner::ConsensusAddressOwner {
2191 start_version: initial_shared_version,
2192 ..
2193 } => ObjectArg::SharedObject {
2194 id: upgrade_capability.0,
2195 initial_shared_version,
2196 mutability: SharedObjectMutability::Mutable,
2197 },
2198 Owner::Immutable => {
2199 return Err(anyhow::anyhow!(
2200 "Upgrade capability is stored immutably and cannot be used for upgrades"
2201 ));
2202 }
2203 Owner::ObjectOwner(_) => {
2206 return Err(anyhow::anyhow!("Upgrade capability controlled by object"));
2207 }
2208 };
2209 builder.obj(capability_arg).unwrap();
2210 let upgrade_arg = builder.pure(upgrade_policy).unwrap();
2211 let digest_arg = builder.pure(digest).unwrap();
2212 let upgrade_ticket = builder.programmable_move_call(
2213 SUI_FRAMEWORK_PACKAGE_ID,
2214 ident_str!("package").to_owned(),
2215 ident_str!("authorize_upgrade").to_owned(),
2216 vec![],
2217 vec![Argument::Input(0), upgrade_arg, digest_arg],
2218 );
2219 let upgrade_receipt = builder.upgrade(package_id, upgrade_ticket, dep_ids, modules);
2220
2221 builder.programmable_move_call(
2222 SUI_FRAMEWORK_PACKAGE_ID,
2223 ident_str!("package").to_owned(),
2224 ident_str!("commit_upgrade").to_owned(),
2225 vec![],
2226 vec![Argument::Input(0), upgrade_receipt],
2227 );
2228
2229 builder.finish()
2230 };
2231 Ok(Self::new_programmable(
2232 sender,
2233 vec![gas_payment],
2234 pt,
2235 gas_budget,
2236 gas_price,
2237 ))
2238 }
2239
2240 pub fn new_programmable(
2241 sender: SuiAddress,
2242 gas_payment: Vec<ObjectRef>,
2243 pt: ProgrammableTransaction,
2244 gas_budget: u64,
2245 gas_price: u64,
2246 ) -> Self {
2247 Self::new_programmable_allow_sponsor(sender, gas_payment, pt, gas_budget, gas_price, sender)
2248 }
2249
2250 pub fn new_programmable_allow_sponsor(
2251 sender: SuiAddress,
2252 gas_payment: Vec<ObjectRef>,
2253 pt: ProgrammableTransaction,
2254 gas_budget: u64,
2255 gas_price: u64,
2256 sponsor: SuiAddress,
2257 ) -> Self {
2258 let kind = TransactionKind::ProgrammableTransaction(pt);
2259 Self::new_with_gas_coins_allow_sponsor(
2260 kind,
2261 sender,
2262 gas_payment,
2263 gas_budget,
2264 gas_price,
2265 sponsor,
2266 )
2267 }
2268
2269 pub fn message_version(&self) -> u64 {
2270 match self {
2271 TransactionData::V1(_) => 1,
2272 }
2273 }
2274
2275 pub fn execution_parts(&self) -> (TransactionKind, SuiAddress, GasData) {
2276 (self.kind().clone(), self.sender(), self.gas_data().clone())
2277 }
2278
2279 pub fn uses_randomness(&self) -> bool {
2280 self.kind()
2281 .shared_input_objects()
2282 .any(|obj| obj.id() == SUI_RANDOMNESS_STATE_OBJECT_ID)
2283 }
2284
2285 pub fn digest(&self) -> TransactionDigest {
2286 TransactionDigest::new(default_hash(self))
2287 }
2288}
2289
2290#[enum_dispatch]
2291pub trait TransactionDataAPI {
2292 fn sender(&self) -> SuiAddress;
2293
2294 fn kind(&self) -> &TransactionKind;
2297
2298 fn kind_mut(&mut self) -> &mut TransactionKind;
2300
2301 fn into_kind(self) -> TransactionKind;
2303
2304 fn signers(&self) -> NonEmpty<SuiAddress>;
2306
2307 fn gas_data(&self) -> &GasData;
2308
2309 fn gas_owner(&self) -> SuiAddress;
2310
2311 fn gas(&self) -> &[ObjectRef];
2312
2313 fn gas_price(&self) -> u64;
2314
2315 fn gas_budget(&self) -> u64;
2316
2317 fn expiration(&self) -> &TransactionExpiration;
2318
2319 fn move_calls(&self) -> Vec<(&ObjectID, &str, &str)>;
2320
2321 fn input_objects(&self) -> UserInputResult<Vec<InputObjectKind>>;
2322
2323 fn shared_input_objects(&self) -> Vec<SharedInputObject>;
2324
2325 fn receiving_objects(&self) -> Vec<ObjectRef>;
2326
2327 fn fastpath_dependency_objects(
2331 &self,
2332 ) -> UserInputResult<(Vec<ObjectRef>, Vec<ObjectID>, Vec<ObjectRef>)>;
2333
2334 fn process_funds_withdrawals(&self) -> UserInputResult<BTreeMap<AccumulatorObjId, u64>>;
2339
2340 fn has_funds_withdrawals(&self) -> bool;
2342
2343 fn get_funds_withdrawals(&self) -> Vec<FundsWithdrawalArg>;
2345
2346 fn validity_check(&self, config: &ProtocolConfig) -> UserInputResult;
2347
2348 fn validity_check_no_gas_check(&self, config: &ProtocolConfig) -> UserInputResult;
2349
2350 fn check_sponsorship(&self) -> UserInputResult;
2352
2353 fn is_system_tx(&self) -> bool;
2354 fn is_genesis_tx(&self) -> bool;
2355
2356 fn is_end_of_epoch_tx(&self) -> bool;
2359
2360 fn is_consensus_commit_prologue(&self) -> bool;
2361
2362 fn is_sponsored_tx(&self) -> bool;
2364
2365 fn is_gas_paid_from_address_balance(&self) -> bool;
2366
2367 fn sender_mut_for_testing(&mut self) -> &mut SuiAddress;
2368
2369 fn gas_data_mut(&mut self) -> &mut GasData;
2370
2371 fn expiration_mut_for_testing(&mut self) -> &mut TransactionExpiration;
2373}
2374
2375impl TransactionDataAPI for TransactionDataV1 {
2376 fn sender(&self) -> SuiAddress {
2377 self.sender
2378 }
2379
2380 fn kind(&self) -> &TransactionKind {
2381 &self.kind
2382 }
2383
2384 fn kind_mut(&mut self) -> &mut TransactionKind {
2385 &mut self.kind
2386 }
2387
2388 fn into_kind(self) -> TransactionKind {
2389 self.kind
2390 }
2391
2392 fn signers(&self) -> NonEmpty<SuiAddress> {
2394 let mut signers = nonempty![self.sender];
2395 if self.gas_owner() != self.sender {
2396 signers.push(self.gas_owner());
2397 }
2398 signers
2399 }
2400
2401 fn gas_data(&self) -> &GasData {
2402 &self.gas_data
2403 }
2404
2405 fn gas_owner(&self) -> SuiAddress {
2406 self.gas_data.owner
2407 }
2408
2409 fn gas(&self) -> &[ObjectRef] {
2410 &self.gas_data.payment
2411 }
2412
2413 fn gas_price(&self) -> u64 {
2414 self.gas_data.price
2415 }
2416
2417 fn gas_budget(&self) -> u64 {
2418 self.gas_data.budget
2419 }
2420
2421 fn expiration(&self) -> &TransactionExpiration {
2422 &self.expiration
2423 }
2424
2425 fn move_calls(&self) -> Vec<(&ObjectID, &str, &str)> {
2426 self.kind.move_calls()
2427 }
2428
2429 fn input_objects(&self) -> UserInputResult<Vec<InputObjectKind>> {
2430 let mut inputs = self.kind.input_objects()?;
2431
2432 if !self.kind.is_system_tx() {
2433 inputs.extend(
2434 self.gas()
2435 .iter()
2436 .map(|obj_ref| InputObjectKind::ImmOrOwnedMoveObject(*obj_ref)),
2437 );
2438 }
2439 Ok(inputs)
2440 }
2441
2442 fn shared_input_objects(&self) -> Vec<SharedInputObject> {
2443 self.kind.shared_input_objects().collect()
2444 }
2445
2446 fn receiving_objects(&self) -> Vec<ObjectRef> {
2447 self.kind.receiving_objects()
2448 }
2449
2450 fn fastpath_dependency_objects(
2451 &self,
2452 ) -> UserInputResult<(Vec<ObjectRef>, Vec<ObjectID>, Vec<ObjectRef>)> {
2453 let mut move_objects = vec![];
2454 let mut packages = vec![];
2455 let mut receiving_objects = vec![];
2456 self.input_objects()?.iter().for_each(|o| match o {
2457 InputObjectKind::ImmOrOwnedMoveObject(object_ref) => {
2458 move_objects.push(*object_ref);
2459 }
2460 InputObjectKind::MovePackage(package_id) => {
2461 packages.push(*package_id);
2462 }
2463 InputObjectKind::SharedMoveObject { .. } => {}
2464 });
2465 self.receiving_objects().iter().for_each(|object_ref| {
2466 receiving_objects.push(*object_ref);
2467 });
2468 Ok((move_objects, packages, receiving_objects))
2469 }
2470
2471 fn process_funds_withdrawals(&self) -> UserInputResult<BTreeMap<AccumulatorObjId, u64>> {
2472 let mut withdraws = self.get_funds_withdrawals();
2473
2474 for withdraw in &withdraws {
2475 if matches!(withdraw.withdraw_from, WithdrawFrom::Sponsor) {
2476 return Err(UserInputError::InvalidWithdrawReservation {
2477 error: "Explicit sponsor withdrawals are not yet supported".to_string(),
2478 });
2479 }
2480 }
2481
2482 if self.is_gas_paid_from_address_balance() {
2483 let gas_withdraw = if self.sender() != self.gas_owner() {
2484 FundsWithdrawalArg::balance_from_sponsor(
2485 self.gas_data().budget,
2486 TypeInput::from(GAS::type_tag()),
2487 )
2488 } else {
2489 FundsWithdrawalArg::balance_from_sender(
2490 self.gas_data().budget,
2491 TypeInput::from(GAS::type_tag()),
2492 )
2493 };
2494 withdraws.push(gas_withdraw);
2495 }
2496
2497 let max_withdraws = 10;
2499 if withdraws.len() > max_withdraws {
2500 return Err(UserInputError::InvalidWithdrawReservation {
2501 error: format!(
2502 "Maximum number of balance withdraw reservations is {max_withdraws}"
2503 ),
2504 });
2505 }
2506
2507 let mut withdraw_map = BTreeMap::new();
2509 for withdraw in withdraws {
2510 let reserved_amount = match &withdraw.reservation {
2511 Reservation::MaxAmountU64(amount) => *amount,
2512 Reservation::EntireBalance => {
2513 return Err(UserInputError::InvalidWithdrawReservation {
2514 error: "Reserving the entire balance is not supported".to_string(),
2515 });
2516 }
2517 };
2518 if reserved_amount == 0 {
2521 return Err(UserInputError::InvalidWithdrawReservation {
2522 error: "Balance withdraw reservation amount must be non-zero".to_string(),
2523 });
2524 }
2525 let account_address = match withdraw.withdraw_from {
2526 WithdrawFrom::Sender => self.sender(),
2527 WithdrawFrom::Sponsor => self.gas_owner(),
2528 };
2529 let account_id =
2530 AccumulatorValue::get_field_id(account_address, &withdraw.type_arg.to_type_tag()?)
2531 .map_err(|e| UserInputError::InvalidWithdrawReservation {
2532 error: e.to_string(),
2533 })?;
2534 let entry = withdraw_map.entry(account_id);
2535 match entry {
2536 Entry::Vacant(vacant) => {
2537 vacant.insert(reserved_amount);
2538 }
2539 Entry::Occupied(mut occupied) => {
2540 let current_amount = *occupied.get();
2541 let new_amount = current_amount.checked_add(reserved_amount).ok_or(
2542 UserInputError::InvalidWithdrawReservation {
2543 error: "Balance withdraw reservation overflow".to_string(),
2544 },
2545 )?;
2546 occupied.insert(new_amount);
2547 }
2548 }
2549 }
2550
2551 Ok(withdraw_map)
2552 }
2553
2554 fn has_funds_withdrawals(&self) -> bool {
2555 if self.is_gas_paid_from_address_balance() {
2556 return true;
2557 }
2558 if let TransactionKind::ProgrammableTransaction(pt) = &self.kind {
2559 for input in &pt.inputs {
2560 if matches!(input, CallArg::FundsWithdrawal(_)) {
2561 return true;
2562 }
2563 }
2564 }
2565 false
2566 }
2567
2568 fn get_funds_withdrawals(&self) -> Vec<FundsWithdrawalArg> {
2569 let TransactionKind::ProgrammableTransaction(pt) = &self.kind else {
2570 return vec![];
2571 };
2572 pt.inputs
2573 .iter()
2574 .filter_map(|input| {
2575 if let CallArg::FundsWithdrawal(withdraw) = input {
2576 Some(withdraw.clone())
2577 } else {
2578 None
2579 }
2580 })
2581 .collect()
2582 }
2583
2584 fn validity_check(&self, config: &ProtocolConfig) -> UserInputResult {
2585 if let TransactionExpiration::ValidDuring {
2586 min_epoch,
2587 max_epoch,
2588 min_timestamp_seconds,
2589 max_timestamp_seconds,
2590 ..
2591 } = self.expiration()
2592 {
2593 if min_timestamp_seconds.is_some() || max_timestamp_seconds.is_some() {
2594 return Err(UserInputError::Unsupported(
2595 "Timestamp-based transaction expiration is not yet supported".to_string(),
2596 ));
2597 }
2598
2599 match (min_epoch, max_epoch) {
2602 (Some(min), Some(max)) => {
2603 if min != max {
2604 return Err(UserInputError::Unsupported(
2605 "Multi-epoch transaction expiration is not yet supported. min_epoch must equal max_epoch".to_string()
2606 ));
2607 }
2608 }
2609 _ => {
2610 return Err(UserInputError::Unsupported(
2611 "Both min_epoch and max_epoch must be specified and equal".to_string(),
2612 ));
2613 }
2614 }
2615 }
2616
2617 if config.enable_accumulators()
2618 && config.enable_address_balance_gas_payments()
2619 && self.is_gas_paid_from_address_balance()
2620 {
2621 match self.expiration() {
2622 TransactionExpiration::None => {
2623 return Err(UserInputError::MissingTransactionExpiration);
2624 }
2625 TransactionExpiration::Epoch(_) => {
2626 return Err(UserInputError::InvalidExpiration {
2627 error: "Address balance gas payments require ValidDuring expiration"
2628 .to_string(),
2629 });
2630 }
2631 TransactionExpiration::ValidDuring { .. } => {}
2632 }
2633 } else {
2634 fp_ensure!(!self.gas().is_empty(), UserInputError::MissingGasPayment);
2635 }
2636
2637 let gas_len = self.gas().len();
2638 let max_gas_objects = config.max_gas_payment_objects() as usize;
2639
2640 let within_limit = if config.correct_gas_payment_limit_check() {
2641 gas_len <= max_gas_objects
2642 } else {
2643 gas_len < max_gas_objects
2644 };
2645
2646 fp_ensure!(
2647 within_limit,
2648 UserInputError::SizeLimitExceeded {
2649 limit: "maximum number of gas payment objects".to_string(),
2650 value: config.max_gas_payment_objects().to_string()
2651 }
2652 );
2653
2654 self.validity_check_no_gas_check(config)
2655 }
2656
2657 fn validity_check_no_gas_check(&self, config: &ProtocolConfig) -> UserInputResult {
2660 self.kind().validity_check(config)?;
2661 self.check_sponsorship()
2662 }
2663
2664 fn is_sponsored_tx(&self) -> bool {
2666 self.gas_owner() != self.sender
2667 }
2668
2669 fn is_gas_paid_from_address_balance(&self) -> bool {
2670 is_gas_paid_from_address_balance(&self.gas_data, &self.kind)
2671 }
2672
2673 fn check_sponsorship(&self) -> UserInputResult {
2675 if self.gas_owner() == self.sender() {
2677 return Ok(());
2678 }
2679 if matches!(&self.kind, TransactionKind::ProgrammableTransaction(_)) {
2680 return Ok(());
2681 }
2682 Err(UserInputError::UnsupportedSponsoredTransactionKind)
2683 }
2684
2685 fn is_end_of_epoch_tx(&self) -> bool {
2686 matches!(
2687 self.kind,
2688 TransactionKind::ChangeEpoch(_) | TransactionKind::EndOfEpochTransaction(_)
2689 )
2690 }
2691
2692 fn is_consensus_commit_prologue(&self) -> bool {
2693 match &self.kind {
2694 TransactionKind::ConsensusCommitPrologue(_)
2695 | TransactionKind::ConsensusCommitPrologueV2(_)
2696 | TransactionKind::ConsensusCommitPrologueV3(_)
2697 | TransactionKind::ConsensusCommitPrologueV4(_) => true,
2698
2699 TransactionKind::ProgrammableTransaction(_)
2700 | TransactionKind::ProgrammableSystemTransaction(_)
2701 | TransactionKind::ChangeEpoch(_)
2702 | TransactionKind::Genesis(_)
2703 | TransactionKind::AuthenticatorStateUpdate(_)
2704 | TransactionKind::EndOfEpochTransaction(_)
2705 | TransactionKind::RandomnessStateUpdate(_) => false,
2706 }
2707 }
2708
2709 fn is_system_tx(&self) -> bool {
2710 self.kind.is_system_tx()
2711 }
2712
2713 fn is_genesis_tx(&self) -> bool {
2714 matches!(self.kind, TransactionKind::Genesis(_))
2715 }
2716
2717 fn sender_mut_for_testing(&mut self) -> &mut SuiAddress {
2718 &mut self.sender
2719 }
2720
2721 fn gas_data_mut(&mut self) -> &mut GasData {
2722 &mut self.gas_data
2723 }
2724
2725 fn expiration_mut_for_testing(&mut self) -> &mut TransactionExpiration {
2726 &mut self.expiration
2727 }
2728}
2729
2730impl TransactionDataV1 {}
2731
2732pub struct TxValidityCheckContext<'a> {
2733 pub config: &'a ProtocolConfig,
2734 pub epoch: EpochId,
2735 pub accumulator_object_init_shared_version: Option<SequenceNumber>,
2736 pub chain_identifier: ChainIdentifier,
2737}
2738
2739#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
2740pub struct SenderSignedData(SizeOneVec<SenderSignedTransaction>);
2741
2742#[derive(Debug, Clone, PartialEq, Eq, Hash)]
2743pub struct SenderSignedTransaction {
2744 pub intent_message: IntentMessage<TransactionData>,
2745 pub tx_signatures: Vec<GenericSignature>,
2749}
2750
2751impl Serialize for SenderSignedTransaction {
2752 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
2753 where
2754 S: serde::Serializer,
2755 {
2756 #[derive(Serialize)]
2757 #[serde(rename = "SenderSignedTransaction")]
2758 struct SignedTxn<'a> {
2759 intent_message: &'a IntentMessage<TransactionData>,
2760 tx_signatures: &'a Vec<GenericSignature>,
2761 }
2762
2763 if self.intent_message().intent != Intent::sui_transaction() {
2764 return Err(serde::ser::Error::custom("invalid Intent for Transaction"));
2765 }
2766
2767 let txn = SignedTxn {
2768 intent_message: self.intent_message(),
2769 tx_signatures: &self.tx_signatures,
2770 };
2771 txn.serialize(serializer)
2772 }
2773}
2774
2775impl<'de> Deserialize<'de> for SenderSignedTransaction {
2776 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
2777 where
2778 D: serde::Deserializer<'de>,
2779 {
2780 #[derive(Deserialize)]
2781 #[serde(rename = "SenderSignedTransaction")]
2782 struct SignedTxn {
2783 intent_message: IntentMessage<TransactionData>,
2784 tx_signatures: Vec<GenericSignature>,
2785 }
2786
2787 let SignedTxn {
2788 intent_message,
2789 tx_signatures,
2790 } = Deserialize::deserialize(deserializer)?;
2791
2792 if intent_message.intent != Intent::sui_transaction() {
2793 return Err(serde::de::Error::custom("invalid Intent for Transaction"));
2794 }
2795
2796 Ok(Self {
2797 intent_message,
2798 tx_signatures,
2799 })
2800 }
2801}
2802
2803impl SenderSignedTransaction {
2804 pub(crate) fn get_signer_sig_mapping(
2805 &self,
2806 verify_legacy_zklogin_address: bool,
2807 ) -> SuiResult<BTreeMap<SuiAddress, &GenericSignature>> {
2808 let mut mapping = BTreeMap::new();
2809 for sig in &self.tx_signatures {
2810 if verify_legacy_zklogin_address {
2811 if let GenericSignature::ZkLoginAuthenticator(z) = sig {
2813 mapping.insert(SuiAddress::try_from_padded(&z.inputs)?, sig);
2814 };
2815 }
2816 let address = sig.try_into()?;
2817 mapping.insert(address, sig);
2818 }
2819 Ok(mapping)
2820 }
2821
2822 pub fn intent_message(&self) -> &IntentMessage<TransactionData> {
2823 &self.intent_message
2824 }
2825}
2826
2827impl SenderSignedData {
2828 pub fn new(tx_data: TransactionData, tx_signatures: Vec<GenericSignature>) -> Self {
2829 Self(SizeOneVec::new(SenderSignedTransaction {
2830 intent_message: IntentMessage::new(Intent::sui_transaction(), tx_data),
2831 tx_signatures,
2832 }))
2833 }
2834
2835 pub fn new_from_sender_signature(tx_data: TransactionData, tx_signature: Signature) -> Self {
2836 Self(SizeOneVec::new(SenderSignedTransaction {
2837 intent_message: IntentMessage::new(Intent::sui_transaction(), tx_data),
2838 tx_signatures: vec![tx_signature.into()],
2839 }))
2840 }
2841
2842 pub fn inner(&self) -> &SenderSignedTransaction {
2843 self.0.element()
2844 }
2845
2846 pub fn into_inner(self) -> SenderSignedTransaction {
2847 self.0.into_inner()
2848 }
2849
2850 pub fn inner_mut(&mut self) -> &mut SenderSignedTransaction {
2851 self.0.element_mut()
2852 }
2853
2854 pub fn add_signature(&mut self, new_signature: Signature) {
2857 self.inner_mut().tx_signatures.push(new_signature.into());
2858 }
2859
2860 pub(crate) fn get_signer_sig_mapping(
2861 &self,
2862 verify_legacy_zklogin_address: bool,
2863 ) -> SuiResult<BTreeMap<SuiAddress, &GenericSignature>> {
2864 self.inner()
2865 .get_signer_sig_mapping(verify_legacy_zklogin_address)
2866 }
2867
2868 pub fn transaction_data(&self) -> &TransactionData {
2869 &self.intent_message().value
2870 }
2871
2872 pub fn intent_message(&self) -> &IntentMessage<TransactionData> {
2873 self.inner().intent_message()
2874 }
2875
2876 pub fn tx_signatures(&self) -> &[GenericSignature] {
2877 &self.inner().tx_signatures
2878 }
2879
2880 pub fn has_zklogin_sig(&self) -> bool {
2881 self.tx_signatures().iter().any(|sig| sig.is_zklogin())
2882 }
2883
2884 pub fn has_upgraded_multisig(&self) -> bool {
2885 self.tx_signatures()
2886 .iter()
2887 .any(|sig| sig.is_upgraded_multisig())
2888 }
2889
2890 #[cfg(test)]
2891 pub fn intent_message_mut_for_testing(&mut self) -> &mut IntentMessage<TransactionData> {
2892 &mut self.inner_mut().intent_message
2893 }
2894
2895 pub fn tx_signatures_mut_for_testing(&mut self) -> &mut Vec<GenericSignature> {
2897 &mut self.inner_mut().tx_signatures
2898 }
2899
2900 pub fn full_message_digest(&self) -> SenderSignedDataDigest {
2901 let mut digest = DefaultHash::default();
2902 bcs::serialize_into(&mut digest, self).expect("serialization should not fail");
2903 let hash = digest.finalize();
2904 SenderSignedDataDigest::new(hash.into())
2905 }
2906
2907 pub fn serialized_size(&self) -> SuiResult<usize> {
2908 bcs::serialized_size(self).map_err(|e| {
2909 SuiErrorKind::TransactionSerializationError {
2910 error: e.to_string(),
2911 }
2912 .into()
2913 })
2914 }
2915
2916 fn check_user_signature_protocol_compatibility(&self, config: &ProtocolConfig) -> SuiResult {
2917 for sig in &self.inner().tx_signatures {
2918 match sig {
2919 GenericSignature::MultiSig(_) => {
2920 if !config.supports_upgraded_multisig() {
2921 return Err(SuiErrorKind::UserInputError {
2922 error: UserInputError::Unsupported(
2923 "upgraded multisig format not enabled on this network".to_string(),
2924 ),
2925 }
2926 .into());
2927 }
2928 }
2929 GenericSignature::ZkLoginAuthenticator(_) => {
2930 if !config.zklogin_auth() {
2931 return Err(SuiErrorKind::UserInputError {
2932 error: UserInputError::Unsupported(
2933 "zklogin is not enabled on this network".to_string(),
2934 ),
2935 }
2936 .into());
2937 }
2938 }
2939 GenericSignature::PasskeyAuthenticator(_) => {
2940 if !config.passkey_auth() {
2941 return Err(SuiErrorKind::UserInputError {
2942 error: UserInputError::Unsupported(
2943 "passkey is not enabled on this network".to_string(),
2944 ),
2945 }
2946 .into());
2947 }
2948 }
2949 GenericSignature::Signature(_) | GenericSignature::MultiSigLegacy(_) => (),
2950 }
2951 }
2952
2953 Ok(())
2954 }
2955
2956 pub fn validity_check(&self, context: &TxValidityCheckContext<'_>) -> Result<usize, SuiError> {
2959 self.check_user_signature_protocol_compatibility(context.config)?;
2961
2962 let tx_data = &self.transaction_data();
2967 fp_ensure!(
2968 !tx_data.is_system_tx(),
2969 SuiErrorKind::UserInputError {
2970 error: UserInputError::Unsupported(
2971 "SenderSignedData must not contain system transaction".to_string()
2972 )
2973 }
2974 .into()
2975 );
2976
2977 match tx_data.expiration() {
2979 TransactionExpiration::None => {
2980 }
2982 TransactionExpiration::Epoch(exp_epoch) => {
2983 if *exp_epoch < context.epoch {
2984 return Err(SuiErrorKind::TransactionExpired.into());
2985 }
2986 }
2987 TransactionExpiration::ValidDuring {
2988 min_epoch,
2989 max_epoch,
2990 chain,
2991 ..
2992 } => {
2993 if *chain != context.chain_identifier {
2994 return Err(SuiErrorKind::UserInputError {
2995 error: UserInputError::InvalidChainId {
2996 provided: format!("{:?}", chain),
2997 expected: format!("{:?}", context.chain_identifier),
2998 },
2999 }
3000 .into());
3001 }
3002
3003 if let Some(min) = min_epoch
3004 && context.epoch < *min
3005 {
3006 return Err(SuiErrorKind::TransactionExpired.into());
3007 }
3008 if let Some(max) = max_epoch
3009 && context.epoch > *max
3010 {
3011 return Err(SuiErrorKind::TransactionExpired.into());
3012 }
3013 }
3014 }
3015
3016 if tx_data.has_funds_withdrawals() {
3017 fp_ensure!(
3018 !tx_data.gas().is_empty() || context.config.enable_address_balance_gas_payments(),
3019 SuiErrorKind::UserInputError {
3020 error: UserInputError::MissingGasPayment
3021 }
3022 .into()
3023 );
3024 fp_ensure!(
3025 context.config.enable_accumulators()
3026 && context.accumulator_object_init_shared_version.is_some(),
3027 SuiErrorKind::UserInputError {
3028 error: UserInputError::Unsupported(
3029 "Address balance withdraw is not enabled".to_string()
3030 )
3031 }
3032 .into()
3033 );
3034 tx_data.process_funds_withdrawals()?;
3035 }
3036
3037 let tx_size = self.serialized_size()?;
3039 let max_tx_size_bytes = context.config.max_tx_size_bytes();
3040 fp_ensure!(
3041 tx_size as u64 <= max_tx_size_bytes,
3042 SuiErrorKind::UserInputError {
3043 error: UserInputError::SizeLimitExceeded {
3044 limit: format!(
3045 "serialized transaction size exceeded maximum of {max_tx_size_bytes}"
3046 ),
3047 value: tx_size.to_string(),
3048 }
3049 }
3050 .into()
3051 );
3052
3053 tx_data
3054 .validity_check(context.config)
3055 .map_err(Into::<SuiError>::into)?;
3056
3057 Ok(tx_size)
3058 }
3059}
3060
3061impl Message for SenderSignedData {
3062 type DigestType = TransactionDigest;
3063 const SCOPE: IntentScope = IntentScope::SenderSignedTransaction;
3064
3065 fn digest(&self) -> Self::DigestType {
3067 self.intent_message().value.digest()
3068 }
3069}
3070
3071impl<S> Envelope<SenderSignedData, S> {
3072 pub fn sender_address(&self) -> SuiAddress {
3073 self.data().intent_message().value.sender()
3074 }
3075
3076 pub fn gas_owner(&self) -> SuiAddress {
3077 self.data().intent_message().value.gas_owner()
3078 }
3079
3080 pub fn gas(&self) -> &[ObjectRef] {
3081 self.data().intent_message().value.gas()
3082 }
3083
3084 pub fn is_consensus_tx(&self) -> bool {
3085 self.transaction_data().has_funds_withdrawals()
3086 || self.shared_input_objects().next().is_some()
3087 }
3088
3089 pub fn shared_input_objects(&self) -> impl Iterator<Item = SharedInputObject> + '_ {
3090 self.data()
3091 .inner()
3092 .intent_message
3093 .value
3094 .shared_input_objects()
3095 .into_iter()
3096 }
3097
3098 pub fn key(&self) -> TransactionKey {
3100 match &self.data().intent_message().value.kind() {
3101 TransactionKind::RandomnessStateUpdate(rsu) => {
3102 TransactionKey::RandomnessRound(rsu.epoch, rsu.randomness_round)
3103 }
3104 _ => TransactionKey::Digest(*self.digest()),
3105 }
3106 }
3107
3108 pub fn non_digest_key(&self) -> Option<TransactionKey> {
3113 match &self.data().intent_message().value.kind() {
3114 TransactionKind::RandomnessStateUpdate(rsu) => Some(TransactionKey::RandomnessRound(
3115 rsu.epoch,
3116 rsu.randomness_round,
3117 )),
3118 _ => None,
3119 }
3120 }
3121
3122 pub fn is_system_tx(&self) -> bool {
3123 self.data().intent_message().value.is_system_tx()
3124 }
3125
3126 pub fn is_sponsored_tx(&self) -> bool {
3127 self.data().intent_message().value.is_sponsored_tx()
3128 }
3129}
3130
3131impl Transaction {
3132 pub fn from_data_and_signer(
3133 data: TransactionData,
3134 signers: Vec<&dyn Signer<Signature>>,
3135 ) -> Self {
3136 let signatures = {
3137 let intent_msg = IntentMessage::new(Intent::sui_transaction(), &data);
3138 signers
3139 .into_iter()
3140 .map(|s| Signature::new_secure(&intent_msg, s))
3141 .collect()
3142 };
3143 Self::from_data(data, signatures)
3144 }
3145
3146 pub fn from_data(data: TransactionData, signatures: Vec<Signature>) -> Self {
3148 Self::from_generic_sig_data(data, signatures.into_iter().map(|s| s.into()).collect())
3149 }
3150
3151 pub fn signature_from_signer(
3152 data: TransactionData,
3153 intent: Intent,
3154 signer: &dyn Signer<Signature>,
3155 ) -> Signature {
3156 let intent_msg = IntentMessage::new(intent, data);
3157 Signature::new_secure(&intent_msg, signer)
3158 }
3159
3160 pub fn from_generic_sig_data(data: TransactionData, signatures: Vec<GenericSignature>) -> Self {
3161 Self::new(SenderSignedData::new(data, signatures))
3162 }
3163
3164 pub fn to_tx_bytes_and_signatures(&self) -> (Base64, Vec<Base64>) {
3167 (
3168 Base64::from_bytes(&bcs::to_bytes(&self.data().intent_message().value).unwrap()),
3169 self.data()
3170 .inner()
3171 .tx_signatures
3172 .iter()
3173 .map(|s| Base64::from_bytes(s.as_ref()))
3174 .collect(),
3175 )
3176 }
3177}
3178
3179impl VerifiedTransaction {
3180 pub fn new_change_epoch(
3181 next_epoch: EpochId,
3182 protocol_version: ProtocolVersion,
3183 storage_charge: u64,
3184 computation_charge: u64,
3185 storage_rebate: u64,
3186 non_refundable_storage_fee: u64,
3187 epoch_start_timestamp_ms: u64,
3188 system_packages: Vec<(SequenceNumber, Vec<Vec<u8>>, Vec<ObjectID>)>,
3189 ) -> Self {
3190 ChangeEpoch {
3191 epoch: next_epoch,
3192 protocol_version,
3193 storage_charge,
3194 computation_charge,
3195 storage_rebate,
3196 non_refundable_storage_fee,
3197 epoch_start_timestamp_ms,
3198 system_packages,
3199 }
3200 .pipe(TransactionKind::ChangeEpoch)
3201 .pipe(Self::new_system_transaction)
3202 }
3203
3204 pub fn new_genesis_transaction(objects: Vec<GenesisObject>) -> Self {
3205 GenesisTransaction { objects }
3206 .pipe(TransactionKind::Genesis)
3207 .pipe(Self::new_system_transaction)
3208 }
3209
3210 pub fn new_consensus_commit_prologue(
3211 epoch: u64,
3212 round: u64,
3213 commit_timestamp_ms: CheckpointTimestamp,
3214 ) -> Self {
3215 ConsensusCommitPrologue {
3216 epoch,
3217 round,
3218 commit_timestamp_ms,
3219 }
3220 .pipe(TransactionKind::ConsensusCommitPrologue)
3221 .pipe(Self::new_system_transaction)
3222 }
3223
3224 pub fn new_consensus_commit_prologue_v2(
3225 epoch: u64,
3226 round: u64,
3227 commit_timestamp_ms: CheckpointTimestamp,
3228 consensus_commit_digest: ConsensusCommitDigest,
3229 ) -> Self {
3230 ConsensusCommitPrologueV2 {
3231 epoch,
3232 round,
3233 commit_timestamp_ms,
3234 consensus_commit_digest,
3235 }
3236 .pipe(TransactionKind::ConsensusCommitPrologueV2)
3237 .pipe(Self::new_system_transaction)
3238 }
3239
3240 pub fn new_consensus_commit_prologue_v3(
3241 epoch: u64,
3242 round: u64,
3243 commit_timestamp_ms: CheckpointTimestamp,
3244 consensus_commit_digest: ConsensusCommitDigest,
3245 consensus_determined_version_assignments: ConsensusDeterminedVersionAssignments,
3246 ) -> Self {
3247 ConsensusCommitPrologueV3 {
3248 epoch,
3249 round,
3250 sub_dag_index: None,
3252 commit_timestamp_ms,
3253 consensus_commit_digest,
3254 consensus_determined_version_assignments,
3255 }
3256 .pipe(TransactionKind::ConsensusCommitPrologueV3)
3257 .pipe(Self::new_system_transaction)
3258 }
3259
3260 pub fn new_consensus_commit_prologue_v4(
3261 epoch: u64,
3262 round: u64,
3263 commit_timestamp_ms: CheckpointTimestamp,
3264 consensus_commit_digest: ConsensusCommitDigest,
3265 consensus_determined_version_assignments: ConsensusDeterminedVersionAssignments,
3266 additional_state_digest: AdditionalConsensusStateDigest,
3267 ) -> Self {
3268 ConsensusCommitPrologueV4 {
3269 epoch,
3270 round,
3271 sub_dag_index: None,
3273 commit_timestamp_ms,
3274 consensus_commit_digest,
3275 consensus_determined_version_assignments,
3276 additional_state_digest,
3277 }
3278 .pipe(TransactionKind::ConsensusCommitPrologueV4)
3279 .pipe(Self::new_system_transaction)
3280 }
3281
3282 pub fn new_authenticator_state_update(
3283 epoch: u64,
3284 round: u64,
3285 new_active_jwks: Vec<ActiveJwk>,
3286 authenticator_obj_initial_shared_version: SequenceNumber,
3287 ) -> Self {
3288 AuthenticatorStateUpdate {
3289 epoch,
3290 round,
3291 new_active_jwks,
3292 authenticator_obj_initial_shared_version,
3293 }
3294 .pipe(TransactionKind::AuthenticatorStateUpdate)
3295 .pipe(Self::new_system_transaction)
3296 }
3297
3298 pub fn new_randomness_state_update(
3299 epoch: u64,
3300 randomness_round: RandomnessRound,
3301 random_bytes: Vec<u8>,
3302 randomness_obj_initial_shared_version: SequenceNumber,
3303 ) -> Self {
3304 RandomnessStateUpdate {
3305 epoch,
3306 randomness_round,
3307 random_bytes,
3308 randomness_obj_initial_shared_version,
3309 }
3310 .pipe(TransactionKind::RandomnessStateUpdate)
3311 .pipe(Self::new_system_transaction)
3312 }
3313
3314 pub fn new_end_of_epoch_transaction(txns: Vec<EndOfEpochTransactionKind>) -> Self {
3315 TransactionKind::EndOfEpochTransaction(txns).pipe(Self::new_system_transaction)
3316 }
3317
3318 pub fn new_system_transaction(system_transaction: TransactionKind) -> Self {
3319 system_transaction
3320 .pipe(TransactionData::new_system_transaction)
3321 .pipe(|data| {
3322 SenderSignedData::new_from_sender_signature(
3323 data,
3324 Ed25519SuiSignature::from_bytes(&[0; Ed25519SuiSignature::LENGTH])
3325 .unwrap()
3326 .into(),
3327 )
3328 })
3329 .pipe(Transaction::new)
3330 .pipe(Self::new_from_verified)
3331 }
3332}
3333
3334impl VerifiedSignedTransaction {
3335 pub fn new(
3337 epoch: EpochId,
3338 transaction: VerifiedTransaction,
3339 authority: AuthorityName,
3340 secret: &dyn Signer<AuthoritySignature>,
3341 ) -> Self {
3342 Self::new_from_verified(SignedTransaction::new(
3343 epoch,
3344 transaction.into_inner().into_data(),
3345 secret,
3346 authority,
3347 ))
3348 }
3349}
3350
3351pub type Transaction = Envelope<SenderSignedData, EmptySignInfo>;
3353pub type VerifiedTransaction = VerifiedEnvelope<SenderSignedData, EmptySignInfo>;
3354pub type TrustedTransaction = TrustedEnvelope<SenderSignedData, EmptySignInfo>;
3355
3356pub type SignedTransaction = Envelope<SenderSignedData, AuthoritySignInfo>;
3358pub type VerifiedSignedTransaction = VerifiedEnvelope<SenderSignedData, AuthoritySignInfo>;
3359
3360impl Transaction {
3361 pub fn verify_signature_for_testing(
3362 &self,
3363 current_epoch: EpochId,
3364 verify_params: &VerifyParams,
3365 ) -> SuiResult {
3366 verify_sender_signed_data_message_signatures(
3367 self.data(),
3368 current_epoch,
3369 verify_params,
3370 Arc::new(VerifiedDigestCache::new_empty()),
3371 )
3372 }
3373
3374 pub fn try_into_verified_for_testing(
3375 self,
3376 current_epoch: EpochId,
3377 verify_params: &VerifyParams,
3378 ) -> SuiResult<VerifiedTransaction> {
3379 self.verify_signature_for_testing(current_epoch, verify_params)?;
3380 Ok(VerifiedTransaction::new_from_verified(self))
3381 }
3382}
3383
3384impl SignedTransaction {
3385 pub fn verify_signatures_authenticated_for_testing(
3386 &self,
3387 committee: &Committee,
3388 verify_params: &VerifyParams,
3389 ) -> SuiResult {
3390 verify_sender_signed_data_message_signatures(
3391 self.data(),
3392 committee.epoch(),
3393 verify_params,
3394 Arc::new(VerifiedDigestCache::new_empty()),
3395 )?;
3396
3397 self.auth_sig().verify_secure(
3398 self.data(),
3399 Intent::sui_app(IntentScope::SenderSignedTransaction),
3400 committee,
3401 )
3402 }
3403
3404 pub fn try_into_verified_for_testing(
3405 self,
3406 committee: &Committee,
3407 verify_params: &VerifyParams,
3408 ) -> SuiResult<VerifiedSignedTransaction> {
3409 self.verify_signatures_authenticated_for_testing(committee, verify_params)?;
3410 Ok(VerifiedSignedTransaction::new_from_verified(self))
3411 }
3412}
3413
3414pub type CertifiedTransaction = Envelope<SenderSignedData, AuthorityStrongQuorumSignInfo>;
3415
3416impl CertifiedTransaction {
3417 pub fn certificate_digest(&self) -> CertificateDigest {
3418 let mut digest = DefaultHash::default();
3419 bcs::serialize_into(&mut digest, self).expect("serialization should not fail");
3420 let hash = digest.finalize();
3421 CertificateDigest::new(hash.into())
3422 }
3423
3424 pub fn gas_price(&self) -> u64 {
3425 self.data().transaction_data().gas_price()
3426 }
3427
3428 pub fn verify_signatures_authenticated(
3431 &self,
3432 committee: &Committee,
3433 verify_params: &VerifyParams,
3434 zklogin_inputs_cache: Arc<VerifiedDigestCache<ZKLoginInputsDigest>>,
3435 ) -> SuiResult {
3436 verify_sender_signed_data_message_signatures(
3437 self.data(),
3438 committee.epoch(),
3439 verify_params,
3440 zklogin_inputs_cache,
3441 )?;
3442 self.auth_sig().verify_secure(
3443 self.data(),
3444 Intent::sui_app(IntentScope::SenderSignedTransaction),
3445 committee,
3446 )
3447 }
3448
3449 pub fn try_into_verified_for_testing(
3450 self,
3451 committee: &Committee,
3452 verify_params: &VerifyParams,
3453 ) -> SuiResult<VerifiedCertificate> {
3454 self.verify_signatures_authenticated(
3455 committee,
3456 verify_params,
3457 Arc::new(VerifiedDigestCache::new_empty()),
3458 )?;
3459 Ok(VerifiedCertificate::new_from_verified(self))
3460 }
3461
3462 pub fn verify_committee_sigs_only(&self, committee: &Committee) -> SuiResult {
3463 self.auth_sig().verify_secure(
3464 self.data(),
3465 Intent::sui_app(IntentScope::SenderSignedTransaction),
3466 committee,
3467 )
3468 }
3469}
3470
3471pub type VerifiedCertificate = VerifiedEnvelope<SenderSignedData, AuthorityStrongQuorumSignInfo>;
3472pub type TrustedCertificate = TrustedEnvelope<SenderSignedData, AuthorityStrongQuorumSignInfo>;
3473
3474#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize, PartialOrd, Ord, Hash)]
3475pub enum InputObjectKind {
3476 MovePackage(ObjectID),
3478 ImmOrOwnedMoveObject(ObjectRef),
3480 SharedMoveObject {
3482 id: ObjectID,
3483 initial_shared_version: SequenceNumber,
3484 mutability: SharedObjectMutability,
3485 },
3486}
3487
3488#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize, PartialOrd, Ord, Hash)]
3489pub enum SharedObjectMutability {
3490 Immutable,
3492 Mutable,
3493 NonExclusiveWrite,
3497}
3498
3499impl SharedObjectMutability {
3500 pub fn is_exclusive(&self) -> bool {
3501 match self {
3502 SharedObjectMutability::Mutable => true,
3503 SharedObjectMutability::Immutable => false,
3504 SharedObjectMutability::NonExclusiveWrite => false,
3505 }
3506 }
3507}
3508
3509impl InputObjectKind {
3510 pub fn object_id(&self) -> ObjectID {
3511 self.full_object_id().id()
3512 }
3513
3514 pub fn full_object_id(&self) -> FullObjectID {
3515 match self {
3516 Self::MovePackage(id) => FullObjectID::Fastpath(*id),
3517 Self::ImmOrOwnedMoveObject((id, _, _)) => FullObjectID::Fastpath(*id),
3518 Self::SharedMoveObject {
3519 id,
3520 initial_shared_version,
3521 ..
3522 } => FullObjectID::Consensus((*id, *initial_shared_version)),
3523 }
3524 }
3525
3526 pub fn version(&self) -> Option<SequenceNumber> {
3527 match self {
3528 Self::MovePackage(..) => None,
3529 Self::ImmOrOwnedMoveObject((_, version, _)) => Some(*version),
3530 Self::SharedMoveObject { .. } => None,
3531 }
3532 }
3533
3534 pub fn object_not_found_error(&self) -> UserInputError {
3535 match *self {
3536 Self::MovePackage(package_id) => {
3537 UserInputError::DependentPackageNotFound { package_id }
3538 }
3539 Self::ImmOrOwnedMoveObject((object_id, version, _)) => UserInputError::ObjectNotFound {
3540 object_id,
3541 version: Some(version),
3542 },
3543 Self::SharedMoveObject { id, .. } => UserInputError::ObjectNotFound {
3544 object_id: id,
3545 version: None,
3546 },
3547 }
3548 }
3549
3550 pub fn is_shared_object(&self) -> bool {
3551 matches!(self, Self::SharedMoveObject { .. })
3552 }
3553}
3554
3555#[derive(Clone, Debug)]
3558pub struct ObjectReadResult {
3559 pub input_object_kind: InputObjectKind,
3560 pub object: ObjectReadResultKind,
3561}
3562
3563#[derive(Clone)]
3564pub enum ObjectReadResultKind {
3565 Object(Object),
3566 ObjectConsensusStreamEnded(SequenceNumber, TransactionDigest),
3569 CancelledTransactionSharedObject(SequenceNumber),
3571}
3572
3573impl ObjectReadResultKind {
3574 pub fn is_cancelled(&self) -> bool {
3575 matches!(
3576 self,
3577 ObjectReadResultKind::CancelledTransactionSharedObject(_)
3578 )
3579 }
3580
3581 pub fn version(&self) -> SequenceNumber {
3582 match self {
3583 ObjectReadResultKind::Object(object) => object.version(),
3584 ObjectReadResultKind::ObjectConsensusStreamEnded(seq, _) => *seq,
3585 ObjectReadResultKind::CancelledTransactionSharedObject(seq) => *seq,
3586 }
3587 }
3588}
3589
3590impl std::fmt::Debug for ObjectReadResultKind {
3591 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3592 match self {
3593 ObjectReadResultKind::Object(obj) => {
3594 write!(f, "Object({:?})", obj.compute_object_reference())
3595 }
3596 ObjectReadResultKind::ObjectConsensusStreamEnded(seq, digest) => {
3597 write!(f, "ObjectConsensusStreamEnded({}, {:?})", seq, digest)
3598 }
3599 ObjectReadResultKind::CancelledTransactionSharedObject(seq) => {
3600 write!(f, "CancelledTransactionSharedObject({})", seq)
3601 }
3602 }
3603 }
3604}
3605
3606impl From<Object> for ObjectReadResultKind {
3607 fn from(object: Object) -> Self {
3608 Self::Object(object)
3609 }
3610}
3611
3612impl ObjectReadResult {
3613 pub fn new(input_object_kind: InputObjectKind, object: ObjectReadResultKind) -> Self {
3614 if let (
3615 InputObjectKind::ImmOrOwnedMoveObject(_),
3616 ObjectReadResultKind::ObjectConsensusStreamEnded(_, _),
3617 ) = (&input_object_kind, &object)
3618 {
3619 panic!("only consensus objects can be ObjectConsensusStreamEnded");
3620 }
3621
3622 if let (
3623 InputObjectKind::ImmOrOwnedMoveObject(_),
3624 ObjectReadResultKind::CancelledTransactionSharedObject(_),
3625 ) = (&input_object_kind, &object)
3626 {
3627 panic!("only consensus objects can be CancelledTransactionSharedObject");
3628 }
3629
3630 Self {
3631 input_object_kind,
3632 object,
3633 }
3634 }
3635
3636 pub fn id(&self) -> ObjectID {
3637 self.input_object_kind.object_id()
3638 }
3639
3640 pub fn as_object(&self) -> Option<&Object> {
3641 match &self.object {
3642 ObjectReadResultKind::Object(object) => Some(object),
3643 ObjectReadResultKind::ObjectConsensusStreamEnded(_, _) => None,
3644 ObjectReadResultKind::CancelledTransactionSharedObject(_) => None,
3645 }
3646 }
3647
3648 pub fn new_from_gas_object(gas: &Object) -> Self {
3649 let objref = gas.compute_object_reference();
3650 Self {
3651 input_object_kind: InputObjectKind::ImmOrOwnedMoveObject(objref),
3652 object: ObjectReadResultKind::Object(gas.clone()),
3653 }
3654 }
3655
3656 pub fn is_mutable(&self) -> bool {
3657 match (&self.input_object_kind, &self.object) {
3658 (InputObjectKind::MovePackage(_), _) => false,
3659 (InputObjectKind::ImmOrOwnedMoveObject(_), ObjectReadResultKind::Object(object)) => {
3660 !object.is_immutable()
3661 }
3662 (
3663 InputObjectKind::ImmOrOwnedMoveObject(_),
3664 ObjectReadResultKind::ObjectConsensusStreamEnded(_, _),
3665 ) => unreachable!(),
3666 (
3667 InputObjectKind::ImmOrOwnedMoveObject(_),
3668 ObjectReadResultKind::CancelledTransactionSharedObject(_),
3669 ) => unreachable!(),
3670 (InputObjectKind::SharedMoveObject { mutability, .. }, _) => match mutability {
3671 SharedObjectMutability::Mutable => true,
3672 SharedObjectMutability::Immutable => false,
3673 SharedObjectMutability::NonExclusiveWrite => false,
3674 },
3675 }
3676 }
3677
3678 pub fn is_shared_object(&self) -> bool {
3679 self.input_object_kind.is_shared_object()
3680 }
3681
3682 pub fn is_consensus_stream_ended(&self) -> bool {
3683 self.consensus_stream_end_info().is_some()
3684 }
3685
3686 pub fn consensus_stream_end_info(&self) -> Option<(SequenceNumber, TransactionDigest)> {
3687 match &self.object {
3688 ObjectReadResultKind::ObjectConsensusStreamEnded(v, tx) => Some((*v, *tx)),
3689 _ => None,
3690 }
3691 }
3692
3693 pub fn get_owned_objref(&self) -> Option<ObjectRef> {
3695 match (&self.input_object_kind, &self.object) {
3696 (InputObjectKind::MovePackage(_), _) => None,
3697 (
3698 InputObjectKind::ImmOrOwnedMoveObject(objref),
3699 ObjectReadResultKind::Object(object),
3700 ) => {
3701 if object.is_immutable() {
3702 None
3703 } else {
3704 Some(*objref)
3705 }
3706 }
3707 (
3708 InputObjectKind::ImmOrOwnedMoveObject(_),
3709 ObjectReadResultKind::ObjectConsensusStreamEnded(_, _),
3710 ) => unreachable!(),
3711 (
3712 InputObjectKind::ImmOrOwnedMoveObject(_),
3713 ObjectReadResultKind::CancelledTransactionSharedObject(_),
3714 ) => unreachable!(),
3715 (InputObjectKind::SharedMoveObject { .. }, _) => None,
3716 }
3717 }
3718
3719 pub fn is_owned(&self) -> bool {
3720 self.get_owned_objref().is_some()
3721 }
3722
3723 pub fn to_shared_input(&self) -> Option<SharedInput> {
3724 match self.input_object_kind {
3725 InputObjectKind::MovePackage(_) => None,
3726 InputObjectKind::ImmOrOwnedMoveObject(_) => None,
3727 InputObjectKind::SharedMoveObject { id, mutability, .. } => Some(match &self.object {
3728 ObjectReadResultKind::Object(obj) => {
3729 SharedInput::Existing(obj.compute_object_reference())
3730 }
3731 ObjectReadResultKind::ObjectConsensusStreamEnded(seq, digest) => {
3732 SharedInput::ConsensusStreamEnded((id, *seq, mutability, *digest))
3733 }
3734 ObjectReadResultKind::CancelledTransactionSharedObject(seq) => {
3735 SharedInput::Cancelled((id, *seq))
3736 }
3737 }),
3738 }
3739 }
3740
3741 pub fn get_previous_transaction(&self) -> Option<TransactionDigest> {
3742 match &self.object {
3743 ObjectReadResultKind::Object(obj) => Some(obj.previous_transaction),
3744 ObjectReadResultKind::ObjectConsensusStreamEnded(_, digest) => Some(*digest),
3745 ObjectReadResultKind::CancelledTransactionSharedObject(_) => None,
3746 }
3747 }
3748}
3749
3750#[derive(Clone)]
3751pub struct InputObjects {
3752 objects: Vec<ObjectReadResult>,
3753}
3754
3755impl std::fmt::Debug for InputObjects {
3756 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3757 f.debug_list().entries(self.objects.iter()).finish()
3758 }
3759}
3760
3761pub struct CheckedInputObjects(InputObjects);
3764
3765impl CheckedInputObjects {
3771 pub fn new_with_checked_transaction_inputs(inputs: InputObjects) -> Self {
3773 Self(inputs)
3774 }
3775
3776 pub fn new_for_genesis(input_objects: Vec<ObjectReadResult>) -> Self {
3778 Self(InputObjects::new(input_objects))
3779 }
3780
3781 pub fn new_for_replay(input_objects: InputObjects) -> Self {
3783 Self(input_objects)
3784 }
3785
3786 pub fn inner(&self) -> &InputObjects {
3787 &self.0
3788 }
3789
3790 pub fn into_inner(self) -> InputObjects {
3791 self.0
3792 }
3793}
3794
3795impl From<Vec<ObjectReadResult>> for InputObjects {
3796 fn from(objects: Vec<ObjectReadResult>) -> Self {
3797 Self::new(objects)
3798 }
3799}
3800
3801impl InputObjects {
3802 pub fn new(objects: Vec<ObjectReadResult>) -> Self {
3803 Self { objects }
3804 }
3805
3806 pub fn len(&self) -> usize {
3807 self.objects.len()
3808 }
3809
3810 pub fn is_empty(&self) -> bool {
3811 self.objects.is_empty()
3812 }
3813
3814 pub fn contains_consensus_stream_ended_objects(&self) -> bool {
3815 self.objects
3816 .iter()
3817 .any(|obj| obj.is_consensus_stream_ended())
3818 }
3819
3820 pub fn get_cancelled_objects(&self) -> Option<(Vec<ObjectID>, SequenceNumber)> {
3823 let mut contains_cancelled = false;
3824 let mut cancel_reason = None;
3825 let mut cancelled_objects = Vec::new();
3826 for obj in &self.objects {
3827 if let ObjectReadResultKind::CancelledTransactionSharedObject(version) = obj.object {
3828 contains_cancelled = true;
3829 if version == SequenceNumber::CONGESTED
3830 || version == SequenceNumber::RANDOMNESS_UNAVAILABLE
3831 {
3832 assert!(cancel_reason.is_none() || cancel_reason == Some(version));
3834 cancel_reason = Some(version);
3835 cancelled_objects.push(obj.id());
3836 }
3837 }
3838 }
3839
3840 if !cancelled_objects.is_empty() {
3841 Some((
3842 cancelled_objects,
3843 cancel_reason
3844 .expect("there should be a cancel reason if there are cancelled objects"),
3845 ))
3846 } else {
3847 assert!(!contains_cancelled);
3848 None
3849 }
3850 }
3851
3852 pub fn filter_owned_objects(&self) -> Vec<ObjectRef> {
3853 let owned_objects: Vec<_> = self
3854 .objects
3855 .iter()
3856 .filter_map(|obj| obj.get_owned_objref())
3857 .collect();
3858
3859 trace!(
3860 num_mutable_objects = owned_objects.len(),
3861 "Checked locks and found mutable objects"
3862 );
3863
3864 owned_objects
3865 }
3866
3867 pub fn filter_shared_objects(&self) -> Vec<SharedInput> {
3868 self.objects
3869 .iter()
3870 .filter(|obj| obj.is_shared_object())
3871 .map(|obj| {
3872 obj.to_shared_input()
3873 .expect("already filtered for shared objects")
3874 })
3875 .collect()
3876 }
3877
3878 pub fn transaction_dependencies(&self) -> BTreeSet<TransactionDigest> {
3879 self.objects
3880 .iter()
3881 .filter_map(|obj| obj.get_previous_transaction())
3882 .collect()
3883 }
3884
3885 pub fn exclusive_mutable_inputs(&self) -> BTreeMap<ObjectID, (VersionDigest, Owner)> {
3888 self.mutables_with_input_kinds()
3889 .filter_map(|(id, (version, owner, kind))| match kind {
3890 InputObjectKind::SharedMoveObject { mutability, .. } => match mutability {
3891 SharedObjectMutability::Mutable => Some((id, (version, owner))),
3892 SharedObjectMutability::Immutable => None,
3893 SharedObjectMutability::NonExclusiveWrite => None,
3894 },
3895 _ => Some((id, (version, owner))),
3896 })
3897 .collect()
3898 }
3899
3900 pub fn non_exclusive_input_objects(&self) -> BTreeMap<ObjectID, Object> {
3901 self.objects
3902 .iter()
3903 .filter_map(|read_result| {
3904 match (read_result.as_object(), read_result.input_object_kind) {
3905 (
3906 Some(object),
3907 InputObjectKind::SharedMoveObject {
3908 mutability: SharedObjectMutability::NonExclusiveWrite,
3909 ..
3910 },
3911 ) => Some((read_result.id(), object.clone())),
3912 _ => None,
3913 }
3914 })
3915 .collect()
3916 }
3917
3918 pub fn all_mutable_inputs(&self) -> BTreeMap<ObjectID, (VersionDigest, Owner)> {
3921 self.mutables_with_input_kinds()
3922 .filter_map(|(id, (version, owner, kind))| match kind {
3923 InputObjectKind::SharedMoveObject { mutability, .. } => match mutability {
3924 SharedObjectMutability::Mutable => Some((id, (version, owner))),
3925 SharedObjectMutability::Immutable => None,
3926 SharedObjectMutability::NonExclusiveWrite => Some((id, (version, owner))),
3927 },
3928 _ => Some((id, (version, owner))),
3929 })
3930 .collect()
3931 }
3932
3933 fn mutables_with_input_kinds(
3934 &self,
3935 ) -> impl Iterator<Item = (ObjectID, (VersionDigest, Owner, InputObjectKind))> + '_ {
3936 self.objects.iter().filter_map(
3937 |ObjectReadResult {
3938 input_object_kind,
3939 object,
3940 }| match (input_object_kind, object) {
3941 (InputObjectKind::MovePackage(_), _) => None,
3942 (
3943 InputObjectKind::ImmOrOwnedMoveObject(object_ref),
3944 ObjectReadResultKind::Object(object),
3945 ) => {
3946 if object.is_immutable() {
3947 None
3948 } else {
3949 Some((
3950 object_ref.0,
3951 (
3952 (object_ref.1, object_ref.2),
3953 object.owner.clone(),
3954 *input_object_kind,
3955 ),
3956 ))
3957 }
3958 }
3959 (
3960 InputObjectKind::ImmOrOwnedMoveObject(_),
3961 ObjectReadResultKind::ObjectConsensusStreamEnded(_, _),
3962 ) => {
3963 unreachable!()
3964 }
3965 (
3966 InputObjectKind::SharedMoveObject { .. },
3967 ObjectReadResultKind::ObjectConsensusStreamEnded(_, _),
3968 ) => None,
3969 (
3970 InputObjectKind::SharedMoveObject { mutability, .. },
3971 ObjectReadResultKind::Object(object),
3972 ) => match *mutability {
3973 SharedObjectMutability::Mutable => {
3974 let oref = object.compute_object_reference();
3975 Some((
3976 oref.0,
3977 ((oref.1, oref.2), object.owner.clone(), *input_object_kind),
3978 ))
3979 }
3980 SharedObjectMutability::Immutable => None,
3981 SharedObjectMutability::NonExclusiveWrite => {
3982 let oref = object.compute_object_reference();
3983 Some((
3984 oref.0,
3985 ((oref.1, oref.2), object.owner.clone(), *input_object_kind),
3986 ))
3987 }
3988 },
3989 (
3990 InputObjectKind::ImmOrOwnedMoveObject(_),
3991 ObjectReadResultKind::CancelledTransactionSharedObject(_),
3992 ) => {
3993 unreachable!()
3994 }
3995 (
3996 InputObjectKind::SharedMoveObject { .. },
3997 ObjectReadResultKind::CancelledTransactionSharedObject(_),
3998 ) => None,
3999 },
4000 )
4001 }
4002
4003 pub fn lamport_timestamp(&self, receiving_objects: &[ObjectRef]) -> SequenceNumber {
4007 let input_versions = self
4008 .objects
4009 .iter()
4010 .filter_map(|object| match &object.object {
4011 ObjectReadResultKind::Object(object) => {
4012 object.data.try_as_move().map(MoveObject::version)
4013 }
4014 ObjectReadResultKind::ObjectConsensusStreamEnded(v, _) => Some(*v),
4015 ObjectReadResultKind::CancelledTransactionSharedObject(_) => None,
4016 })
4017 .chain(receiving_objects.iter().map(|object_ref| object_ref.1));
4018
4019 SequenceNumber::lamport_increment(input_versions)
4020 }
4021
4022 pub fn object_kinds(&self) -> impl Iterator<Item = &InputObjectKind> {
4023 self.objects.iter().map(
4024 |ObjectReadResult {
4025 input_object_kind, ..
4026 }| input_object_kind,
4027 )
4028 }
4029
4030 pub fn consensus_stream_ended_objects(&self) -> BTreeMap<ObjectID, SequenceNumber> {
4031 self.objects
4032 .iter()
4033 .filter_map(|obj| {
4034 if let InputObjectKind::SharedMoveObject {
4035 id,
4036 initial_shared_version,
4037 ..
4038 } = obj.input_object_kind
4039 {
4040 obj.is_consensus_stream_ended()
4041 .then_some((id, initial_shared_version))
4042 } else {
4043 None
4044 }
4045 })
4046 .collect()
4047 }
4048
4049 pub fn into_object_map(self) -> BTreeMap<ObjectID, Object> {
4050 self.objects
4051 .into_iter()
4052 .filter_map(|o| o.as_object().map(|object| (o.id(), object.clone())))
4053 .collect()
4054 }
4055
4056 pub fn push(&mut self, object: ObjectReadResult) {
4057 self.objects.push(object);
4058 }
4059
4060 pub fn iter(&self) -> impl Iterator<Item = &ObjectReadResult> {
4061 self.objects.iter()
4062 }
4063
4064 pub fn iter_objects(&self) -> impl Iterator<Item = &Object> {
4065 self.objects.iter().filter_map(|o| o.as_object())
4066 }
4067
4068 pub fn non_exclusive_mutable_inputs(
4069 &self,
4070 ) -> impl Iterator<Item = (ObjectID, SequenceNumber)> + '_ {
4071 self.objects.iter().filter_map(
4072 |ObjectReadResult {
4073 input_object_kind,
4074 object,
4075 }| match input_object_kind {
4076 InputObjectKind::SharedMoveObject {
4080 id,
4081 mutability: SharedObjectMutability::NonExclusiveWrite,
4082 ..
4083 } if !object.is_cancelled() => Some((*id, object.version())),
4084 _ => None,
4085 },
4086 )
4087 }
4088}
4089
4090#[derive(Clone, Debug)]
4094pub enum ReceivingObjectReadResultKind {
4095 Object(Object),
4096 PreviouslyReceivedObject,
4098}
4099
4100impl ReceivingObjectReadResultKind {
4101 pub fn as_object(&self) -> Option<&Object> {
4102 match &self {
4103 Self::Object(object) => Some(object),
4104 Self::PreviouslyReceivedObject => None,
4105 }
4106 }
4107}
4108
4109pub struct ReceivingObjectReadResult {
4110 pub object_ref: ObjectRef,
4111 pub object: ReceivingObjectReadResultKind,
4112}
4113
4114impl ReceivingObjectReadResult {
4115 pub fn new(object_ref: ObjectRef, object: ReceivingObjectReadResultKind) -> Self {
4116 Self { object_ref, object }
4117 }
4118
4119 pub fn is_previously_received(&self) -> bool {
4120 matches!(
4121 self.object,
4122 ReceivingObjectReadResultKind::PreviouslyReceivedObject
4123 )
4124 }
4125}
4126
4127impl From<Object> for ReceivingObjectReadResultKind {
4128 fn from(object: Object) -> Self {
4129 Self::Object(object)
4130 }
4131}
4132
4133pub struct ReceivingObjects {
4134 pub objects: Vec<ReceivingObjectReadResult>,
4135}
4136
4137impl ReceivingObjects {
4138 pub fn iter(&self) -> impl Iterator<Item = &ReceivingObjectReadResult> {
4139 self.objects.iter()
4140 }
4141
4142 pub fn iter_objects(&self) -> impl Iterator<Item = &Object> {
4143 self.objects.iter().filter_map(|o| o.object.as_object())
4144 }
4145}
4146
4147impl From<Vec<ReceivingObjectReadResult>> for ReceivingObjects {
4148 fn from(objects: Vec<ReceivingObjectReadResult>) -> Self {
4149 Self { objects }
4150 }
4151}
4152
4153impl Display for CertifiedTransaction {
4154 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
4155 let mut writer = String::new();
4156 writeln!(writer, "Transaction Hash: {:?}", self.digest())?;
4157 writeln!(
4158 writer,
4159 "Signed Authorities Bitmap : {:?}",
4160 self.auth_sig().signers_map
4161 )?;
4162 write!(writer, "{}", &self.data().intent_message().value.kind())?;
4163 write!(f, "{}", writer)
4164 }
4165}
4166
4167#[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
4171pub enum TransactionKey {
4172 Digest(TransactionDigest),
4173 RandomnessRound(EpochId, RandomnessRound),
4174 AccumulatorSettlement(EpochId, u64 ),
4175}
4176
4177impl TransactionKey {
4178 pub fn unwrap_digest(&self) -> &TransactionDigest {
4179 match self {
4180 TransactionKey::Digest(d) => d,
4181 _ => panic!("called expect_digest on a non-Digest TransactionKey: {self:?}"),
4182 }
4183 }
4184
4185 pub fn as_digest(&self) -> Option<&TransactionDigest> {
4186 match self {
4187 TransactionKey::Digest(d) => Some(d),
4188 _ => None,
4189 }
4190 }
4191}