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