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