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