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