Skip to main content

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