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