sui_types/
transaction.rs

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