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