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
2499/// SIP-45: the number of consensus submissions a gas price pays for. Every transaction may
2500/// be proposed by `MAX_UNPAID_ALLOWED_PROPOSERS` validators; a gas price of `n` times the
2501/// reference price buys `n`. The single definition behind sizing an explicit proposer set,
2502/// the immediate slots of staggered submission, and the deferral of unpaid duplicates.
2503pub fn paid_proposer_count(gas_price: u64, reference_gas_price: u64) -> u64 {
2504    MAX_UNPAID_ALLOWED_PROPOSERS.max(gas_price / reference_gas_price.max(1))
2505}
2506
2507impl TransactionExpiration {
2508    /// Validators remember all executed transaction digests from the current and previous
2509    /// epoch. Therefore, a one or two epoch validity window provides replay protection.
2510    /// Either the transaction is statically invalid (current epoch not within range) or the
2511    /// validator will remember if the transaction was already executed.
2512    pub fn is_replay_protected(&self) -> bool {
2513        matches!(
2514            self,
2515            TransactionExpiration::ValidDuring {
2516                min_epoch: Some(min_epoch),
2517                max_epoch: Some(max_epoch),
2518                ..
2519            }
2520            | TransactionExpiration::Validity {
2521                min_epoch: Some(min_epoch),
2522                max_epoch: Some(max_epoch),
2523                ..
2524            } if *max_epoch == *min_epoch || *max_epoch == min_epoch.saturating_add(1)
2525        )
2526    }
2527
2528    /// The validators allowed to propose this transaction in consensus during `epoch`.
2529    ///
2530    /// A set recorded for any other epoch indexes into a committee that is not the one deciding
2531    /// this transaction, so it is ignored entirely and the transaction is treated as naming no
2532    /// proposers. The incentive to record the current epoch is on the signer, who is the party
2533    /// the restriction protects; transactions without a usable set may in future be subject to
2534    /// submission delays to limit amplification.
2535    pub fn allowed_proposers(&self, epoch: EpochId) -> Option<&AllowedProposers> {
2536        match self {
2537            TransactionExpiration::Validity {
2538                allowed_proposers: Some(allowed),
2539                ..
2540            } if allowed.epoch == epoch => Some(allowed),
2541            _ => None,
2542        }
2543    }
2544
2545    /// Whether the transaction restricts its proposers during `epoch`. Only such a transaction has
2546    /// its consensus amplification bounded by `validity_check`.
2547    pub fn restricts_proposers(&self, epoch: EpochId) -> bool {
2548        self.allowed_proposers(epoch).is_some()
2549    }
2550
2551    /// Whether `proposer` (an index into `epoch`'s committee) may propose this transaction in
2552    /// consensus.
2553    pub fn is_allowed_proposer(&self, proposer: u32, epoch: EpochId) -> bool {
2554        self.allowed_proposers(epoch).is_none_or(|allowed| {
2555            // The set is strictly increasing, so stop as soon as it passes `proposer`. An
2556            // unsorted set is rejected by validity_check; reading it as a miss here is
2557            // deterministic across validators, which is all block verification requires.
2558            allowed
2559                .proposers
2560                .iter()
2561                .find(|p| **p >= proposer)
2562                .is_some_and(|p| *p == proposer)
2563        })
2564    }
2565}
2566
2567#[enum_dispatch(TransactionDataAPI)]
2568#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)]
2569pub enum TransactionData {
2570    V1(TransactionDataV1),
2571    // When new variants are introduced, it is important that we check version support
2572    // in the validity_check function based on the protocol config.
2573}
2574
2575#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)]
2576pub struct TransactionDataV1 {
2577    pub kind: TransactionKind,
2578    pub sender: SuiAddress,
2579    pub gas_data: GasData,
2580    pub expiration: TransactionExpiration,
2581}
2582
2583impl TransactionData {
2584    pub fn as_v1(&self) -> &TransactionDataV1 {
2585        match self {
2586            TransactionData::V1(v1) => v1,
2587        }
2588    }
2589
2590    /// Whether the transaction is protected against replay, given its loaded `input_objects`:
2591    /// it has a `ValidDuring` expiration of at most two epochs, a gas payment object, or an
2592    /// address-owned input or coin reservation among `input_objects`. `input_objects` is only
2593    /// consumed when the expiration and gas payment do not already protect the transaction.
2594    pub fn has_replay_protection(
2595        &self,
2596        input_objects: impl IntoIterator<Item = impl std::borrow::Borrow<ObjectReadResult>>,
2597    ) -> bool {
2598        self.expiration().is_replay_protected()
2599            || !self.gas_data().payment.is_empty()
2600            || input_objects
2601                .into_iter()
2602                .any(|object| object.borrow().is_replay_protected_input())
2603    }
2604    fn new_system_transaction(kind: TransactionKind) -> Self {
2605        // assert transaction kind if a system transaction
2606        assert!(kind.is_system_tx());
2607        let sender = SuiAddress::default();
2608        TransactionData::V1(TransactionDataV1 {
2609            kind,
2610            sender,
2611            gas_data: GasData {
2612                price: GAS_PRICE_FOR_SYSTEM_TX,
2613                owner: sender,
2614                payment: vec![(ObjectID::ZERO, SequenceNumber::default(), ObjectDigest::MIN)],
2615                budget: 0,
2616            },
2617            expiration: TransactionExpiration::None,
2618        })
2619    }
2620
2621    pub fn new(
2622        kind: TransactionKind,
2623        sender: SuiAddress,
2624        gas_payment: ObjectRef,
2625        gas_budget: u64,
2626        gas_price: u64,
2627    ) -> Self {
2628        TransactionData::V1(TransactionDataV1 {
2629            kind,
2630            sender,
2631            gas_data: GasData {
2632                price: gas_price,
2633                owner: sender,
2634                payment: vec![gas_payment],
2635                budget: gas_budget,
2636            },
2637            expiration: TransactionExpiration::None,
2638        })
2639    }
2640
2641    pub fn new_with_gas_coins(
2642        kind: TransactionKind,
2643        sender: SuiAddress,
2644        gas_payment: Vec<ObjectRef>,
2645        gas_budget: u64,
2646        gas_price: u64,
2647    ) -> Self {
2648        Self::new_with_gas_coins_allow_sponsor(
2649            kind,
2650            sender,
2651            gas_payment,
2652            gas_budget,
2653            gas_price,
2654            sender,
2655        )
2656    }
2657
2658    pub fn new_with_gas_coins_allow_sponsor(
2659        kind: TransactionKind,
2660        sender: SuiAddress,
2661        gas_payment: Vec<ObjectRef>,
2662        gas_budget: u64,
2663        gas_price: u64,
2664        gas_sponsor: SuiAddress,
2665    ) -> Self {
2666        TransactionData::V1(TransactionDataV1 {
2667            kind,
2668            sender,
2669            gas_data: GasData {
2670                price: gas_price,
2671                owner: gas_sponsor,
2672                payment: gas_payment,
2673                budget: gas_budget,
2674            },
2675            expiration: TransactionExpiration::None,
2676        })
2677    }
2678
2679    pub fn new_with_gas_data(kind: TransactionKind, sender: SuiAddress, gas_data: GasData) -> Self {
2680        TransactionData::V1(TransactionDataV1 {
2681            kind,
2682            sender,
2683            gas_data,
2684            expiration: TransactionExpiration::None,
2685        })
2686    }
2687
2688    pub fn new_with_gas_data_and_expiration(
2689        kind: TransactionKind,
2690        sender: SuiAddress,
2691        gas_data: GasData,
2692        expiration: TransactionExpiration,
2693    ) -> Self {
2694        TransactionData::V1(TransactionDataV1 {
2695            kind,
2696            sender,
2697            gas_data,
2698            expiration,
2699        })
2700    }
2701
2702    pub fn new_move_call(
2703        sender: SuiAddress,
2704        package: ObjectID,
2705        module: Identifier,
2706        function: Identifier,
2707        type_arguments: Vec<TypeTag>,
2708        gas_payment: ObjectRef,
2709        arguments: Vec<CallArg>,
2710        gas_budget: u64,
2711        gas_price: u64,
2712    ) -> anyhow::Result<Self> {
2713        Self::new_move_call_with_gas_coins(
2714            sender,
2715            package,
2716            module,
2717            function,
2718            type_arguments,
2719            vec![gas_payment],
2720            arguments,
2721            gas_budget,
2722            gas_price,
2723        )
2724    }
2725
2726    pub fn new_move_call_with_gas_coins(
2727        sender: SuiAddress,
2728        package: ObjectID,
2729        module: Identifier,
2730        function: Identifier,
2731        type_arguments: Vec<TypeTag>,
2732        gas_payment: Vec<ObjectRef>,
2733        arguments: Vec<CallArg>,
2734        gas_budget: u64,
2735        gas_price: u64,
2736    ) -> anyhow::Result<Self> {
2737        let pt = {
2738            let mut builder = ProgrammableTransactionBuilder::new();
2739            builder.move_call(package, module, function, type_arguments, arguments)?;
2740            builder.finish()
2741        };
2742        Ok(Self::new_programmable(
2743            sender,
2744            gas_payment,
2745            pt,
2746            gas_budget,
2747            gas_price,
2748        ))
2749    }
2750
2751    pub fn new_transfer(
2752        recipient: SuiAddress,
2753        full_object_ref: FullObjectRef,
2754        sender: SuiAddress,
2755        gas_payment: ObjectRef,
2756        gas_budget: u64,
2757        gas_price: u64,
2758    ) -> Self {
2759        let pt = {
2760            let mut builder = ProgrammableTransactionBuilder::new();
2761            builder.transfer_object(recipient, full_object_ref).unwrap();
2762            builder.finish()
2763        };
2764        Self::new_programmable(sender, vec![gas_payment], pt, gas_budget, gas_price)
2765    }
2766
2767    pub fn new_transfer_sui(
2768        recipient: SuiAddress,
2769        sender: SuiAddress,
2770        amount: Option<u64>,
2771        gas_payment: ObjectRef,
2772        gas_budget: u64,
2773        gas_price: u64,
2774    ) -> Self {
2775        Self::new_transfer_sui_allow_sponsor(
2776            recipient,
2777            sender,
2778            amount,
2779            gas_payment,
2780            gas_budget,
2781            gas_price,
2782            sender,
2783        )
2784    }
2785
2786    pub fn new_transfer_sui_allow_sponsor(
2787        recipient: SuiAddress,
2788        sender: SuiAddress,
2789        amount: Option<u64>,
2790        gas_payment: ObjectRef,
2791        gas_budget: u64,
2792        gas_price: u64,
2793        gas_sponsor: SuiAddress,
2794    ) -> Self {
2795        let pt = {
2796            let mut builder = ProgrammableTransactionBuilder::new();
2797            builder.transfer_sui(recipient, amount);
2798            builder.finish()
2799        };
2800        Self::new_programmable_allow_sponsor(
2801            sender,
2802            vec![gas_payment],
2803            pt,
2804            gas_budget,
2805            gas_price,
2806            gas_sponsor,
2807        )
2808    }
2809
2810    pub fn new_pay(
2811        sender: SuiAddress,
2812        coins: Vec<ObjectRef>,
2813        recipients: Vec<SuiAddress>,
2814        amounts: Vec<u64>,
2815        gas_payment: ObjectRef,
2816        gas_budget: u64,
2817        gas_price: u64,
2818    ) -> anyhow::Result<Self> {
2819        let pt = {
2820            let mut builder = ProgrammableTransactionBuilder::new();
2821            builder.pay(coins, recipients, amounts)?;
2822            builder.finish()
2823        };
2824        Ok(Self::new_programmable(
2825            sender,
2826            vec![gas_payment],
2827            pt,
2828            gas_budget,
2829            gas_price,
2830        ))
2831    }
2832
2833    pub fn new_pay_sui(
2834        sender: SuiAddress,
2835        mut coins: Vec<ObjectRef>,
2836        recipients: Vec<SuiAddress>,
2837        amounts: Vec<u64>,
2838        gas_payment: ObjectRef,
2839        gas_budget: u64,
2840        gas_price: u64,
2841    ) -> anyhow::Result<Self> {
2842        coins.insert(0, gas_payment);
2843        let pt = {
2844            let mut builder = ProgrammableTransactionBuilder::new();
2845            builder.pay_sui(recipients, amounts)?;
2846            builder.finish()
2847        };
2848        Ok(Self::new_programmable(
2849            sender, coins, pt, gas_budget, gas_price,
2850        ))
2851    }
2852
2853    pub fn new_pay_all_sui(
2854        sender: SuiAddress,
2855        mut coins: Vec<ObjectRef>,
2856        recipient: SuiAddress,
2857        gas_payment: ObjectRef,
2858        gas_budget: u64,
2859        gas_price: u64,
2860    ) -> Self {
2861        coins.insert(0, gas_payment);
2862        let pt = {
2863            let mut builder = ProgrammableTransactionBuilder::new();
2864            builder.pay_all_sui(recipient);
2865            builder.finish()
2866        };
2867        Self::new_programmable(sender, coins, pt, gas_budget, gas_price)
2868    }
2869
2870    pub fn new_split_coin(
2871        sender: SuiAddress,
2872        coin: ObjectRef,
2873        amounts: Vec<u64>,
2874        gas_payment: ObjectRef,
2875        gas_budget: u64,
2876        gas_price: u64,
2877    ) -> Self {
2878        let pt = {
2879            let mut builder = ProgrammableTransactionBuilder::new();
2880            builder.split_coin(sender, coin, amounts);
2881            builder.finish()
2882        };
2883        Self::new_programmable(sender, vec![gas_payment], pt, gas_budget, gas_price)
2884    }
2885
2886    pub fn new_module(
2887        sender: SuiAddress,
2888        gas_payment: ObjectRef,
2889        modules: Vec<Vec<u8>>,
2890        dep_ids: Vec<ObjectID>,
2891        gas_budget: u64,
2892        gas_price: u64,
2893    ) -> Self {
2894        let pt = {
2895            let mut builder = ProgrammableTransactionBuilder::new();
2896            let upgrade_cap = builder.publish_upgradeable(modules, dep_ids);
2897            builder.transfer_arg(sender, upgrade_cap);
2898            builder.finish()
2899        };
2900        Self::new_programmable(sender, vec![gas_payment], pt, gas_budget, gas_price)
2901    }
2902
2903    pub fn new_upgrade(
2904        sender: SuiAddress,
2905        gas_payment: ObjectRef,
2906        package_id: ObjectID,
2907        modules: Vec<Vec<u8>>,
2908        dep_ids: Vec<ObjectID>,
2909        (upgrade_capability, capability_owner): (ObjectRef, Owner),
2910        upgrade_policy: u8,
2911        digest: Vec<u8>,
2912        gas_budget: u64,
2913        gas_price: u64,
2914    ) -> anyhow::Result<Self> {
2915        let pt = {
2916            let mut builder = ProgrammableTransactionBuilder::new();
2917            let capability_arg = match capability_owner {
2918                Owner::AddressOwner(_) => ObjectArg::ImmOrOwnedObject(upgrade_capability),
2919                Owner::Shared {
2920                    initial_shared_version,
2921                }
2922                | Owner::ConsensusAddressOwner {
2923                    start_version: initial_shared_version,
2924                    ..
2925                }
2926                | Owner::Party {
2927                    start_version: initial_shared_version,
2928                    ..
2929                } => ObjectArg::SharedObject {
2930                    id: upgrade_capability.0,
2931                    initial_shared_version,
2932                    mutability: SharedObjectMutability::Mutable,
2933                },
2934                Owner::Immutable => {
2935                    return Err(anyhow::anyhow!(
2936                        "Upgrade capability is stored immutably and cannot be used for upgrades"
2937                    ));
2938                }
2939                // If the capability is owned by an object, then the module defining the owning
2940                // object gets to decide how the upgrade capability should be used.
2941                Owner::ObjectOwner(_) => {
2942                    return Err(anyhow::anyhow!("Upgrade capability controlled by object"));
2943                }
2944            };
2945            builder.obj(capability_arg).unwrap();
2946            let upgrade_arg = builder.pure(upgrade_policy).unwrap();
2947            let digest_arg = builder.pure(digest).unwrap();
2948            let upgrade_ticket = builder.programmable_move_call(
2949                SUI_FRAMEWORK_PACKAGE_ID,
2950                ident_str!("package").to_owned(),
2951                ident_str!("authorize_upgrade").to_owned(),
2952                vec![],
2953                vec![Argument::Input(0), upgrade_arg, digest_arg],
2954            );
2955            let upgrade_receipt = builder.upgrade(package_id, upgrade_ticket, dep_ids, modules);
2956
2957            builder.programmable_move_call(
2958                SUI_FRAMEWORK_PACKAGE_ID,
2959                ident_str!("package").to_owned(),
2960                ident_str!("commit_upgrade").to_owned(),
2961                vec![],
2962                vec![Argument::Input(0), upgrade_receipt],
2963            );
2964
2965            builder.finish()
2966        };
2967        Ok(Self::new_programmable(
2968            sender,
2969            vec![gas_payment],
2970            pt,
2971            gas_budget,
2972            gas_price,
2973        ))
2974    }
2975
2976    pub fn new_programmable(
2977        sender: SuiAddress,
2978        gas_payment: Vec<ObjectRef>,
2979        pt: ProgrammableTransaction,
2980        gas_budget: u64,
2981        gas_price: u64,
2982    ) -> Self {
2983        Self::new_programmable_allow_sponsor(sender, gas_payment, pt, gas_budget, gas_price, sender)
2984    }
2985
2986    pub fn new_programmable_allow_sponsor(
2987        sender: SuiAddress,
2988        gas_payment: Vec<ObjectRef>,
2989        pt: ProgrammableTransaction,
2990        gas_budget: u64,
2991        gas_price: u64,
2992        sponsor: SuiAddress,
2993    ) -> Self {
2994        let kind = TransactionKind::ProgrammableTransaction(pt);
2995        Self::new_with_gas_coins_allow_sponsor(
2996            kind,
2997            sender,
2998            gas_payment,
2999            gas_budget,
3000            gas_price,
3001            sponsor,
3002        )
3003    }
3004
3005    pub fn new_programmable_with_address_balance_gas(
3006        sender: SuiAddress,
3007        pt: ProgrammableTransaction,
3008        gas_budget: u64,
3009        gas_price: u64,
3010        chain_identifier: ChainIdentifier,
3011        current_epoch: EpochId,
3012        nonce: u32,
3013    ) -> Self {
3014        TransactionData::V1(TransactionDataV1 {
3015            kind: TransactionKind::ProgrammableTransaction(pt),
3016            sender,
3017            gas_data: GasData {
3018                payment: vec![],
3019                owner: sender,
3020                price: gas_price,
3021                budget: gas_budget,
3022            },
3023            expiration: TransactionExpiration::ValidDuring {
3024                min_epoch: Some(current_epoch),
3025                max_epoch: Some(current_epoch + 1),
3026                min_timestamp: None,
3027                max_timestamp: None,
3028                chain: chain_identifier,
3029                nonce,
3030            },
3031        })
3032    }
3033
3034    pub fn message_version(&self) -> u64 {
3035        match self {
3036            TransactionData::V1(_) => 1,
3037        }
3038    }
3039
3040    pub fn execution_parts(&self) -> (TransactionKind, SuiAddress, GasData) {
3041        (self.kind().clone(), self.sender(), self.gas_data().clone())
3042    }
3043
3044    pub fn uses_randomness(&self) -> bool {
3045        self.kind()
3046            .shared_input_objects()
3047            .any(|obj| obj.id() == SUI_RANDOMNESS_STATE_OBJECT_ID)
3048    }
3049
3050    pub fn digest(&self) -> TransactionDigest {
3051        TransactionDigest::new(default_hash(self))
3052    }
3053}
3054
3055#[enum_dispatch]
3056pub trait TransactionDataAPI {
3057    fn sender(&self) -> SuiAddress;
3058
3059    // Note: this implies that SingleTransactionKind itself must be versioned, so that it can be
3060    // shared across versions. This will be easy to do since it is already an enum.
3061    fn kind(&self) -> &TransactionKind;
3062
3063    // Used by programmable_transaction_builder
3064    fn kind_mut(&mut self) -> &mut TransactionKind;
3065
3066    // kind is moved out of often enough that this is worth it to special case.
3067    fn into_kind(self) -> TransactionKind;
3068
3069    /// Transaction signer and Gas owner
3070    fn required_signers(&self) -> NonEmpty<SuiAddress>;
3071
3072    fn gas_data(&self) -> &GasData;
3073
3074    fn gas_owner(&self) -> SuiAddress;
3075
3076    fn gas(&self) -> &[ObjectRef];
3077
3078    fn gas_price(&self) -> u64;
3079
3080    fn gas_budget(&self) -> u64;
3081
3082    fn expiration(&self) -> &TransactionExpiration;
3083
3084    fn expiration_mut(&mut self) -> &mut TransactionExpiration;
3085
3086    fn move_calls(&self) -> Vec<(usize, &ObjectID, &str, &str)>;
3087
3088    fn input_objects(&self) -> UserInputResult<Vec<InputObjectKind>>;
3089
3090    fn shared_input_objects(&self) -> Vec<SharedInputObject>;
3091
3092    fn receiving_objects(&self) -> Vec<ObjectRef>;
3093
3094    // Dependency (input, package & receiving) objects that already have a version,
3095    // and do not require version assignment from consensus.
3096    // Returns move objects, package objects and receiving objects.
3097    fn fastpath_dependency_objects(
3098        &self,
3099    ) -> UserInputResult<(Vec<ObjectRef>, Vec<ObjectID>, Vec<ObjectRef>)>;
3100
3101    /// Processes funds withdraws and returns a map from funds account object ID to (total
3102    /// reserved amount, type tag, owner address). This method aggregates all withdraw operations
3103    /// for the same account by merging their reservations. Each account object ID is derived from
3104    /// the owner address and the type parameter of each withdraw operation.
3105    ///
3106    /// This method is used at signing time, and can reject a transaction if it contains
3107    /// invalid reservations.
3108    fn process_funds_withdrawals_for_signing(
3109        &self,
3110        chain_identifier: ChainIdentifier,
3111        coin_resolver: &dyn CoinReservationResolverTrait,
3112    ) -> UserInputResult<BTreeMap<AccumulatorObjId, (u64, TypeTag, SuiAddress)>>;
3113
3114    /// Like `process_funds_withdrawals_for_signing`, but excludes the implicit gas payment
3115    /// withdrawal. This is used during gas selection estimation to avoid double-counting the
3116    /// gas budget when determining available address balance.
3117    fn process_funds_withdrawals_for_estimation(
3118        &self,
3119        chain_identifier: ChainIdentifier,
3120        coin_resolver: &dyn CoinReservationResolverTrait,
3121    ) -> UserInputResult<BTreeMap<AccumulatorObjId, (u64, TypeTag, SuiAddress)>>;
3122
3123    /// Like `process_funds_withdrawals_for_signing`, but must only be called on a certified
3124    /// transaction, i.e. one that is known to be valid.
3125    fn process_funds_withdrawals_for_execution(
3126        &self,
3127        chain_identifier: ChainIdentifier,
3128    ) -> BTreeMap<AccumulatorObjId, u64>;
3129
3130    /// Validates the declared funder, the spender, and the funds type of each
3131    /// `WithdrawFrom::SenderAllowance` against its loaded input object. Execution trusts the
3132    /// declared funder, which is immutable on the allowance. Policy checks live in Move.
3133    fn check_allowance_inputs(&self, input_objects: &InputObjects) -> UserInputResult<()>;
3134
3135    // A cheap way to quickly check if the transaction has funds withdraws.
3136    fn has_funds_withdrawals(&self) -> bool;
3137
3138    fn coin_reservation_obj_refs(
3139        &self,
3140        chain_identifier: ChainIdentifier,
3141    ) -> Vec<ParsedObjectRefWithdrawal>;
3142
3143    fn validity_check(&self, context: &TxValidityCheckContext<'_>) -> SuiResult;
3144
3145    /// Check if the transaction is compliant with sponsorship.
3146    fn check_sponsorship(&self) -> UserInputResult;
3147
3148    fn is_system_tx(&self) -> bool;
3149    fn is_genesis_tx(&self) -> bool;
3150
3151    /// returns true if the transaction is one that is specially sequenced to run at the very end
3152    /// of the epoch
3153    fn is_end_of_epoch_tx(&self) -> bool;
3154
3155    fn is_consensus_commit_prologue(&self) -> bool;
3156
3157    /// Check if the transaction is sponsored (namely gas owner != sender)
3158    fn is_sponsored_tx(&self) -> bool;
3159
3160    fn is_gas_paid_from_address_balance(&self) -> bool;
3161
3162    fn is_gasless_transaction(&self) -> bool;
3163
3164    fn sender_mut_for_testing(&mut self) -> &mut SuiAddress;
3165
3166    fn gas_data_mut(&mut self) -> &mut GasData;
3167
3168    // This should be used in testing only.
3169    fn expiration_mut_for_testing(&mut self) -> &mut TransactionExpiration;
3170}
3171
3172impl TransactionDataAPI for TransactionDataV1 {
3173    fn sender(&self) -> SuiAddress {
3174        self.sender
3175    }
3176
3177    fn kind(&self) -> &TransactionKind {
3178        &self.kind
3179    }
3180
3181    fn kind_mut(&mut self) -> &mut TransactionKind {
3182        &mut self.kind
3183    }
3184
3185    fn into_kind(self) -> TransactionKind {
3186        self.kind
3187    }
3188
3189    /// Transaction signer and Gas owner
3190    fn required_signers(&self) -> NonEmpty<SuiAddress> {
3191        let mut signers = nonempty![self.sender];
3192        if self.gas_owner() != self.sender {
3193            signers.push(self.gas_owner());
3194        }
3195        signers
3196    }
3197
3198    fn gas_data(&self) -> &GasData {
3199        &self.gas_data
3200    }
3201
3202    fn gas_owner(&self) -> SuiAddress {
3203        self.gas_data.owner
3204    }
3205
3206    fn gas(&self) -> &[ObjectRef] {
3207        &self.gas_data.payment
3208    }
3209
3210    fn gas_price(&self) -> u64 {
3211        self.gas_data.price
3212    }
3213
3214    fn gas_budget(&self) -> u64 {
3215        self.gas_data.budget
3216    }
3217
3218    fn expiration(&self) -> &TransactionExpiration {
3219        &self.expiration
3220    }
3221
3222    fn expiration_mut(&mut self) -> &mut TransactionExpiration {
3223        &mut self.expiration
3224    }
3225
3226    fn move_calls(&self) -> Vec<(usize, &ObjectID, &str, &str)> {
3227        self.kind.move_calls()
3228    }
3229
3230    fn input_objects(&self) -> UserInputResult<Vec<InputObjectKind>> {
3231        let mut inputs = self.kind.input_objects()?;
3232
3233        if !self.kind.is_system_tx() {
3234            inputs.extend(
3235                self.gas()
3236                    .iter()
3237                    .filter(|obj_ref| !ParsedDigest::is_coin_reservation_digest(&obj_ref.2))
3238                    .map(|obj_ref| InputObjectKind::ImmOrOwnedMoveObject(*obj_ref)),
3239            );
3240        }
3241        Ok(inputs)
3242    }
3243
3244    fn shared_input_objects(&self) -> Vec<SharedInputObject> {
3245        self.kind.shared_input_objects().collect()
3246    }
3247
3248    fn receiving_objects(&self) -> Vec<ObjectRef> {
3249        self.kind.receiving_objects()
3250    }
3251
3252    fn fastpath_dependency_objects(
3253        &self,
3254    ) -> UserInputResult<(Vec<ObjectRef>, Vec<ObjectID>, Vec<ObjectRef>)> {
3255        let mut move_objects = vec![];
3256        let mut packages = vec![];
3257        let mut receiving_objects = vec![];
3258        self.input_objects()?.iter().for_each(|o| match o {
3259            InputObjectKind::ImmOrOwnedMoveObject(object_ref) => {
3260                move_objects.push(*object_ref);
3261            }
3262            InputObjectKind::MovePackage(package_id) => {
3263                packages.push(*package_id);
3264            }
3265            InputObjectKind::SharedMoveObject { .. } => {}
3266        });
3267        self.receiving_objects().iter().for_each(|object_ref| {
3268            receiving_objects.push(*object_ref);
3269        });
3270        Ok((move_objects, packages, receiving_objects))
3271    }
3272
3273    fn process_funds_withdrawals_for_signing(
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, true)
3279    }
3280
3281    fn process_funds_withdrawals_for_estimation(
3282        &self,
3283        chain_identifier: ChainIdentifier,
3284        coin_resolver: &dyn CoinReservationResolverTrait,
3285    ) -> UserInputResult<BTreeMap<AccumulatorObjId, (u64, TypeTag, SuiAddress)>> {
3286        self.accumulate_funds_withdrawals(chain_identifier, coin_resolver, false)
3287    }
3288
3289    fn process_funds_withdrawals_for_execution(
3290        &self,
3291        chain_identifier: ChainIdentifier,
3292    ) -> BTreeMap<AccumulatorObjId, u64> {
3293        let mut withdraws: Vec<_> = self.get_funds_withdrawals().collect();
3294        withdraws.extend(self.get_funds_withdrawal_for_gas_payment());
3295
3296        // Accumulate all withdraws per account.
3297        let mut withdraw_map: BTreeMap<AccumulatorObjId, u64> = BTreeMap::new();
3298        for withdraw in withdraws {
3299            let reserved_amount = match &withdraw.reservation {
3300                Reservation::MaxAmountU64(amount) => {
3301                    assert!(*amount > 0, "verified in validity check");
3302                    *amount
3303                }
3304            };
3305
3306            let withdrawal_owner = withdraw.owner_for_withdrawal(self);
3307
3308            // unwrap checked at signing time
3309            let account_id =
3310                AccumulatorValue::get_field_id(withdrawal_owner, &withdraw.type_arg.to_type_tag())
3311                    .unwrap();
3312
3313            let value = withdraw_map.entry(account_id).or_default();
3314            // overflow checked at signing time
3315            *value = value.checked_add(reserved_amount).unwrap();
3316        }
3317
3318        // It is not necessarily possible to construct a FundsWithdrawalArg for coin reservations, because
3319        // the accumulator object may not exist any more. This is okay, as the scheduler will simply
3320        // cancel the transaction if there are no funds available.
3321        for obj in self.coin_reservation_obj_refs() {
3322            assert_reachable!("processing coin reservation withdrawal");
3323            // unwrap safe because of signing time checks
3324            let parsed = ParsedObjectRefWithdrawal::parse(&obj, chain_identifier).unwrap();
3325            let value = withdraw_map
3326                // new_unchecked is safe because we verify that this is a valid accumulator object id
3327                // at signing time
3328                // The underlying object may have been deleted by now - this is okay. We don't need type information
3329                // here, we only need the accumulator object id.
3330                .entry(AccumulatorObjId::new_unchecked(parsed.unmasked_object_id))
3331                .or_default();
3332            // overflow checked at signing time
3333            *value = value.checked_add(parsed.reservation_amount()).unwrap();
3334        }
3335
3336        withdraw_map
3337    }
3338
3339    fn check_allowance_inputs(&self, input_objects: &InputObjects) -> UserInputResult<()> {
3340        let allowance_withdrawals: Vec<_> = self
3341            .get_funds_withdrawals()
3342            .filter_map(|w| match w.withdraw_from {
3343                WithdrawFrom::SenderAllowance { funder, allowance } => {
3344                    Some((funder, allowance, w.type_arg.to_type_tag()))
3345                }
3346                _ => None,
3347            })
3348            .collect();
3349        if allowance_withdrawals.is_empty() {
3350            return Ok(());
3351        }
3352        // An allowance must be among the tx's inputs, or it fails to resolve here.
3353        let objects_by_id: BTreeMap<ObjectID, &Object> = input_objects
3354            .iter()
3355            .filter_map(|input| Some((input.id(), input.as_object()?)))
3356            .collect();
3357        // Two withdrawals may source the same allowance with different declared funders
3358        // (and should be rejected).
3359        let mut resolved_allowances: BTreeMap<ObjectID, ResolvedAllowance> = BTreeMap::new();
3360        for (specified_funder, allowance, requested_funds_type) in allowance_withdrawals {
3361            let resolved: &ResolvedAllowance = match resolved_allowances.entry(allowance) {
3362                Entry::Occupied(entry) => entry.into_mut(),
3363                Entry::Vacant(entry) => {
3364                    let object = objects_by_id.get(&allowance).ok_or_else(|| {
3365                        UserInputError::InvalidWithdrawReservation {
3366                            error: format!(
3367                                "Specified allowance {allowance} not found among the tx inputs"
3368                            ),
3369                        }
3370                    })?;
3371                    entry.insert(parse_allowance_object(object)?)
3372                }
3373            };
3374            if resolved.funder != specified_funder {
3375                return Err(UserInputError::InvalidWithdrawReservation {
3376                    error: format!(
3377                        "Specified funder {specified_funder} does not match the funder of \
3378                        allowance {allowance}"
3379                    ),
3380                });
3381            }
3382            if resolved.spender != Some(self.sender()) {
3383                return Err(UserInputError::InvalidWithdrawReservation {
3384                    error: format!(
3385                        "Transaction sender is not the spender of allowance {allowance}"
3386                    ),
3387                });
3388            }
3389            if resolved.funds_type != requested_funds_type {
3390                return Err(UserInputError::InvalidWithdrawReservation {
3391                    error: format!(
3392                        "Allowance {allowance} is for {}, not {requested_funds_type}",
3393                        resolved.funds_type
3394                    ),
3395                });
3396            }
3397        }
3398        Ok(())
3399    }
3400
3401    fn has_funds_withdrawals(&self) -> bool {
3402        if self.is_gas_paid_from_address_balance() && self.gas_data().budget > 0 {
3403            return true;
3404        }
3405        if let TransactionKind::ProgrammableTransaction(pt) = &self.kind {
3406            for input in &pt.inputs {
3407                if matches!(input, CallArg::FundsWithdrawal(_)) {
3408                    return true;
3409                }
3410            }
3411        }
3412        if self.coin_reservation_obj_refs().next().is_some() {
3413            return true;
3414        }
3415        false
3416    }
3417
3418    fn coin_reservation_obj_refs(
3419        &self,
3420        chain_identifier: ChainIdentifier,
3421    ) -> Vec<ParsedObjectRefWithdrawal> {
3422        self.coin_reservation_obj_refs()
3423            .filter_map(|obj_ref| ParsedObjectRefWithdrawal::parse(&obj_ref, chain_identifier))
3424            .collect()
3425    }
3426
3427    fn validity_check(&self, context: &TxValidityCheckContext<'_>) -> SuiResult {
3428        let config = context.config;
3429
3430        // Checks to see if the transaction has expired
3431        match self.expiration() {
3432            TransactionExpiration::None => (), // always valid
3433            TransactionExpiration::Epoch(max_epoch) => {
3434                if context.epoch > *max_epoch {
3435                    return Err(SuiErrorKind::TransactionExpired.into());
3436                }
3437            }
3438            TransactionExpiration::ValidDuring {
3439                min_epoch,
3440                max_epoch,
3441                min_timestamp,
3442                max_timestamp,
3443                chain,
3444                ..
3445            }
3446            | TransactionExpiration::Validity {
3447                min_epoch,
3448                max_epoch,
3449                min_timestamp,
3450                max_timestamp,
3451                chain,
3452                ..
3453            } => {
3454                // The variant itself is gated regardless of whether its proposer set is usable,
3455                // so that a transaction accepted after the upgrade cannot be accepted before it.
3456                if matches!(self.expiration(), TransactionExpiration::Validity { .. }) {
3457                    fp_ensure!(
3458                        config.allowed_proposers(),
3459                        UserInputError::Unsupported(
3460                            "Restricting the proposers of a transaction is not supported"
3461                                .to_string(),
3462                        )
3463                        .into()
3464                    );
3465                }
3466
3467                // A proposer set recorded for another epoch is ignored, so there is nothing to
3468                // check: the transaction is treated as if it named no proposers at all.
3469                if let Some(allowed_proposers) = self.expiration().allowed_proposers(context.epoch)
3470                {
3471                    let proposers = &allowed_proposers.proposers;
3472
3473                    // SIP-45: submitting the same transaction to several proposers amplifies its
3474                    // consensus cost, which must be paid for with a raised gas price. Sizing the
3475                    // proposer set is the sender's declaration of how much it intends to amplify.
3476                    // No gas price buys more proposers than there are validators.
3477                    //
3478                    // Checked first, so that everything below walks a bounded list.
3479                    let max_proposers =
3480                        paid_proposer_count(self.gas_data.price, context.reference_gas_price)
3481                            .min(context.committee_size as u64);
3482                    fp_ensure!(
3483                        proposers.len() as u64 <= max_proposers,
3484                        UserInputError::InvalidExpiration {
3485                            error: format!(
3486                                "allowed_proposers has {} entries, but at most {max_proposers} \
3487                                 are permitted with gas price {}, reference gas price {}, and a \
3488                                 committee of {}",
3489                                proposers.len(),
3490                                self.gas_data.price,
3491                                context.reference_gas_price,
3492                                context.committee_size,
3493                            ),
3494                        }
3495                        .into()
3496                    );
3497
3498                    // Canonical encoding: a proposer set cannot be padded with duplicates, and
3499                    // lookups can rely on the ordering.
3500                    fp_ensure!(
3501                        proposers.iter().is_sorted_by(|a, b| a < b),
3502                        UserInputError::InvalidExpiration {
3503                            error: "allowed_proposers must be strictly increasing".to_string(),
3504                        }
3505                        .into()
3506                    );
3507
3508                    // An out-of-range index names no one, so it can only make the transaction
3509                    // unproposable.
3510                    if let Some(out_of_range) =
3511                        proposers.iter().find(|i| **i >= context.committee_size)
3512                    {
3513                        return Err(UserInputError::InvalidExpiration {
3514                            error: format!(
3515                                "allowed_proposers contains index {out_of_range}, but the \
3516                                 committee has {} members",
3517                                context.committee_size,
3518                            ),
3519                        }
3520                        .into());
3521                    }
3522                }
3523
3524                if min_timestamp.is_some() || max_timestamp.is_some() {
3525                    return Err(UserInputError::Unsupported(
3526                        "Timestamp-based transaction expiration is not yet supported".to_string(),
3527                    )
3528                    .into());
3529                }
3530
3531                // Legacy behavior: If a validity window is present, it must have either one- or
3532                // two-epoch validity, even if the transaction has other replay-protection.
3533                // New behavior: any epoch range can be specified. Replay protection is enforced
3534                // by sui_transaction_checks::check_replay_protection.
3535                match (min_epoch, max_epoch) {
3536                    _ if config.relax_valid_during_for_owned_inputs() => (),
3537                    (Some(min), Some(max)) => {
3538                        if config.enable_multi_epoch_transaction_expiration() {
3539                            if !(*max == *min || *max == min.saturating_add(1)) {
3540                                return Err(UserInputError::Unsupported(
3541                                    "max_epoch must be at most min_epoch + 1".to_string(),
3542                                )
3543                                .into());
3544                            }
3545                        } else if min != max {
3546                            return Err(UserInputError::Unsupported(
3547                                "min_epoch must equal max_epoch".to_string(),
3548                            )
3549                            .into());
3550                        }
3551                    }
3552                    _ => {
3553                        return Err(UserInputError::Unsupported(
3554                            "Both min_epoch and max_epoch must be specified".to_string(),
3555                        )
3556                        .into());
3557                    }
3558                }
3559
3560                if *chain != context.chain_identifier {
3561                    return Err(UserInputError::InvalidChainId {
3562                        provided: format!("{:?}", chain),
3563                        expected: format!("{:?}", context.chain_identifier),
3564                    }
3565                    .into());
3566                }
3567
3568                if let Some(min) = min_epoch
3569                    && context.epoch < *min
3570                {
3571                    return Err(SuiErrorKind::TransactionExpired.into());
3572                }
3573                if let Some(max) = max_epoch
3574                    && context.epoch > *max
3575                {
3576                    return Err(SuiErrorKind::TransactionExpired.into());
3577                }
3578            }
3579        }
3580
3581        if self.has_funds_withdrawals() {
3582            // TODO: this check is incorrect, we should only require this if there are zero owned
3583            // inputs
3584            fp_ensure!(
3585                !self.gas().is_empty() || config.enable_address_balance_gas_payments(),
3586                UserInputError::MissingGasPayment.into()
3587            );
3588
3589            fp_ensure!(
3590                config.enable_accumulators(),
3591                UserInputError::Unsupported("Address balance withdraw is not enabled".to_string())
3592                    .into()
3593            );
3594
3595            // TODO(address-balances): Use a protocol config parameter for max_withdraws.
3596            let max_withdraws = 10;
3597            let mut num_reservations = 0;
3598
3599            for withdraw in self.kind.get_funds_withdrawals() {
3600                num_reservations += 1;
3601                match withdraw.withdraw_from {
3602                    WithdrawFrom::Sender => (),
3603                    WithdrawFrom::Sponsor => {
3604                        return Err(UserInputError::InvalidWithdrawReservation {
3605                            error: "Explicit sponsor withdrawals are not yet supported".to_string(),
3606                        }
3607                        .into());
3608                    }
3609                    // The allowance itself is checked after input loading, in
3610                    // `check_allowance_inputs`.
3611                    WithdrawFrom::SenderAllowance { .. } => {
3612                        fp_ensure!(
3613                            config.enable_allowances(),
3614                            UserInputError::Unsupported(
3615                                "Allowance withdrawals are not enabled".to_string()
3616                            )
3617                            .into()
3618                        );
3619                    }
3620                }
3621
3622                match withdraw.reservation {
3623                    Reservation::MaxAmountU64(amount) => {
3624                        fp_ensure!(
3625                            amount > 0,
3626                            UserInputError::InvalidWithdrawReservation {
3627                                error: "Balance withdraw reservation amount must be non-zero"
3628                                    .to_string(),
3629                            }
3630                            .into()
3631                        );
3632                    }
3633                };
3634            }
3635
3636            for parsed in self.parsed_coin_reservations(context.chain_identifier) {
3637                num_reservations += 1;
3638                // coin reservations are valid for the current and next epoch, just as transactions that
3639                // specify a TransactionDuring are.
3640                // TODO: this check can be skipped if the transaction contains any address owned inputs.
3641                if parsed.epoch_id() != context.epoch && parsed.epoch_id() + 1 != context.epoch {
3642                    return Err(SuiErrorKind::TransactionExpired.into());
3643                }
3644                if parsed.reservation_amount() == 0 {
3645                    return Err(UserInputError::InvalidWithdrawReservation {
3646                        error: "Balance withdraw reservation amount must be non-zero".to_string(),
3647                    }
3648                    .into());
3649                }
3650            }
3651
3652            // Count implicit gas budget as a withdrawal when gas is paid from address balance
3653            if config.enable_address_balance_gas_payments()
3654                && self.is_gas_paid_from_address_balance()
3655            {
3656                num_reservations += 1;
3657            }
3658
3659            fp_ensure!(
3660                num_reservations <= max_withdraws,
3661                UserInputError::InvalidWithdrawReservation {
3662                    error: format!(
3663                        "Maximum number of balance withdraw reservations is {max_withdraws}"
3664                    ),
3665                }
3666                .into()
3667            );
3668        }
3669
3670        if config.enable_accumulators()
3671            && config.enable_address_balance_gas_payments()
3672            && self.is_gas_paid_from_address_balance()
3673        {
3674            if config.address_balance_gas_reject_gas_coin_arg()
3675                && let TransactionKind::ProgrammableTransaction(pt) = &self.kind
3676            {
3677                fp_ensure!(
3678                    !pt.commands.iter().any(|cmd| cmd.is_gas_coin_used()),
3679                    UserInputError::Unsupported(
3680                        "Argument::GasCoin is not supported with address balance gas payments"
3681                            .to_string(),
3682                    )
3683                    .into()
3684                );
3685            }
3686
3687            let is_gasless = config.enable_gasless() && self.is_gasless_transaction();
3688            if config.address_balance_gas_check_rgp_at_signing() && !is_gasless {
3689                fp_ensure!(
3690                    self.gas_data.price >= context.reference_gas_price,
3691                    UserInputError::GasPriceUnderRGP {
3692                        gas_price: self.gas_data.price,
3693                        reference_gas_price: context.reference_gas_price,
3694                    }
3695                    .into()
3696                );
3697            }
3698
3699            // Legacy behavior: when paying gas from address balance, we require ValidDuring expiration
3700            // even if the transaction has other replay-protected inputs.
3701            // New behavior: the check is done in `check_replay_protection` in sui-transaction-checks,
3702            // which only requires two-epoch ValidDuring if there are no replay-protected inputs.
3703            if !config.relax_valid_during_for_owned_inputs() {
3704                if matches!(self.expiration(), TransactionExpiration::None) {
3705                    // To avoid changing error behavior unnecessarily, we flag this as a missing gas payment error
3706                    // instead of a missing expiration error.
3707                    return Err(UserInputError::MissingGasPayment.into());
3708                }
3709
3710                if !self.expiration().is_replay_protected() {
3711                    return Err(UserInputError::InvalidExpiration {
3712                        error: "Address balance gas payments require ValidDuring expiration"
3713                            .to_string(),
3714                    }
3715                    .into());
3716                }
3717            }
3718        } else {
3719            fp_ensure!(
3720                !self.gas().is_empty(),
3721                UserInputError::MissingGasPayment.into()
3722            );
3723        }
3724
3725        let gas_len = self.gas().len();
3726        let max_gas_objects = config.max_gas_payment_objects() as usize;
3727
3728        let within_limit = if config.correct_gas_payment_limit_check() {
3729            gas_len <= max_gas_objects
3730        } else {
3731            gas_len < max_gas_objects
3732        };
3733
3734        fp_ensure!(
3735            within_limit,
3736            UserInputError::SizeLimitExceeded {
3737                limit: "maximum number of gas payment objects".to_string(),
3738                value: config.max_gas_payment_objects().to_string()
3739            }
3740            .into()
3741        );
3742
3743        if !config.enable_coin_reservation_obj_refs() {
3744            for (_, _, gas_digest) in self.gas() {
3745                fp_ensure!(
3746                    !ParsedDigest::is_coin_reservation_digest(gas_digest),
3747                    UserInputError::GasObjectNotOwnedObject {
3748                        owner: Owner::AddressOwner(self.sender)
3749                    }
3750                    .into()
3751                );
3752            }
3753        } else {
3754            // When coin reservations are enabled, validate that gas coin reservations are for SUI,
3755            // and that they are owned by the sender. (Sponsorship via coin reservations is not supported.)
3756            let sui_accumulator_id =
3757                *AccumulatorValue::get_field_id(self.sender, &Balance::type_tag(GAS::type_tag()))?
3758                    .inner();
3759
3760            for gas_ref in self.gas() {
3761                if let Some(parsed) =
3762                    ParsedObjectRefWithdrawal::parse(gas_ref, context.chain_identifier)
3763                {
3764                    // Coin reservations draw from the sender's address balance, so they cannot
3765                    // be used in sponsored transactions where gas is paid by someone else.
3766                    fp_ensure!(
3767                        self.gas_owner() == self.sender,
3768                        UserInputError::GasObjectNotOwnedObject {
3769                            owner: Owner::AddressOwner(self.sender)
3770                        }
3771                        .into()
3772                    );
3773                    fp_ensure!(
3774                        parsed.unmasked_object_id == sui_accumulator_id,
3775                        UserInputError::GasObjectNotOwnedObject {
3776                            owner: Owner::AddressOwner(self.sender)
3777                        }
3778                        .into()
3779                    );
3780                }
3781            }
3782        }
3783
3784        if !self.is_system_tx() {
3785            fp_ensure!(
3786                !check_for_gas_price_too_high(config.gas_model_version())
3787                    || self.gas_data.price < config.max_gas_price(),
3788                UserInputError::GasPriceTooHigh {
3789                    max_gas_price: config.max_gas_price(),
3790                }
3791                .into()
3792            );
3793            let cost_table = SuiCostTable::new(config, self.gas_data.price);
3794
3795            fp_ensure!(
3796                self.gas_data.budget <= cost_table.max_gas_budget,
3797                UserInputError::GasBudgetTooHigh {
3798                    gas_budget: self.gas_data().budget,
3799                    max_budget: cost_table.max_gas_budget,
3800                }
3801                .into()
3802            );
3803            let is_gasless = config.enable_gasless() && self.is_gasless_transaction();
3804            if is_gasless {
3805                fp_ensure!(
3806                    self.gas_data.budget == 0,
3807                    UserInputError::Unsupported(
3808                        "gas_budget must be 0 for gasless transactions".to_string()
3809                    )
3810                    .into()
3811                );
3812            } else {
3813                fp_ensure!(
3814                    self.gas_data.budget >= cost_table.min_transaction_cost,
3815                    UserInputError::GasBudgetTooLow {
3816                        gas_budget: self.gas_data.budget,
3817                        min_budget: cost_table.min_transaction_cost,
3818                    }
3819                    .into()
3820                );
3821            }
3822        }
3823
3824        self.kind().validity_check(config)?;
3825
3826        if config.enable_gasless() && self.is_gasless_transaction() {
3827            let TransactionKind::ProgrammableTransaction(pt) = &self.kind else {
3828                debug_fatal!("gasless transaction is not a ProgrammableTransaction");
3829                return Err(UserInputError::Unsupported(
3830                    "Gasless transactions must be programmable transactions".to_string(),
3831                )
3832                .into());
3833            };
3834            pt.validate_gasless_transaction(config)?;
3835        }
3836
3837        self.check_sponsorship()?;
3838        Ok(())
3839    }
3840
3841    /// Check if the transaction is sponsored (namely gas owner != sender)
3842    fn is_sponsored_tx(&self) -> bool {
3843        self.gas_owner() != self.sender
3844    }
3845
3846    // Note: it is possible to pay gas from a coin reservation, which ultimately draws from
3847    // the address balance. This function still returns false in that case. In other words,
3848    // it indicates use of the first-class API for address balance gas payments, not the legacy API.
3849    fn is_gas_paid_from_address_balance(&self) -> bool {
3850        is_gas_paid_from_address_balance(&self.gas_data, &self.kind)
3851    }
3852
3853    fn is_gasless_transaction(&self) -> bool {
3854        is_gasless_transaction(&self.gas_data, &self.kind)
3855    }
3856
3857    /// Check if the transaction is compliant with sponsorship.
3858    fn check_sponsorship(&self) -> UserInputResult {
3859        // Not a sponsored transaction, nothing to check
3860        if self.gas_owner() == self.sender() {
3861            return Ok(());
3862        }
3863        if matches!(&self.kind, TransactionKind::ProgrammableTransaction(_)) {
3864            return Ok(());
3865        }
3866        Err(UserInputError::UnsupportedSponsoredTransactionKind)
3867    }
3868
3869    fn is_end_of_epoch_tx(&self) -> bool {
3870        matches!(
3871            self.kind,
3872            TransactionKind::ChangeEpoch(_) | TransactionKind::EndOfEpochTransaction(_)
3873        )
3874    }
3875
3876    fn is_consensus_commit_prologue(&self) -> bool {
3877        match &self.kind {
3878            TransactionKind::ConsensusCommitPrologue(_)
3879            | TransactionKind::ConsensusCommitPrologueV2(_)
3880            | TransactionKind::ConsensusCommitPrologueV3(_)
3881            | TransactionKind::ConsensusCommitPrologueV4(_) => true,
3882
3883            TransactionKind::ProgrammableTransaction(_)
3884            | TransactionKind::ProgrammableSystemTransaction(_)
3885            | TransactionKind::ChangeEpoch(_)
3886            | TransactionKind::Genesis(_)
3887            | TransactionKind::AuthenticatorStateUpdate(_)
3888            | TransactionKind::EndOfEpochTransaction(_)
3889            | TransactionKind::RandomnessStateUpdate(_) => false,
3890        }
3891    }
3892
3893    fn is_system_tx(&self) -> bool {
3894        self.kind.is_system_tx()
3895    }
3896
3897    fn is_genesis_tx(&self) -> bool {
3898        matches!(self.kind, TransactionKind::Genesis(_))
3899    }
3900
3901    fn sender_mut_for_testing(&mut self) -> &mut SuiAddress {
3902        &mut self.sender
3903    }
3904
3905    fn gas_data_mut(&mut self) -> &mut GasData {
3906        &mut self.gas_data
3907    }
3908
3909    fn expiration_mut_for_testing(&mut self) -> &mut TransactionExpiration {
3910        &mut self.expiration
3911    }
3912}
3913
3914impl TransactionDataV1 {
3915    fn accumulate_funds_withdrawals(
3916        &self,
3917        chain_identifier: ChainIdentifier,
3918        coin_resolver: &dyn CoinReservationResolverTrait,
3919        include_gas_payment: bool,
3920    ) -> UserInputResult<BTreeMap<AccumulatorObjId, (u64, TypeTag, SuiAddress)>> {
3921        let mut withdraws: Vec<_> = self.get_funds_withdrawals().collect();
3922
3923        for withdraw in self.parsed_coin_reservations(chain_identifier) {
3924            let withdrawal_arg =
3925                coin_resolver.resolve_funds_withdrawal(self.sender(), withdraw, None)?;
3926            withdraws.push(withdrawal_arg);
3927        }
3928
3929        if include_gas_payment {
3930            withdraws.extend(self.get_funds_withdrawal_for_gas_payment());
3931        }
3932
3933        let mut withdraw_map: BTreeMap<AccumulatorObjId, (u64, TypeTag, SuiAddress)> =
3934            BTreeMap::new();
3935        for withdraw in withdraws {
3936            let reserved_amount = match &withdraw.reservation {
3937                Reservation::MaxAmountU64(amount) => {
3938                    if *amount == 0 {
3939                        return Err(UserInputError::InvalidWithdrawReservation {
3940                            error: "Balance withdraw reservation amount must be non-zero"
3941                                .to_string(),
3942                        });
3943                    }
3944                    *amount
3945                }
3946            };
3947
3948            let account_address = withdraw.owner_for_withdrawal(self);
3949            let type_tag = withdraw.type_arg.to_type_tag();
3950            let account_id =
3951                AccumulatorValue::get_field_id(account_address, &type_tag).map_err(|e| {
3952                    UserInputError::InvalidWithdrawReservation {
3953                        error: e.to_string(),
3954                    }
3955                })?;
3956
3957            let (current_amount, _, _) = withdraw_map
3958                .entry(account_id)
3959                .or_insert_with(|| (0, type_tag, account_address));
3960            *current_amount = current_amount.checked_add(reserved_amount).ok_or(
3961                UserInputError::InvalidWithdrawReservation {
3962                    error: "Balance withdraw reservation overflow".to_string(),
3963                },
3964            )?;
3965        }
3966
3967        Ok(withdraw_map)
3968    }
3969
3970    fn get_funds_withdrawal_for_gas_payment(&self) -> Option<FundsWithdrawalArg> {
3971        if self.is_gas_paid_from_address_balance() && self.gas_data().budget > 0 {
3972            Some(if self.sender() != self.gas_owner() {
3973                FundsWithdrawalArg::balance_from_sponsor(self.gas_data().budget, GAS::type_tag())
3974            } else {
3975                FundsWithdrawalArg::balance_from_sender(self.gas_data().budget, GAS::type_tag())
3976            })
3977        } else {
3978            None
3979        }
3980    }
3981
3982    fn get_funds_withdrawals(&self) -> impl Iterator<Item = FundsWithdrawalArg> + '_ {
3983        self.kind.get_funds_withdrawals().cloned()
3984    }
3985
3986    fn coin_reservation_obj_refs(&self) -> impl Iterator<Item = ObjectRef> {
3987        self.kind
3988            .get_coin_reservation_obj_refs()
3989            .chain(self.gas().iter().filter_map(|gas_ref| {
3990                if ParsedDigest::is_coin_reservation_digest(&gas_ref.2) {
3991                    Some(*gas_ref)
3992                } else {
3993                    None
3994                }
3995            }))
3996    }
3997
3998    fn parsed_coin_reservations(
3999        &self,
4000        chain_identifier: ChainIdentifier,
4001    ) -> impl Iterator<Item = ParsedObjectRefWithdrawal> {
4002        self.coin_reservation_obj_refs().map(move |obj_ref| {
4003            ParsedObjectRefWithdrawal::parse(&obj_ref, chain_identifier).unwrap()
4004        })
4005    }
4006}
4007
4008pub struct TxValidityCheckContext<'a> {
4009    pub config: &'a ProtocolConfig,
4010    pub epoch: EpochId,
4011    pub chain_identifier: ChainIdentifier,
4012    pub reference_gas_price: u64,
4013    /// Number of validators in the current epoch's committee, used to bound committee indices.
4014    pub committee_size: u32,
4015}
4016
4017impl<'a> TxValidityCheckContext<'a> {
4018    pub fn from_cfg_for_testing(config: &'a ProtocolConfig) -> Self {
4019        Self {
4020            config,
4021            epoch: 0,
4022            chain_identifier: ChainIdentifier::default(),
4023            reference_gas_price: 1000,
4024            committee_size: 4,
4025        }
4026    }
4027}
4028
4029#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
4030pub struct SenderSignedData(SizeOneVec<SenderSignedTransaction>);
4031
4032#[derive(Debug, Clone, PartialEq, Eq, Hash)]
4033pub struct SenderSignedTransaction {
4034    pub intent_message: IntentMessage<TransactionData>,
4035    /// A list of signatures signed by all transaction participants.
4036    /// 1. non participant signature must not be present.
4037    /// 2. signature order does not matter.
4038    pub tx_signatures: Vec<GenericSignature>,
4039}
4040
4041impl Serialize for SenderSignedTransaction {
4042    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
4043    where
4044        S: serde::Serializer,
4045    {
4046        #[derive(Serialize)]
4047        #[serde(rename = "SenderSignedTransaction")]
4048        struct SignedTxn<'a> {
4049            intent_message: &'a IntentMessage<TransactionData>,
4050            tx_signatures: &'a Vec<GenericSignature>,
4051        }
4052
4053        if self.intent_message().intent != Intent::sui_transaction() {
4054            return Err(serde::ser::Error::custom("invalid Intent for Transaction"));
4055        }
4056
4057        let txn = SignedTxn {
4058            intent_message: self.intent_message(),
4059            tx_signatures: &self.tx_signatures,
4060        };
4061        txn.serialize(serializer)
4062    }
4063}
4064
4065impl<'de> Deserialize<'de> for SenderSignedTransaction {
4066    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
4067    where
4068        D: serde::Deserializer<'de>,
4069    {
4070        #[derive(Deserialize)]
4071        #[serde(rename = "SenderSignedTransaction")]
4072        struct SignedTxn {
4073            intent_message: IntentMessage<TransactionData>,
4074            tx_signatures: Vec<GenericSignature>,
4075        }
4076
4077        let SignedTxn {
4078            intent_message,
4079            tx_signatures,
4080        } = Deserialize::deserialize(deserializer)?;
4081
4082        if intent_message.intent != Intent::sui_transaction() {
4083            return Err(serde::de::Error::custom("invalid Intent for Transaction"));
4084        }
4085
4086        Ok(Self {
4087            intent_message,
4088            tx_signatures,
4089        })
4090    }
4091}
4092
4093impl SenderSignedTransaction {
4094    /// Returns a mapping from signer address to the signature and its index in `tx_signatures`.
4095    pub(crate) fn get_signer_sig_mapping(
4096        &self,
4097        verify_legacy_zklogin_address: bool,
4098    ) -> SuiResult<BTreeMap<SuiAddress, (u8, &GenericSignature)>> {
4099        let mut mapping = BTreeMap::new();
4100        for (idx, sig) in self.tx_signatures.iter().enumerate() {
4101            if verify_legacy_zklogin_address && let GenericSignature::ZkLoginAuthenticator(z) = sig
4102            {
4103                // Try deriving the address from the legacy padded way.
4104                mapping.insert(SuiAddress::try_from_padded(&z.inputs)?, (idx as u8, sig));
4105            }
4106            let address = sig.try_into()?;
4107            mapping.insert(address, (idx as u8, sig));
4108        }
4109        Ok(mapping)
4110    }
4111
4112    pub fn intent_message(&self) -> &IntentMessage<TransactionData> {
4113        &self.intent_message
4114    }
4115}
4116
4117/// Any ML-DSA-65 part of a multisig is a BCS variant older binaries cannot
4118/// decode, so it stays out of blocks until the flag is on, even when only
4119/// classical members signed.
4120fn mldsa65_multisig_unsupported() -> SuiError {
4121    SuiErrorKind::UserInputError {
4122        error: UserInputError::Unsupported(
4123            "ML-DSA-65 multisig members are not enabled on this network".to_string(),
4124        ),
4125    }
4126    .into()
4127}
4128
4129impl SenderSignedData {
4130    pub fn new(tx_data: TransactionData, tx_signatures: Vec<GenericSignature>) -> Self {
4131        Self(SizeOneVec::new(SenderSignedTransaction {
4132            intent_message: IntentMessage::new(Intent::sui_transaction(), tx_data),
4133            tx_signatures,
4134        }))
4135    }
4136
4137    pub fn new_from_sender_signature(tx_data: TransactionData, tx_signature: Signature) -> Self {
4138        Self(SizeOneVec::new(SenderSignedTransaction {
4139            intent_message: IntentMessage::new(Intent::sui_transaction(), tx_data),
4140            tx_signatures: vec![tx_signature.into()],
4141        }))
4142    }
4143
4144    pub fn inner(&self) -> &SenderSignedTransaction {
4145        self.0.element()
4146    }
4147
4148    pub fn into_inner(self) -> SenderSignedTransaction {
4149        self.0.into_inner()
4150    }
4151
4152    pub fn inner_mut(&mut self) -> &mut SenderSignedTransaction {
4153        self.0.element_mut()
4154    }
4155
4156    // This function does not check validity of the signature
4157    // or perform any de-dup checks.
4158    pub fn add_signature(&mut self, new_signature: Signature) {
4159        self.inner_mut().tx_signatures.push(new_signature.into());
4160    }
4161
4162    pub(crate) fn get_signer_sig_mapping(
4163        &self,
4164        verify_legacy_zklogin_address: bool,
4165    ) -> SuiResult<BTreeMap<SuiAddress, (u8, &GenericSignature)>> {
4166        self.inner()
4167            .get_signer_sig_mapping(verify_legacy_zklogin_address)
4168    }
4169
4170    pub fn transaction_data(&self) -> &TransactionData {
4171        &self.intent_message().value
4172    }
4173
4174    pub fn intent_message(&self) -> &IntentMessage<TransactionData> {
4175        self.inner().intent_message()
4176    }
4177
4178    pub fn tx_signatures(&self) -> &[GenericSignature] {
4179        &self.inner().tx_signatures
4180    }
4181
4182    pub fn has_zklogin_sig(&self) -> bool {
4183        self.tx_signatures().iter().any(|sig| sig.is_zklogin())
4184    }
4185
4186    pub fn has_upgraded_multisig(&self) -> bool {
4187        self.tx_signatures()
4188            .iter()
4189            .any(|sig| sig.is_upgraded_multisig())
4190    }
4191
4192    #[cfg(test)]
4193    pub fn intent_message_mut_for_testing(&mut self) -> &mut IntentMessage<TransactionData> {
4194        &mut self.inner_mut().intent_message
4195    }
4196
4197    // used cross-crate, so cannot be #[cfg(test)]
4198    pub fn tx_signatures_mut_for_testing(&mut self) -> &mut Vec<GenericSignature> {
4199        &mut self.inner_mut().tx_signatures
4200    }
4201
4202    /// Includes alias_versions to ensure cache invalidation when aliases change.
4203    pub fn full_message_digest_with_alias_versions(
4204        &self,
4205        alias_versions: &Vec<(SuiAddress, Option<SequenceNumber>)>,
4206    ) -> SenderSignedDataDigest {
4207        let mut digest = DefaultHash::default();
4208        bcs::serialize_into(&mut digest, self).expect("serialization should not fail");
4209        bcs::serialize_into(&mut digest, alias_versions).expect("serialization should not fail");
4210        let hash = digest.finalize();
4211        SenderSignedDataDigest::new(hash.into())
4212    }
4213
4214    pub fn serialized_size(&self) -> SuiResult<usize> {
4215        bcs::serialized_size(self).map_err(|e| {
4216            SuiErrorKind::TransactionSerializationError {
4217                error: e.to_string(),
4218            }
4219            .into()
4220        })
4221    }
4222
4223    fn check_user_signature_protocol_compatibility(&self, config: &ProtocolConfig) -> SuiResult {
4224        for sig in &self.inner().tx_signatures {
4225            match sig {
4226                GenericSignature::MultiSig(multisig) => {
4227                    if !config.upgraded_multisig_supported() {
4228                        return Err(SuiErrorKind::UserInputError {
4229                            error: UserInputError::Unsupported(
4230                                "upgraded multisig format not enabled on this network".to_string(),
4231                            ),
4232                        }
4233                        .into());
4234                    }
4235                    if multisig.uses_mldsa65() && !config.mldsa65_auth() {
4236                        return Err(mldsa65_multisig_unsupported());
4237                    }
4238                }
4239                GenericSignature::ZkLoginAuthenticator(_) => {
4240                    if !config.zklogin_auth() {
4241                        return Err(SuiErrorKind::UserInputError {
4242                            error: UserInputError::Unsupported(
4243                                "zklogin is not enabled on this network".to_string(),
4244                            ),
4245                        }
4246                        .into());
4247                    }
4248                }
4249                GenericSignature::PasskeyAuthenticator(_) => {
4250                    if !config.passkey_auth() {
4251                        return Err(SuiErrorKind::UserInputError {
4252                            error: UserInputError::Unsupported(
4253                                "passkey is not enabled on this network".to_string(),
4254                            ),
4255                        }
4256                        .into());
4257                    }
4258                }
4259                GenericSignature::Signature(_) => {
4260                    // Only ML-DSA-65 is gated; the classical schemes predate feature flags.
4261                    if sig.is_mldsa65() && !config.mldsa65_auth() {
4262                        return Err(SuiErrorKind::UserInputError {
4263                            error: UserInputError::Unsupported(
4264                                "ML-DSA-65 signatures are not enabled on this network".to_string(),
4265                            ),
4266                        }
4267                        .into());
4268                    }
4269                }
4270                GenericSignature::MultiSigLegacy(multisig) => {
4271                    if multisig.uses_mldsa65() && !config.mldsa65_auth() {
4272                        return Err(mldsa65_multisig_unsupported());
4273                    }
4274                }
4275            }
4276        }
4277
4278        Ok(())
4279    }
4280
4281    /// Validate untrusted user transaction, including its size, input count, command count, etc.
4282    /// Returns the certificate serialised bytes size.
4283    pub fn validity_check(&self, context: &TxValidityCheckContext<'_>) -> Result<usize, SuiError> {
4284        // Check that the features used by the user signatures are enabled on the network.
4285        self.check_user_signature_protocol_compatibility(context.config)?;
4286
4287        // TODO: The following checks can be moved to TransactionData, if we pass context into it.
4288
4289        // CRITICAL!!
4290        // Users cannot send system transactions.
4291        let tx_data = &self.transaction_data();
4292        fp_ensure!(
4293            !tx_data.is_system_tx(),
4294            SuiErrorKind::UserInputError {
4295                error: UserInputError::Unsupported(
4296                    "SenderSignedData must not contain system transaction".to_string()
4297                )
4298            }
4299            .into()
4300        );
4301
4302        // Enforce overall transaction size limit.
4303        let tx_size = self.serialized_size()?;
4304        let max_tx_size_bytes = context.config.max_tx_size_bytes();
4305        fp_ensure!(
4306            tx_size as u64 <= max_tx_size_bytes,
4307            SuiErrorKind::UserInputError {
4308                error: UserInputError::SizeLimitExceeded {
4309                    limit: format!(
4310                        "serialized transaction size exceeded maximum of {max_tx_size_bytes}"
4311                    ),
4312                    value: tx_size.to_string(),
4313                }
4314            }
4315            .into()
4316        );
4317
4318        if context.config.enable_gasless() && tx_data.is_gasless_transaction() {
4319            let gasless_max = context.config.get_gasless_max_tx_size_bytes();
4320            fp_ensure!(
4321                tx_size as u64 <= gasless_max,
4322                SuiErrorKind::UserInputError {
4323                    error: UserInputError::SizeLimitExceeded {
4324                        limit: format!(
4325                            "serialized gasless transaction size exceeded maximum of {gasless_max}"
4326                        ),
4327                        value: tx_size.to_string(),
4328                    }
4329                }
4330                .into()
4331            );
4332        }
4333
4334        tx_data.validity_check(context)?;
4335
4336        Ok(tx_size)
4337    }
4338}
4339
4340impl Message for SenderSignedData {
4341    type DigestType = TransactionDigest;
4342    const SCOPE: IntentScope = IntentScope::SenderSignedTransaction;
4343
4344    /// Computes the tx digest that encodes the Rust type prefix from Signable trait.
4345    fn digest(&self) -> Self::DigestType {
4346        self.intent_message().value.digest()
4347    }
4348}
4349
4350impl<S> Envelope<SenderSignedData, S> {
4351    pub fn sender_address(&self) -> SuiAddress {
4352        self.data().intent_message().value.sender()
4353    }
4354
4355    pub fn gas_owner(&self) -> SuiAddress {
4356        self.data().intent_message().value.gas_owner()
4357    }
4358
4359    pub fn gas(&self) -> &[ObjectRef] {
4360        self.data().intent_message().value.gas()
4361    }
4362
4363    pub fn is_consensus_tx(&self) -> bool {
4364        self.transaction_data().has_funds_withdrawals()
4365            || self.shared_input_objects().next().is_some()
4366    }
4367
4368    pub fn shared_input_objects(&self) -> impl Iterator<Item = SharedInputObject> + '_ {
4369        self.data()
4370            .inner()
4371            .intent_message
4372            .value
4373            .shared_input_objects()
4374            .into_iter()
4375    }
4376
4377    // Returns the primary key for this transaction.
4378    pub fn key(&self) -> TransactionKey {
4379        match &self.data().intent_message().value.kind() {
4380            TransactionKind::RandomnessStateUpdate(rsu) => {
4381                TransactionKey::RandomnessRound(rsu.epoch, rsu.randomness_round)
4382            }
4383            _ => TransactionKey::Digest(*self.digest()),
4384        }
4385    }
4386
4387    // Returns non-Digest keys that could be used to refer to this transaction.
4388    //
4389    // At the moment this returns a single Option for efficiency, but if more key types are added,
4390    // the return type could change to Vec<TransactionKey>.
4391    pub fn non_digest_key(&self) -> Option<TransactionKey> {
4392        match &self.data().intent_message().value.kind() {
4393            TransactionKind::RandomnessStateUpdate(rsu) => Some(TransactionKey::RandomnessRound(
4394                rsu.epoch,
4395                rsu.randomness_round,
4396            )),
4397            _ => None,
4398        }
4399    }
4400
4401    pub fn is_system_tx(&self) -> bool {
4402        self.data().intent_message().value.is_system_tx()
4403    }
4404
4405    pub fn is_sponsored_tx(&self) -> bool {
4406        self.data().intent_message().value.is_sponsored_tx()
4407    }
4408}
4409
4410impl Transaction {
4411    pub fn from_data_and_signer(
4412        data: TransactionData,
4413        signers: Vec<&dyn Signer<Signature>>,
4414    ) -> Self {
4415        let signatures = {
4416            let intent_msg = IntentMessage::new(Intent::sui_transaction(), &data);
4417            signers
4418                .into_iter()
4419                .map(|s| Signature::new_secure(&intent_msg, s))
4420                .collect()
4421        };
4422        Self::from_data(data, signatures)
4423    }
4424
4425    // TODO: Rename this function and above to make it clearer.
4426    pub fn from_data(data: TransactionData, signatures: Vec<Signature>) -> Self {
4427        Self::from_generic_sig_data(data, signatures.into_iter().map(|s| s.into()).collect())
4428    }
4429
4430    pub fn signature_from_signer(
4431        data: TransactionData,
4432        intent: Intent,
4433        signer: &dyn Signer<Signature>,
4434    ) -> Signature {
4435        let intent_msg = IntentMessage::new(intent, data);
4436        Signature::new_secure(&intent_msg, signer)
4437    }
4438
4439    pub fn from_generic_sig_data(data: TransactionData, signatures: Vec<GenericSignature>) -> Self {
4440        Self::new(SenderSignedData::new(data, signatures))
4441    }
4442
4443    /// Returns the Base64 encoded tx_bytes
4444    /// and a list of Base64 encoded [enum GenericSignature].
4445    pub fn to_tx_bytes_and_signatures(&self) -> (Base64, Vec<Base64>) {
4446        (
4447            Base64::from_bytes(&bcs::to_bytes(&self.data().intent_message().value).unwrap()),
4448            self.data()
4449                .inner()
4450                .tx_signatures
4451                .iter()
4452                .map(|s| Base64::from_bytes(s.as_ref()))
4453                .collect(),
4454        )
4455    }
4456}
4457
4458impl VerifiedTransaction {
4459    pub fn new_change_epoch(
4460        next_epoch: EpochId,
4461        protocol_version: ProtocolVersion,
4462        storage_charge: u64,
4463        computation_charge: u64,
4464        storage_rebate: u64,
4465        non_refundable_storage_fee: u64,
4466        epoch_start_timestamp_ms: u64,
4467        system_packages: Vec<(SequenceNumber, Vec<Vec<u8>>, Vec<ObjectID>)>,
4468    ) -> Self {
4469        ChangeEpoch {
4470            epoch: next_epoch,
4471            protocol_version,
4472            storage_charge,
4473            computation_charge,
4474            storage_rebate,
4475            non_refundable_storage_fee,
4476            epoch_start_timestamp_ms,
4477            system_packages,
4478        }
4479        .pipe(TransactionKind::ChangeEpoch)
4480        .pipe(Self::new_system_transaction)
4481    }
4482
4483    pub fn new_genesis_transaction(objects: Vec<GenesisObject>) -> Self {
4484        GenesisTransaction { objects }
4485            .pipe(TransactionKind::Genesis)
4486            .pipe(Self::new_system_transaction)
4487    }
4488
4489    pub fn new_consensus_commit_prologue(
4490        epoch: u64,
4491        round: u64,
4492        commit_timestamp_ms: CheckpointTimestamp,
4493    ) -> Self {
4494        ConsensusCommitPrologue {
4495            epoch,
4496            round,
4497            commit_timestamp_ms,
4498        }
4499        .pipe(TransactionKind::ConsensusCommitPrologue)
4500        .pipe(Self::new_system_transaction)
4501    }
4502
4503    pub fn new_consensus_commit_prologue_v2(
4504        epoch: u64,
4505        round: u64,
4506        commit_timestamp_ms: CheckpointTimestamp,
4507        consensus_commit_digest: ConsensusCommitDigest,
4508    ) -> Self {
4509        ConsensusCommitPrologueV2 {
4510            epoch,
4511            round,
4512            commit_timestamp_ms,
4513            consensus_commit_digest,
4514        }
4515        .pipe(TransactionKind::ConsensusCommitPrologueV2)
4516        .pipe(Self::new_system_transaction)
4517    }
4518
4519    pub fn new_consensus_commit_prologue_v3(
4520        epoch: u64,
4521        round: u64,
4522        commit_timestamp_ms: CheckpointTimestamp,
4523        consensus_commit_digest: ConsensusCommitDigest,
4524        consensus_determined_version_assignments: ConsensusDeterminedVersionAssignments,
4525    ) -> Self {
4526        ConsensusCommitPrologueV3 {
4527            epoch,
4528            round,
4529            // sub_dag_index is reserved for when we have multi commits per round.
4530            sub_dag_index: None,
4531            commit_timestamp_ms,
4532            consensus_commit_digest,
4533            consensus_determined_version_assignments,
4534        }
4535        .pipe(TransactionKind::ConsensusCommitPrologueV3)
4536        .pipe(Self::new_system_transaction)
4537    }
4538
4539    pub fn new_consensus_commit_prologue_v4(
4540        epoch: u64,
4541        round: u64,
4542        commit_timestamp_ms: CheckpointTimestamp,
4543        consensus_commit_digest: ConsensusCommitDigest,
4544        consensus_determined_version_assignments: ConsensusDeterminedVersionAssignments,
4545        additional_state_digest: AdditionalConsensusStateDigest,
4546    ) -> Self {
4547        ConsensusCommitPrologueV4 {
4548            epoch,
4549            round,
4550            // sub_dag_index is reserved for when we have multi commits per round.
4551            sub_dag_index: None,
4552            commit_timestamp_ms,
4553            consensus_commit_digest,
4554            consensus_determined_version_assignments,
4555            additional_state_digest,
4556        }
4557        .pipe(TransactionKind::ConsensusCommitPrologueV4)
4558        .pipe(Self::new_system_transaction)
4559    }
4560
4561    pub fn new_authenticator_state_update(
4562        epoch: u64,
4563        round: u64,
4564        new_active_jwks: Vec<ActiveJwk>,
4565        authenticator_obj_initial_shared_version: SequenceNumber,
4566    ) -> Self {
4567        AuthenticatorStateUpdate {
4568            epoch,
4569            round,
4570            new_active_jwks,
4571            authenticator_obj_initial_shared_version,
4572        }
4573        .pipe(TransactionKind::AuthenticatorStateUpdate)
4574        .pipe(Self::new_system_transaction)
4575    }
4576
4577    pub fn new_randomness_state_update(
4578        epoch: u64,
4579        randomness_round: RandomnessRound,
4580        random_bytes: Vec<u8>,
4581        randomness_obj_initial_shared_version: SequenceNumber,
4582    ) -> Self {
4583        RandomnessStateUpdate {
4584            epoch,
4585            randomness_round,
4586            random_bytes,
4587            randomness_obj_initial_shared_version,
4588        }
4589        .pipe(TransactionKind::RandomnessStateUpdate)
4590        .pipe(Self::new_system_transaction)
4591    }
4592
4593    pub fn new_end_of_epoch_transaction(txns: Vec<EndOfEpochTransactionKind>) -> Self {
4594        TransactionKind::EndOfEpochTransaction(txns).pipe(Self::new_system_transaction)
4595    }
4596
4597    pub fn new_system_transaction(system_transaction: TransactionKind) -> Self {
4598        system_transaction
4599            .pipe(TransactionData::new_system_transaction)
4600            .pipe(|data| {
4601                SenderSignedData::new_from_sender_signature(
4602                    data,
4603                    Ed25519SuiSignature::from_bytes(&[0; Ed25519SuiSignature::LENGTH])
4604                        .unwrap()
4605                        .into(),
4606                )
4607            })
4608            .pipe(Transaction::new)
4609            .pipe(Self::new_from_verified)
4610    }
4611}
4612
4613impl VerifiedSignedTransaction {
4614    /// Use signing key to create a signed object.
4615    pub fn new(
4616        epoch: EpochId,
4617        transaction: VerifiedTransaction,
4618        authority: AuthorityName,
4619        secret: &dyn Signer<AuthoritySignature>,
4620    ) -> Self {
4621        Self::new_from_verified(SignedTransaction::new(
4622            epoch,
4623            transaction.into_inner().into_data(),
4624            secret,
4625            authority,
4626        ))
4627    }
4628}
4629
4630/// A transaction that is signed by a sender but not yet by an authority.
4631pub type Transaction = Envelope<SenderSignedData, EmptySignInfo>;
4632pub type VerifiedTransaction = VerifiedEnvelope<SenderSignedData, EmptySignInfo>;
4633pub type TrustedTransaction = TrustedEnvelope<SenderSignedData, EmptySignInfo>;
4634
4635/// A transaction that is signed by a sender and also by an authority.
4636pub type SignedTransaction = Envelope<SenderSignedData, AuthoritySignInfo>;
4637pub type VerifiedSignedTransaction = VerifiedEnvelope<SenderSignedData, AuthoritySignInfo>;
4638
4639impl Transaction {
4640    pub fn verify_signature_for_testing(
4641        &self,
4642        current_epoch: EpochId,
4643        verify_params: &VerifyParams,
4644    ) -> SuiResult {
4645        verify_sender_signed_data_message_signatures(
4646            self.data(),
4647            current_epoch,
4648            verify_params,
4649            Arc::new(VerifiedDigestCache::new_empty()),
4650            vec![],
4651        )?;
4652        Ok(())
4653    }
4654
4655    pub fn try_into_verified_for_testing(
4656        self,
4657        current_epoch: EpochId,
4658        verify_params: &VerifyParams,
4659    ) -> SuiResult<VerifiedTransaction> {
4660        self.verify_signature_for_testing(current_epoch, verify_params)?;
4661        Ok(VerifiedTransaction::new_from_verified(self))
4662    }
4663}
4664
4665impl SignedTransaction {
4666    pub fn verify_signatures_authenticated_for_testing(
4667        &self,
4668        committee: &Committee,
4669        verify_params: &VerifyParams,
4670    ) -> SuiResult {
4671        verify_sender_signed_data_message_signatures(
4672            self.data(),
4673            committee.epoch(),
4674            verify_params,
4675            Arc::new(VerifiedDigestCache::new_empty()),
4676            vec![],
4677        )?;
4678
4679        self.auth_sig().verify_secure(
4680            self.data(),
4681            Intent::sui_app(IntentScope::SenderSignedTransaction),
4682            committee,
4683        )
4684    }
4685
4686    pub fn try_into_verified_for_testing(
4687        self,
4688        committee: &Committee,
4689        verify_params: &VerifyParams,
4690    ) -> SuiResult<VerifiedSignedTransaction> {
4691        self.verify_signatures_authenticated_for_testing(committee, verify_params)?;
4692        Ok(VerifiedSignedTransaction::new_from_verified(self))
4693    }
4694}
4695
4696pub type CertifiedTransaction = Envelope<SenderSignedData, AuthorityStrongQuorumSignInfo>;
4697
4698impl CertifiedTransaction {
4699    pub fn gas_price(&self) -> u64 {
4700        self.data().transaction_data().gas_price()
4701    }
4702}
4703
4704pub type VerifiedCertificate = VerifiedEnvelope<SenderSignedData, AuthorityStrongQuorumSignInfo>;
4705pub type TrustedCertificate = TrustedEnvelope<SenderSignedData, AuthorityStrongQuorumSignInfo>;
4706
4707#[derive(Clone, Debug, Serialize, Deserialize)]
4708pub struct WithAliases<T>(
4709    T,
4710    #[serde(with = "nonempty_as_vec")] NonEmpty<(u8, Option<SequenceNumber>)>,
4711);
4712
4713impl<T> WithAliases<T> {
4714    pub fn new(tx: T, aliases: NonEmpty<(u8, Option<SequenceNumber>)>) -> Self {
4715        Self(tx, aliases)
4716    }
4717
4718    pub fn tx(&self) -> &T {
4719        &self.0
4720    }
4721
4722    pub fn aliases(&self) -> &NonEmpty<(u8, Option<SequenceNumber>)> {
4723        &self.1
4724    }
4725
4726    pub fn into_tx(self) -> T {
4727        self.0
4728    }
4729
4730    pub fn into_aliases(self) -> NonEmpty<(u8, Option<SequenceNumber>)> {
4731        self.1
4732    }
4733
4734    pub fn into_inner(self) -> (T, NonEmpty<(u8, Option<SequenceNumber>)>) {
4735        (self.0, self.1)
4736    }
4737}
4738
4739impl<T: Message, S> WithAliases<VerifiedEnvelope<T, S>> {
4740    /// Analogous to VerifiedEnvelope::serializable.
4741    pub fn serializable(self) -> WithAliases<TrustedEnvelope<T, S>> {
4742        WithAliases(self.0.serializable(), self.1)
4743    }
4744}
4745
4746impl<S> WithAliases<Envelope<SenderSignedData, S>> {
4747    /// Creates a WithAliases where each required signer is mapped to its corresponding
4748    /// signature index (assuming 1:1 correspondence) with no alias object version.
4749    pub fn no_aliases(tx: Envelope<SenderSignedData, S>) -> Self {
4750        let required_signers = tx.intent_message().value.required_signers();
4751        assert_eq!(required_signers.len(), tx.tx_signatures().len());
4752        let no_aliases = required_signers
4753            .iter()
4754            .enumerate()
4755            .map(|(idx, _)| (idx as u8, None))
4756            .collect::<Vec<_>>();
4757        Self::new(
4758            tx,
4759            NonEmpty::from_vec(no_aliases).expect("must have at least one required_signer"),
4760        )
4761    }
4762}
4763
4764impl<S> WithAliases<VerifiedEnvelope<SenderSignedData, S>> {
4765    /// Creates a WithAliases where each required signer is mapped to its corresponding
4766    /// signature index (assuming 1:1 correspondence) with no alias object version.
4767    pub fn no_aliases(tx: VerifiedEnvelope<SenderSignedData, S>) -> Self {
4768        let required_signers = tx.intent_message().value.required_signers();
4769        assert_eq!(required_signers.len(), tx.tx_signatures().len());
4770        let no_aliases = required_signers
4771            .iter()
4772            .enumerate()
4773            .map(|(idx, _)| (idx as u8, None))
4774            .collect::<Vec<_>>();
4775        Self::new(
4776            tx,
4777            NonEmpty::from_vec(no_aliases).expect("must have at least one required_signer"),
4778        )
4779    }
4780}
4781
4782pub type TransactionWithAliases = WithAliases<Transaction>;
4783pub type VerifiedTransactionWithAliases = WithAliases<VerifiedTransaction>;
4784pub type TrustedTransactionWithAliases = WithAliases<TrustedTransaction>;
4785
4786/// Deprecated version of WithAliases that uses SuiAddress instead of u8.
4787/// This is needed to read data from deferred_transactions_with_aliases_v2 table
4788/// which was written with the old format before the type was changed.
4789// TODO: Delete this after all production networks are on the latest table.
4790#[derive(Clone, Debug, Serialize, Deserialize)]
4791pub struct DeprecatedWithAliases<T>(
4792    T,
4793    #[serde(with = "nonempty_as_vec")] NonEmpty<(SuiAddress, Option<SequenceNumber>)>,
4794);
4795
4796impl<T> DeprecatedWithAliases<T> {
4797    pub fn into_inner(self) -> (T, NonEmpty<(SuiAddress, Option<SequenceNumber>)>) {
4798        (self.0, self.1)
4799    }
4800}
4801
4802impl<T: Message, S> From<WithAliases<VerifiedEnvelope<T, S>>> for WithAliases<Envelope<T, S>> {
4803    fn from(value: WithAliases<VerifiedEnvelope<T, S>>) -> Self {
4804        Self(value.0.into(), value.1)
4805    }
4806}
4807
4808impl<T: Message, S> From<WithAliases<TrustedEnvelope<T, S>>>
4809    for WithAliases<VerifiedEnvelope<T, S>>
4810{
4811    fn from(value: WithAliases<TrustedEnvelope<T, S>>) -> Self {
4812        Self(value.0.into(), value.1)
4813    }
4814}
4815
4816mod nonempty_as_vec {
4817    use super::*;
4818    use serde::{Deserialize, Deserializer, Serialize, Serializer};
4819
4820    pub fn serialize<S, T>(value: &NonEmpty<T>, serializer: S) -> Result<S::Ok, S::Error>
4821    where
4822        S: Serializer,
4823        T: Serialize,
4824    {
4825        let vec: Vec<&T> = value.iter().collect();
4826        vec.serialize(serializer)
4827    }
4828
4829    pub fn deserialize<'de, D, T>(deserializer: D) -> Result<NonEmpty<T>, D::Error>
4830    where
4831        D: Deserializer<'de>,
4832        T: Deserialize<'de> + Clone,
4833    {
4834        use serde::de::{SeqAccess, Visitor};
4835        use std::fmt;
4836        use std::marker::PhantomData;
4837
4838        struct NonEmptyVisitor<T>(PhantomData<T>);
4839
4840        impl<'de, T> Visitor<'de> for NonEmptyVisitor<T>
4841        where
4842            T: Deserialize<'de> + Clone,
4843        {
4844            type Value = NonEmpty<T>;
4845
4846            fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
4847                formatter.write_str("a non-empty sequence")
4848            }
4849
4850            fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
4851            where
4852                A: SeqAccess<'de>,
4853            {
4854                let head = seq
4855                    .next_element()?
4856                    .ok_or_else(|| serde::de::Error::custom("empty vector"))?;
4857
4858                let mut tail = Vec::new();
4859                while let Some(elem) = seq.next_element()? {
4860                    tail.push(elem);
4861                }
4862
4863                Ok(NonEmpty { head, tail })
4864            }
4865        }
4866
4867        deserializer.deserialize_seq(NonEmptyVisitor(PhantomData))
4868    }
4869}
4870
4871// =============================================================================
4872// TransactionWithClaims - Generalized claim system for consensus messages
4873// =============================================================================
4874
4875/// Claims that can be attached to a transaction for consensus validation.
4876/// Each claim type represents a piece of information that:
4877/// 1. The submitting validator includes in the consensus message
4878/// 2. Voting validators verify before accepting
4879/// 3. The consensus handler can use deterministically
4880#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
4881pub enum TransactionClaim {
4882    /// DEPRECATED. Do not use.
4883    #[deprecated(note = "Use AddressAliasesV2")]
4884    AddressAliases(
4885        #[serde(with = "nonempty_as_vec")] NonEmpty<(SuiAddress, Option<SequenceNumber>)>,
4886    ),
4887
4888    /// Object IDs that are claimed to be immutable.
4889    /// Used to filter out immutable objects from lock acquisition in consensus handler.
4890    ImmutableInputObjects(Vec<ObjectID>),
4891
4892    /// Address aliases used for signature verification.
4893    /// Length must equal the number of `required_signers`. Each element maps the corresponding
4894    /// signer to the signature index and alias object version (if any) used to verify it.
4895    AddressAliasesV2(#[serde(with = "nonempty_as_vec")] NonEmpty<(u8, Option<SequenceNumber>)>),
4896}
4897
4898/// A transaction with attached claims that have been verified by voting validators.
4899#[derive(Clone, Debug, Serialize, Deserialize)]
4900pub struct TransactionWithClaims<T> {
4901    tx: T,
4902    claims: Vec<TransactionClaim>,
4903}
4904
4905impl<T> TransactionWithClaims<T> {
4906    pub fn new(tx: T, claims: Vec<TransactionClaim>) -> Self {
4907        Self { tx, claims }
4908    }
4909
4910    /// Create from a transaction with only address aliases.
4911    pub fn from_aliases(tx: T, aliases: NonEmpty<(u8, Option<SequenceNumber>)>) -> Self {
4912        Self {
4913            tx,
4914            claims: vec![TransactionClaim::AddressAliasesV2(aliases)],
4915        }
4916    }
4917
4918    /// Creates from a transaction without any aliases attached.
4919    pub fn no_aliases(tx: T) -> Self {
4920        Self { tx, claims: vec![] }
4921    }
4922
4923    pub fn tx(&self) -> &T {
4924        &self.tx
4925    }
4926
4927    pub fn into_tx(self) -> T {
4928        self.tx
4929    }
4930
4931    /// Get the address aliases V2 claim. Differentiate between empty and not present for validation.
4932    pub fn aliases(&self) -> Option<NonEmpty<(u8, Option<SequenceNumber>)>> {
4933        self.claims
4934            .iter()
4935            .find_map(|c| match c {
4936                TransactionClaim::AddressAliasesV2(aliases) => Some(aliases),
4937                _ => None,
4938            })
4939            .cloned()
4940    }
4941
4942    // TODO: Remove once `fix_checkpoint_signature_mapping` flag is enabled in testnet.
4943    #[allow(deprecated)]
4944    pub fn aliases_v1(&self) -> Option<NonEmpty<(SuiAddress, Option<SequenceNumber>)>> {
4945        self.claims
4946            .iter()
4947            .find_map(|c| match c {
4948                TransactionClaim::AddressAliases(aliases) => Some(aliases),
4949                _ => None,
4950            })
4951            .cloned()
4952    }
4953
4954    /// Get the immutable input objects claim. Returns empty vector if not present.
4955    pub fn get_immutable_objects(&self) -> Vec<ObjectID> {
4956        self.claims
4957            .iter()
4958            .find_map(|c| match c {
4959                TransactionClaim::ImmutableInputObjects(objs) => Some(objs.clone()),
4960                _ => None,
4961            })
4962            .unwrap_or_default()
4963    }
4964}
4965
4966pub type PlainTransactionWithClaims = TransactionWithClaims<Transaction>;
4967
4968/// Convert from `WithAliases<VerifiedEnvelope>` to `TransactionWithClaims<Envelope>`.
4969/// Used when feature flag is off to convert existing WithAliases to the new type.
4970impl<T: Message, S> From<WithAliases<VerifiedEnvelope<T, S>>>
4971    for TransactionWithClaims<Envelope<T, S>>
4972{
4973    fn from(value: WithAliases<VerifiedEnvelope<T, S>>) -> Self {
4974        let (tx, aliases) = value.into_inner();
4975        Self::from_aliases(tx.into(), aliases)
4976    }
4977}
4978
4979#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize, PartialOrd, Ord, Hash)]
4980pub enum InputObjectKind {
4981    // A Move package, must be immutable.
4982    MovePackage(ObjectID),
4983    // A Move object, either immutable, or owned mutable.
4984    ImmOrOwnedMoveObject(ObjectRef),
4985    // A Move object that's shared and mutable.
4986    SharedMoveObject {
4987        id: ObjectID,
4988        initial_shared_version: SequenceNumber,
4989        mutability: SharedObjectMutability,
4990    },
4991}
4992
4993#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize, PartialOrd, Ord, Hash)]
4994pub enum SharedObjectMutability {
4995    // The "classic" mutable/immutable modes.
4996    Immutable,
4997    Mutable,
4998    // Non-exclusive write is used to allow multiple transactions to
4999    // simultaneously add disjoint dynamic fields to an object.
5000    // (Currently only used by settlement transactions).
5001    NonExclusiveWrite,
5002}
5003
5004impl SharedObjectMutability {
5005    pub fn is_exclusive(&self) -> bool {
5006        match self {
5007            SharedObjectMutability::Mutable => true,
5008            SharedObjectMutability::Immutable => false,
5009            SharedObjectMutability::NonExclusiveWrite => false,
5010        }
5011    }
5012}
5013
5014impl InputObjectKind {
5015    pub fn object_id(&self) -> ObjectID {
5016        self.full_object_id().id()
5017    }
5018
5019    pub fn full_object_id(&self) -> FullObjectID {
5020        match self {
5021            Self::MovePackage(id) => FullObjectID::Fastpath(*id),
5022            Self::ImmOrOwnedMoveObject((id, _, _)) => FullObjectID::Fastpath(*id),
5023            Self::SharedMoveObject {
5024                id,
5025                initial_shared_version,
5026                ..
5027            } => FullObjectID::Consensus((*id, *initial_shared_version)),
5028        }
5029    }
5030
5031    pub fn version(&self) -> Option<SequenceNumber> {
5032        match self {
5033            Self::MovePackage(..) => None,
5034            Self::ImmOrOwnedMoveObject((_, version, _)) => Some(*version),
5035            Self::SharedMoveObject { .. } => None,
5036        }
5037    }
5038
5039    pub fn object_not_found_error(&self) -> UserInputError {
5040        match *self {
5041            Self::MovePackage(package_id) => {
5042                UserInputError::DependentPackageNotFound { package_id }
5043            }
5044            Self::ImmOrOwnedMoveObject((object_id, version, _)) => UserInputError::ObjectNotFound {
5045                object_id,
5046                version: Some(version),
5047            },
5048            Self::SharedMoveObject { id, .. } => UserInputError::ObjectNotFound {
5049                object_id: id,
5050                version: None,
5051            },
5052        }
5053    }
5054
5055    pub fn is_shared_object(&self) -> bool {
5056        matches!(self, Self::SharedMoveObject { .. })
5057    }
5058}
5059
5060/// The result of reading an object for execution. Because shared objects may be deleted, one
5061/// possible result of reading a shared object is that ObjectReadResultKind::Deleted is returned.
5062#[derive(Clone, Debug)]
5063pub struct ObjectReadResult {
5064    pub input_object_kind: InputObjectKind,
5065    pub object: ObjectReadResultKind,
5066}
5067
5068#[derive(Clone)]
5069pub enum ObjectReadResultKind {
5070    Object(Object),
5071    // The version of the object that the transaction intended to read, and the digest of the tx
5072    // that removed it from consensus.
5073    ObjectConsensusStreamEnded(SequenceNumber, TransactionDigest),
5074    // A shared object in a cancelled transaction. The sequence number embeds cancellation reason.
5075    CancelledTransactionSharedObject(SequenceNumber),
5076}
5077
5078impl ObjectReadResultKind {
5079    pub fn is_cancelled(&self) -> bool {
5080        matches!(
5081            self,
5082            ObjectReadResultKind::CancelledTransactionSharedObject(_)
5083        )
5084    }
5085
5086    pub fn version(&self) -> SequenceNumber {
5087        match self {
5088            ObjectReadResultKind::Object(object) => object.version(),
5089            ObjectReadResultKind::ObjectConsensusStreamEnded(seq, _) => *seq,
5090            ObjectReadResultKind::CancelledTransactionSharedObject(seq) => *seq,
5091        }
5092    }
5093}
5094
5095impl std::fmt::Debug for ObjectReadResultKind {
5096    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
5097        match self {
5098            ObjectReadResultKind::Object(obj) => {
5099                write!(f, "Object({:?})", obj.compute_object_reference())
5100            }
5101            ObjectReadResultKind::ObjectConsensusStreamEnded(seq, digest) => {
5102                write!(f, "ObjectConsensusStreamEnded({}, {:?})", seq, digest)
5103            }
5104            ObjectReadResultKind::CancelledTransactionSharedObject(seq) => {
5105                write!(f, "CancelledTransactionSharedObject({})", seq)
5106            }
5107        }
5108    }
5109}
5110
5111impl From<Object> for ObjectReadResultKind {
5112    fn from(object: Object) -> Self {
5113        Self::Object(object)
5114    }
5115}
5116
5117impl ObjectReadResult {
5118    pub fn new(input_object_kind: InputObjectKind, object: ObjectReadResultKind) -> Self {
5119        if let (
5120            InputObjectKind::ImmOrOwnedMoveObject(_),
5121            ObjectReadResultKind::ObjectConsensusStreamEnded(_, _),
5122        ) = (&input_object_kind, &object)
5123        {
5124            panic!("only consensus objects can be ObjectConsensusStreamEnded");
5125        }
5126
5127        if let (
5128            InputObjectKind::ImmOrOwnedMoveObject(_),
5129            ObjectReadResultKind::CancelledTransactionSharedObject(_),
5130        ) = (&input_object_kind, &object)
5131        {
5132            panic!("only consensus objects can be CancelledTransactionSharedObject");
5133        }
5134
5135        Self {
5136            input_object_kind,
5137            object,
5138        }
5139    }
5140
5141    pub fn id(&self) -> ObjectID {
5142        self.input_object_kind.object_id()
5143    }
5144
5145    pub fn as_object(&self) -> Option<&Object> {
5146        match &self.object {
5147            ObjectReadResultKind::Object(object) => Some(object),
5148            ObjectReadResultKind::ObjectConsensusStreamEnded(_, _) => None,
5149            ObjectReadResultKind::CancelledTransactionSharedObject(_) => None,
5150        }
5151    }
5152
5153    pub fn new_from_gas_object(gas: &Object) -> Self {
5154        let objref = gas.compute_object_reference();
5155        Self {
5156            input_object_kind: InputObjectKind::ImmOrOwnedMoveObject(objref),
5157            object: ObjectReadResultKind::Object(gas.clone()),
5158        }
5159    }
5160
5161    pub fn is_mutable(&self) -> bool {
5162        match (&self.input_object_kind, &self.object) {
5163            (InputObjectKind::MovePackage(_), _) => false,
5164            (InputObjectKind::ImmOrOwnedMoveObject(_), ObjectReadResultKind::Object(object)) => {
5165                !object.is_immutable()
5166            }
5167            (
5168                InputObjectKind::ImmOrOwnedMoveObject(_),
5169                ObjectReadResultKind::ObjectConsensusStreamEnded(_, _),
5170            ) => unreachable!(),
5171            (
5172                InputObjectKind::ImmOrOwnedMoveObject(_),
5173                ObjectReadResultKind::CancelledTransactionSharedObject(_),
5174            ) => unreachable!(),
5175            (InputObjectKind::SharedMoveObject { mutability, .. }, _) => match mutability {
5176                SharedObjectMutability::Mutable => true,
5177                SharedObjectMutability::Immutable => false,
5178                SharedObjectMutability::NonExclusiveWrite => false,
5179            },
5180        }
5181    }
5182
5183    pub fn is_shared_object(&self) -> bool {
5184        self.input_object_kind.is_shared_object()
5185    }
5186
5187    pub fn is_consensus_stream_ended(&self) -> bool {
5188        self.consensus_stream_end_info().is_some()
5189    }
5190
5191    pub fn consensus_stream_end_info(&self) -> Option<(SequenceNumber, TransactionDigest)> {
5192        match &self.object {
5193            ObjectReadResultKind::ObjectConsensusStreamEnded(v, tx) => Some((*v, *tx)),
5194            _ => None,
5195        }
5196    }
5197
5198    /// Return the object ref iff the object is an address-owned object (i.e. not shared, not immutable).
5199    pub fn get_address_owned_objref(&self) -> Option<ObjectRef> {
5200        match (&self.input_object_kind, &self.object) {
5201            (InputObjectKind::MovePackage(_), _) => None,
5202            (
5203                InputObjectKind::ImmOrOwnedMoveObject(objref),
5204                ObjectReadResultKind::Object(object),
5205            ) => {
5206                if object.is_immutable() {
5207                    None
5208                } else {
5209                    Some(*objref)
5210                }
5211            }
5212            (
5213                InputObjectKind::ImmOrOwnedMoveObject(_),
5214                ObjectReadResultKind::ObjectConsensusStreamEnded(_, _),
5215            ) => unreachable!(),
5216            (
5217                InputObjectKind::ImmOrOwnedMoveObject(_),
5218                ObjectReadResultKind::CancelledTransactionSharedObject(_),
5219            ) => unreachable!(),
5220            (InputObjectKind::SharedMoveObject { .. }, _) => None,
5221        }
5222    }
5223
5224    pub fn is_address_owned(&self) -> bool {
5225        self.get_address_owned_objref().is_some()
5226    }
5227
5228    pub fn is_replay_protected_input(&self) -> bool {
5229        if let InputObjectKind::ImmOrOwnedMoveObject(obj_ref) = &self.input_object_kind
5230            && ParsedDigest::is_coin_reservation_digest(&obj_ref.2)
5231        {
5232            true
5233        } else {
5234            self.is_address_owned()
5235        }
5236    }
5237
5238    pub fn to_shared_input(&self) -> Option<SharedInput> {
5239        match self.input_object_kind {
5240            InputObjectKind::MovePackage(_) => None,
5241            InputObjectKind::ImmOrOwnedMoveObject(_) => None,
5242            InputObjectKind::SharedMoveObject { id, mutability, .. } => Some(match &self.object {
5243                ObjectReadResultKind::Object(obj) => {
5244                    SharedInput::Existing(obj.compute_object_reference())
5245                }
5246                ObjectReadResultKind::ObjectConsensusStreamEnded(seq, digest) => {
5247                    SharedInput::ConsensusStreamEnded((id, *seq, mutability, *digest))
5248                }
5249                ObjectReadResultKind::CancelledTransactionSharedObject(seq) => {
5250                    SharedInput::Cancelled((id, *seq))
5251                }
5252            }),
5253        }
5254    }
5255
5256    pub fn get_previous_transaction(&self) -> Option<TransactionDigest> {
5257        match &self.object {
5258            ObjectReadResultKind::Object(obj) => Some(obj.previous_transaction),
5259            ObjectReadResultKind::ObjectConsensusStreamEnded(_, digest) => Some(*digest),
5260            ObjectReadResultKind::CancelledTransactionSharedObject(_) => None,
5261        }
5262    }
5263}
5264
5265#[derive(Clone)]
5266pub struct InputObjects {
5267    objects: Vec<ObjectReadResult>,
5268}
5269
5270impl std::fmt::Debug for InputObjects {
5271    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
5272        f.debug_list().entries(self.objects.iter()).finish()
5273    }
5274}
5275
5276// An InputObjects new-type that has been verified by sui-transaction-checks, and can be
5277// safely passed to execution.
5278#[derive(Clone)]
5279pub struct CheckedInputObjects(InputObjects);
5280
5281// DO NOT CALL outside of sui-transaction-checks, genesis, or replay.
5282//
5283// CheckedInputObjects should really be defined in sui-transaction-checks so that we can
5284// make public construction impossible. But we can't do that because it would result in circular
5285// dependencies.
5286impl CheckedInputObjects {
5287    // Only called by sui-transaction-checks.
5288    pub fn new_with_checked_transaction_inputs(inputs: InputObjects) -> Self {
5289        Self(inputs)
5290    }
5291
5292    // Only called when building the genesis transaction
5293    pub fn new_for_genesis(input_objects: Vec<ObjectReadResult>) -> Self {
5294        Self(InputObjects::new(input_objects))
5295    }
5296
5297    // Only called from the replay tool.
5298    pub fn new_for_replay(input_objects: InputObjects) -> Self {
5299        Self(input_objects)
5300    }
5301
5302    pub fn inner(&self) -> &InputObjects {
5303        &self.0
5304    }
5305
5306    pub fn into_inner(self) -> InputObjects {
5307        self.0
5308    }
5309}
5310
5311impl From<Vec<ObjectReadResult>> for InputObjects {
5312    fn from(objects: Vec<ObjectReadResult>) -> Self {
5313        Self::new(objects)
5314    }
5315}
5316
5317impl InputObjects {
5318    pub fn new(objects: Vec<ObjectReadResult>) -> Self {
5319        Self { objects }
5320    }
5321
5322    pub fn len(&self) -> usize {
5323        self.objects.len()
5324    }
5325
5326    pub fn is_empty(&self) -> bool {
5327        self.objects.is_empty()
5328    }
5329
5330    pub fn contains_consensus_stream_ended_objects(&self) -> bool {
5331        self.objects
5332            .iter()
5333            .any(|obj| obj.is_consensus_stream_ended())
5334    }
5335
5336    // Returns IDs of objects responsible for a transaction being cancelled, and the corresponding
5337    // reason for cancellation.
5338    pub fn get_cancelled_objects(&self) -> Option<(Vec<ObjectID>, SequenceNumber)> {
5339        let mut contains_cancelled = false;
5340        let mut cancel_reason = None;
5341        let mut cancelled_objects = Vec::new();
5342        for obj in &self.objects {
5343            if let ObjectReadResultKind::CancelledTransactionSharedObject(version) = obj.object {
5344                contains_cancelled = true;
5345                if version == SequenceNumber::CONGESTED
5346                    || version == SequenceNumber::RANDOMNESS_UNAVAILABLE
5347                {
5348                    // Verify we don't have multiple cancellation reasons.
5349                    assert!(cancel_reason.is_none() || cancel_reason == Some(version));
5350                    cancel_reason = Some(version);
5351                    cancelled_objects.push(obj.id());
5352                }
5353            }
5354        }
5355
5356        if !cancelled_objects.is_empty() {
5357            Some((
5358                cancelled_objects,
5359                cancel_reason
5360                    .expect("there should be a cancel reason if there are cancelled objects"),
5361            ))
5362        } else {
5363            assert!(!contains_cancelled);
5364            None
5365        }
5366    }
5367
5368    pub fn filter_owned_objects(&self) -> Vec<ObjectRef> {
5369        let owned_objects: Vec<_> = self
5370            .objects
5371            .iter()
5372            .filter_map(|obj| obj.get_address_owned_objref())
5373            .collect();
5374
5375        trace!(
5376            num_mutable_objects = owned_objects.len(),
5377            "Checked locks and found mutable objects"
5378        );
5379
5380        owned_objects
5381    }
5382
5383    pub fn filter_shared_objects(&self) -> Vec<SharedInput> {
5384        self.objects
5385            .iter()
5386            .filter(|obj| obj.is_shared_object())
5387            .map(|obj| {
5388                obj.to_shared_input()
5389                    .expect("already filtered for shared objects")
5390            })
5391            .collect()
5392    }
5393
5394    pub fn transaction_dependencies(&self) -> BTreeSet<TransactionDigest> {
5395        self.objects
5396            .iter()
5397            .filter_map(|obj| obj.get_previous_transaction())
5398            .collect()
5399    }
5400
5401    /// All inputs that will be directly mutated by the transaction. This does
5402    /// not include SharedObjectMutability::NonExclusiveWrite inputs.
5403    pub fn exclusive_mutable_inputs(&self) -> BTreeMap<ObjectID, (VersionDigest, Owner)> {
5404        self.mutables_with_input_kinds()
5405            .filter_map(|(id, (version, owner, kind))| match kind {
5406                InputObjectKind::SharedMoveObject { mutability, .. } => match mutability {
5407                    SharedObjectMutability::Mutable => Some((id, (version, owner))),
5408                    SharedObjectMutability::Immutable => None,
5409                    SharedObjectMutability::NonExclusiveWrite => None,
5410                },
5411                _ => Some((id, (version, owner))),
5412            })
5413            .collect()
5414    }
5415
5416    pub fn non_exclusive_input_objects(&self) -> BTreeMap<ObjectID, Object> {
5417        self.objects
5418            .iter()
5419            .filter_map(|read_result| {
5420                match (read_result.as_object(), read_result.input_object_kind) {
5421                    (
5422                        Some(object),
5423                        InputObjectKind::SharedMoveObject {
5424                            mutability: SharedObjectMutability::NonExclusiveWrite,
5425                            ..
5426                        },
5427                    ) => Some((read_result.id(), object.clone())),
5428                    _ => None,
5429                }
5430            })
5431            .collect()
5432    }
5433
5434    /// All inputs that can be taken as &mut T, which includes both
5435    /// SharedObjectMutability::Mutable and SharedObjectMutability::NonExclusiveWrite inputs.
5436    pub fn all_mutable_inputs(&self) -> BTreeMap<ObjectID, (VersionDigest, Owner)> {
5437        self.mutables_with_input_kinds()
5438            .filter_map(|(id, (version, owner, kind))| match kind {
5439                InputObjectKind::SharedMoveObject { mutability, .. } => match mutability {
5440                    SharedObjectMutability::Mutable => Some((id, (version, owner))),
5441                    SharedObjectMutability::Immutable => None,
5442                    SharedObjectMutability::NonExclusiveWrite => Some((id, (version, owner))),
5443                },
5444                _ => Some((id, (version, owner))),
5445            })
5446            .collect()
5447    }
5448
5449    fn mutables_with_input_kinds(
5450        &self,
5451    ) -> impl Iterator<Item = (ObjectID, (VersionDigest, Owner, InputObjectKind))> + '_ {
5452        self.objects.iter().filter_map(
5453            |ObjectReadResult {
5454                 input_object_kind,
5455                 object,
5456             }| match (input_object_kind, object) {
5457                (InputObjectKind::MovePackage(_), _) => None,
5458                (
5459                    InputObjectKind::ImmOrOwnedMoveObject(object_ref),
5460                    ObjectReadResultKind::Object(object),
5461                ) => {
5462                    if object.is_immutable() {
5463                        None
5464                    } else {
5465                        Some((
5466                            object_ref.0,
5467                            (
5468                                (object_ref.1, object_ref.2),
5469                                object.owner.clone(),
5470                                *input_object_kind,
5471                            ),
5472                        ))
5473                    }
5474                }
5475                (
5476                    InputObjectKind::ImmOrOwnedMoveObject(_),
5477                    ObjectReadResultKind::ObjectConsensusStreamEnded(_, _),
5478                ) => {
5479                    unreachable!()
5480                }
5481                (
5482                    InputObjectKind::SharedMoveObject { .. },
5483                    ObjectReadResultKind::ObjectConsensusStreamEnded(_, _),
5484                ) => None,
5485                (
5486                    InputObjectKind::SharedMoveObject { mutability, .. },
5487                    ObjectReadResultKind::Object(object),
5488                ) => match *mutability {
5489                    SharedObjectMutability::Mutable => {
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                    SharedObjectMutability::Immutable => None,
5497                    SharedObjectMutability::NonExclusiveWrite => {
5498                        let oref = object.compute_object_reference();
5499                        Some((
5500                            oref.0,
5501                            ((oref.1, oref.2), object.owner.clone(), *input_object_kind),
5502                        ))
5503                    }
5504                },
5505                (
5506                    InputObjectKind::ImmOrOwnedMoveObject(_),
5507                    ObjectReadResultKind::CancelledTransactionSharedObject(_),
5508                ) => {
5509                    unreachable!()
5510                }
5511                (
5512                    InputObjectKind::SharedMoveObject { .. },
5513                    ObjectReadResultKind::CancelledTransactionSharedObject(_),
5514                ) => None,
5515            },
5516        )
5517    }
5518
5519    /// The version to set on objects created by the computation that `self` is input to.
5520    /// Guaranteed to be strictly greater than the versions of all input objects and objects
5521    /// received in the transaction.
5522    pub fn lamport_timestamp(&self, receiving_objects: &[ObjectRef]) -> SequenceNumber {
5523        let input_versions = self
5524            .objects
5525            .iter()
5526            .filter_map(|object| match &object.object {
5527                ObjectReadResultKind::Object(object) => {
5528                    object.data.try_as_move().map(MoveObject::version)
5529                }
5530                ObjectReadResultKind::ObjectConsensusStreamEnded(v, _) => Some(*v),
5531                ObjectReadResultKind::CancelledTransactionSharedObject(_) => None,
5532            })
5533            .chain(receiving_objects.iter().map(|object_ref| object_ref.1));
5534
5535        SequenceNumber::lamport_increment(input_versions)
5536    }
5537
5538    pub fn object_kinds(&self) -> impl Iterator<Item = &InputObjectKind> {
5539        self.objects.iter().map(
5540            |ObjectReadResult {
5541                 input_object_kind, ..
5542             }| input_object_kind,
5543        )
5544    }
5545
5546    pub fn consensus_stream_ended_objects(&self) -> BTreeMap<ObjectID, SequenceNumber> {
5547        self.objects
5548            .iter()
5549            .filter_map(|obj| {
5550                if let InputObjectKind::SharedMoveObject {
5551                    id,
5552                    initial_shared_version,
5553                    ..
5554                } = obj.input_object_kind
5555                {
5556                    obj.is_consensus_stream_ended()
5557                        .then_some((id, initial_shared_version))
5558                } else {
5559                    None
5560                }
5561            })
5562            .collect()
5563    }
5564
5565    pub fn into_object_map(self) -> BTreeMap<ObjectID, Object> {
5566        self.objects
5567            .into_iter()
5568            .filter_map(|o| o.as_object().map(|object| (o.id(), object.clone())))
5569            .collect()
5570    }
5571
5572    pub fn push(&mut self, object: ObjectReadResult) {
5573        self.objects.push(object);
5574    }
5575
5576    pub fn iter(&self) -> impl Iterator<Item = &ObjectReadResult> {
5577        self.objects.iter()
5578    }
5579
5580    pub fn iter_objects(&self) -> impl Iterator<Item = &Object> {
5581        self.objects.iter().filter_map(|o| o.as_object())
5582    }
5583
5584    pub fn non_exclusive_mutable_inputs(
5585        &self,
5586    ) -> impl Iterator<Item = (ObjectID, SequenceNumber)> + '_ {
5587        self.objects.iter().filter_map(
5588            |ObjectReadResult {
5589                 input_object_kind,
5590                 object,
5591             }| match input_object_kind {
5592                // TODO: this is not exercised yet since settlement transactions cannot be
5593                // cancelled, but if/when we expose non-exclusive writes to users,
5594                // a cancelled transaction should not be considered to have done any writes.
5595                InputObjectKind::SharedMoveObject {
5596                    id,
5597                    mutability: SharedObjectMutability::NonExclusiveWrite,
5598                    ..
5599                } if !object.is_cancelled() => Some((*id, object.version())),
5600                _ => None,
5601            },
5602        )
5603    }
5604}
5605
5606// Result of attempting to read a receiving object (currently only at signing time).
5607// Because an object may have been previously received and deleted, the result may be
5608// ReceivingObjectReadResultKind::PreviouslyReceivedObject.
5609#[derive(Clone, Debug)]
5610pub enum ReceivingObjectReadResultKind {
5611    Object(Object),
5612    // The object was received by some other transaction, and we were not able to read it
5613    PreviouslyReceivedObject,
5614}
5615
5616impl ReceivingObjectReadResultKind {
5617    pub fn as_object(&self) -> Option<&Object> {
5618        match &self {
5619            Self::Object(object) => Some(object),
5620            Self::PreviouslyReceivedObject => None,
5621        }
5622    }
5623}
5624
5625pub struct ReceivingObjectReadResult {
5626    pub object_ref: ObjectRef,
5627    pub object: ReceivingObjectReadResultKind,
5628}
5629
5630impl ReceivingObjectReadResult {
5631    pub fn new(object_ref: ObjectRef, object: ReceivingObjectReadResultKind) -> Self {
5632        Self { object_ref, object }
5633    }
5634
5635    pub fn is_previously_received(&self) -> bool {
5636        matches!(
5637            self.object,
5638            ReceivingObjectReadResultKind::PreviouslyReceivedObject
5639        )
5640    }
5641}
5642
5643impl From<Object> for ReceivingObjectReadResultKind {
5644    fn from(object: Object) -> Self {
5645        Self::Object(object)
5646    }
5647}
5648
5649pub struct ReceivingObjects {
5650    pub objects: Vec<ReceivingObjectReadResult>,
5651}
5652
5653impl ReceivingObjects {
5654    pub fn iter(&self) -> impl Iterator<Item = &ReceivingObjectReadResult> {
5655        self.objects.iter()
5656    }
5657
5658    pub fn iter_objects(&self) -> impl Iterator<Item = &Object> {
5659        self.objects.iter().filter_map(|o| o.object.as_object())
5660    }
5661}
5662
5663impl From<Vec<ReceivingObjectReadResult>> for ReceivingObjects {
5664    fn from(objects: Vec<ReceivingObjectReadResult>) -> Self {
5665        Self { objects }
5666    }
5667}
5668
5669impl Display for CertifiedTransaction {
5670    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
5671        let mut writer = String::new();
5672        writeln!(writer, "Transaction Hash: {:?}", self.digest())?;
5673        writeln!(
5674            writer,
5675            "Signed Authorities Bitmap : {:?}",
5676            self.auth_sig().signers_map
5677        )?;
5678        write!(writer, "{}", self.data().intent_message().value.kind())?;
5679        write!(f, "{}", writer)
5680    }
5681}
5682
5683/// TransactionKey uniquely identifies a transaction across all epochs.
5684/// Note that a single transaction may have multiple keys, for example a RandomnessStateUpdate
5685/// could be identified by both `Digest` and `RandomnessRound`.
5686#[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
5687pub enum TransactionKey {
5688    Digest(TransactionDigest),
5689    RandomnessRound(EpochId, RandomnessRound),
5690    AccumulatorSettlement(EpochId, u64 /* checkpoint height */),
5691    ConsensusCommitPrologue(EpochId, u64 /* round */, u32 /* sub_dag_index */),
5692}
5693
5694impl TransactionKey {
5695    pub fn unwrap_digest(&self) -> &TransactionDigest {
5696        match self {
5697            TransactionKey::Digest(d) => d,
5698            _ => panic!("called unwrap_digest on a non-Digest TransactionKey: {self:?}"),
5699        }
5700    }
5701
5702    pub fn as_digest(&self) -> Option<&TransactionDigest> {
5703        match self {
5704            TransactionKey::Digest(d) => Some(d),
5705            _ => None,
5706        }
5707    }
5708}