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::{ZipDebugEqIteratorExt, assert_reachable, debug_fatal};
59use nonempty::{NonEmpty, nonempty};
60use serde::{Deserialize, Serialize};
61use shared_crypto::intent::{Intent, IntentMessage, IntentScope};
62use std::fmt::Write;
63use std::fmt::{Debug, Display, Formatter};
64use std::sync::Arc;
65use std::sync::RwLock;
66use std::time::Duration;
67use std::{
68 collections::{BTreeMap, BTreeSet, HashSet},
69 hash::Hash,
70 iter,
71};
72use strum::IntoStaticStr;
73use sui_protocol_config::{PerObjectCongestionControlMode, ProtocolConfig};
74use tap::Pipe;
75use tracing::trace;
76
77#[cfg(test)]
78#[path = "unit_tests/transaction_serialization_tests.rs"]
79mod transaction_serialization_tests;
80
81pub const TEST_ONLY_GAS_UNIT_FOR_TRANSFER: u64 = 10_000;
82pub const TEST_ONLY_GAS_UNIT_FOR_OBJECT_BASICS: u64 = 50_000;
83pub const TEST_ONLY_GAS_UNIT_FOR_PUBLISH: u64 = 70_000;
84pub const TEST_ONLY_GAS_UNIT_FOR_STAKING: u64 = 50_000;
85pub const TEST_ONLY_GAS_UNIT_FOR_GENERIC: u64 = 50_000;
86pub const TEST_ONLY_GAS_UNIT_FOR_SPLIT_COIN: u64 = 10_000;
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 type_arg_constraints
1359 .iter()
1360 .zip_debug_eq(&self.type_arguments)
1361 {
1362 let Some(type_arg_constraint) = type_arg_constraint else {
1363 continue;
1364 };
1365 let type_arg = type_input.to_type_tag().map_err(|e| {
1366 UserInputError::Unsupported(format!(
1367 "Failed to parse type argument {type_input} as a type tag: {e}"
1368 ))
1369 })?;
1370 let fund_type = match type_arg_constraint {
1371 TypeArgConstraint::FundType => type_arg,
1372 TypeArgConstraint::BalanceType => Balance::maybe_get_balance_type_param(&type_arg)
1373 .ok_or_else(|| {
1374 UserInputError::Unsupported(format!(
1375 "Expected a type Balance<_> but got {type_input}",
1376 ))
1377 })?,
1378 };
1379 fp_ensure!(
1380 allowed_token_types.contains_key(&fund_type),
1381 UserInputError::Unsupported(format!(
1382 "Fund type {fund_type} is not currently allowed in gasless transactions"
1383 ))
1384 );
1385 }
1386 Ok(())
1387 }
1388}
1389
1390impl Command {
1391 pub fn move_call(
1392 package: ObjectID,
1393 module: Identifier,
1394 function: Identifier,
1395 type_arguments: Vec<TypeTag>,
1396 arguments: Vec<Argument>,
1397 ) -> Self {
1398 let module = module.to_string();
1399 let function = function.to_string();
1400 let type_arguments = type_arguments.into_iter().map(TypeInput::from).collect();
1401 Command::MoveCall(Box::new(ProgrammableMoveCall {
1402 package,
1403 module,
1404 function,
1405 type_arguments,
1406 arguments,
1407 }))
1408 }
1409
1410 pub fn make_move_vec(ty: Option<TypeTag>, args: Vec<Argument>) -> Self {
1411 Command::MakeMoveVec(ty.map(TypeInput::from), args)
1412 }
1413
1414 fn input_objects(&self) -> Vec<InputObjectKind> {
1415 match self {
1416 Command::Upgrade(_, deps, package_id, _) => deps
1417 .iter()
1418 .map(|id| InputObjectKind::MovePackage(*id))
1419 .chain(Some(InputObjectKind::MovePackage(*package_id)))
1420 .collect(),
1421 Command::Publish(_, deps) => deps
1422 .iter()
1423 .map(|id| InputObjectKind::MovePackage(*id))
1424 .collect(),
1425 Command::MoveCall(c) => c.input_objects(),
1426 Command::MakeMoveVec(Some(t), _) => {
1427 let mut packages = BTreeSet::new();
1428 add_type_input_packages(&mut packages, t);
1429 packages
1430 .into_iter()
1431 .map(InputObjectKind::MovePackage)
1432 .collect()
1433 }
1434 Command::MakeMoveVec(None, _)
1435 | Command::TransferObjects(_, _)
1436 | Command::SplitCoins(_, _)
1437 | Command::MergeCoins(_, _) => vec![],
1438 }
1439 }
1440
1441 fn non_system_packages_to_be_published(&self) -> Option<&Vec<Vec<u8>>> {
1442 match self {
1443 Command::Upgrade(v, _, _, _) => Some(v),
1444 Command::Publish(v, _) => Some(v),
1445 Command::MoveCall(_)
1446 | Command::TransferObjects(_, _)
1447 | Command::SplitCoins(_, _)
1448 | Command::MergeCoins(_, _)
1449 | Command::MakeMoveVec(_, _) => None,
1450 }
1451 }
1452
1453 fn validity_check(&self, config: &ProtocolConfig) -> UserInputResult {
1454 match self {
1455 Command::MoveCall(call) => call.validity_check(config)?,
1456 Command::TransferObjects(args, _)
1457 | Command::MergeCoins(_, args)
1458 | Command::SplitCoins(_, args) => {
1459 fp_ensure!(!args.is_empty(), UserInputError::EmptyCommandInput);
1460 fp_ensure!(
1461 args.len() < config.max_arguments() as usize,
1462 UserInputError::SizeLimitExceeded {
1463 limit: "maximum arguments in a programmable transaction command"
1464 .to_string(),
1465 value: config.max_arguments().to_string()
1466 }
1467 );
1468 }
1469 Command::MakeMoveVec(ty_opt, args) => {
1470 fp_ensure!(
1472 ty_opt.is_some() || !args.is_empty(),
1473 UserInputError::EmptyCommandInput
1474 );
1475 if let Some(ty) = ty_opt {
1476 let mut type_arguments_count = 0;
1477 type_input_validity_check(ty, config, &mut type_arguments_count)?;
1478 }
1479 fp_ensure!(
1480 args.len() < config.max_arguments() as usize,
1481 UserInputError::SizeLimitExceeded {
1482 limit: "maximum arguments in a programmable transaction command"
1483 .to_string(),
1484 value: config.max_arguments().to_string()
1485 }
1486 );
1487 }
1488 Command::Publish(modules, deps) | Command::Upgrade(modules, deps, _, _) => {
1489 fp_ensure!(!modules.is_empty(), UserInputError::EmptyCommandInput);
1490 fp_ensure!(
1491 modules.len() < config.max_modules_in_publish() as usize,
1492 UserInputError::SizeLimitExceeded {
1493 limit: "maximum modules in a programmable transaction upgrade command"
1494 .to_string(),
1495 value: config.max_modules_in_publish().to_string()
1496 }
1497 );
1498 if let Some(max_package_dependencies) = config.max_package_dependencies_as_option()
1499 {
1500 fp_ensure!(
1501 deps.len() < max_package_dependencies as usize,
1502 UserInputError::SizeLimitExceeded {
1503 limit: "maximum package dependencies".to_string(),
1504 value: max_package_dependencies.to_string()
1505 }
1506 );
1507 };
1508 }
1509 };
1510 Ok(())
1511 }
1512
1513 fn validate_gasless_transaction(
1514 &self,
1515 allowed_token_types: &BTreeMap<TypeTag, u64>,
1516 ) -> UserInputResult {
1517 match self {
1518 Command::MoveCall(call) => call.validate_gasless_transaction(allowed_token_types),
1519 Command::MergeCoins(_, _) | Command::SplitCoins(_, _) => Ok(()),
1520 _ => Err(UserInputError::Unsupported(
1521 "Gasless transactions only support MoveCall, MergeCoins, and SplitCoins commands"
1522 .to_string(),
1523 )),
1524 }
1525 }
1526
1527 fn is_input_arg_used(&self, input_arg: u16) -> bool {
1528 self.is_argument_used(Argument::Input(input_arg))
1529 }
1530
1531 pub fn is_gas_coin_used(&self) -> bool {
1532 self.is_argument_used(Argument::GasCoin)
1533 }
1534
1535 pub fn is_argument_used(&self, argument: Argument) -> bool {
1536 self.arguments().any(|a| a == &argument)
1537 }
1538
1539 fn input_arguments(&self) -> impl Iterator<Item = u16> + '_ {
1540 self.arguments().filter_map(|arg| match arg {
1541 Argument::Input(i) => Some(*i),
1542 _ => None,
1543 })
1544 }
1545
1546 fn arguments(&self) -> impl Iterator<Item = &Argument> + '_ {
1547 let (args, single): (&[Argument], Option<&Argument>) = match self {
1548 Command::MoveCall(c) => (&c.arguments, None),
1549 Command::TransferObjects(args, arg)
1550 | Command::MergeCoins(arg, args)
1551 | Command::SplitCoins(arg, args) => (args, Some(arg)),
1552 Command::MakeMoveVec(_, args) => (args, None),
1553 Command::Upgrade(_, _, _, arg) => (&[], Some(arg)),
1554 Command::Publish(_, _) => (&[], None),
1555 };
1556 args.iter().chain(single)
1557 }
1558}
1559
1560pub fn write_sep<T: Display>(
1561 f: &mut Formatter<'_>,
1562 items: impl IntoIterator<Item = T>,
1563 sep: &str,
1564) -> std::fmt::Result {
1565 let mut xs = items.into_iter();
1566 let Some(x) = xs.next() else {
1567 return Ok(());
1568 };
1569 write!(f, "{x}")?;
1570 for x in xs {
1571 write!(f, "{sep}{x}")?;
1572 }
1573 Ok(())
1574}
1575
1576impl ProgrammableTransaction {
1577 pub fn input_objects(&self) -> UserInputResult<Vec<InputObjectKind>> {
1578 let ProgrammableTransaction { inputs, commands } = self;
1579 let input_arg_objects = inputs
1580 .iter()
1581 .flat_map(|arg| arg.input_objects())
1582 .collect::<Vec<_>>();
1583 let mut used = HashSet::new();
1585 if !input_arg_objects.iter().all(|o| used.insert(o.object_id())) {
1586 return Err(UserInputError::DuplicateObjectRefInput);
1587 }
1588 let command_input_objects: BTreeSet<InputObjectKind> = commands
1590 .iter()
1591 .flat_map(|command| command.input_objects())
1592 .collect();
1593 Ok(input_arg_objects
1594 .into_iter()
1595 .chain(command_input_objects)
1596 .collect())
1597 }
1598
1599 fn receiving_objects(&self) -> Vec<ObjectRef> {
1600 let ProgrammableTransaction { inputs, .. } = self;
1601 inputs
1602 .iter()
1603 .flat_map(|arg| arg.receiving_objects())
1604 .collect()
1605 }
1606
1607 fn validity_check(&self, config: &ProtocolConfig) -> UserInputResult {
1608 let ProgrammableTransaction { inputs, commands } = self;
1609 fp_ensure!(
1610 commands.len() < config.max_programmable_tx_commands() as usize,
1611 UserInputError::SizeLimitExceeded {
1612 limit: "maximum commands in a programmable transaction".to_string(),
1613 value: config.max_programmable_tx_commands().to_string()
1614 }
1615 );
1616 let total_inputs = self.input_objects()?.len() + self.receiving_objects().len();
1617 fp_ensure!(
1618 total_inputs <= config.max_input_objects() as usize,
1619 UserInputError::SizeLimitExceeded {
1620 limit: "maximum input + receiving objects in a transaction".to_string(),
1621 value: config.max_input_objects().to_string()
1622 }
1623 );
1624 for input in inputs {
1625 input.validity_check(config)?
1626 }
1627 if let Some(max_publish_commands) = config.max_publish_or_upgrade_per_ptb_as_option() {
1628 let publish_count = commands
1629 .iter()
1630 .filter(|c| matches!(c, Command::Publish(_, _) | Command::Upgrade(_, _, _, _)))
1631 .count() as u64;
1632 fp_ensure!(
1633 publish_count <= max_publish_commands,
1634 UserInputError::MaxPublishCountExceeded {
1635 max_publish_commands,
1636 publish_count,
1637 }
1638 );
1639 }
1640 for command in commands {
1641 command.validity_check(config)?;
1642 }
1643
1644 if let Some(random_index) = inputs.iter().position(|obj| {
1647 matches!(
1648 obj,
1649 CallArg::Object(ObjectArg::SharedObject { id, .. }) if *id == SUI_RANDOMNESS_STATE_OBJECT_ID
1650 )
1651 }) {
1652 fp_ensure!(
1653 config.random_beacon(),
1654 UserInputError::Unsupported(
1655 "randomness is not enabled on this network".to_string(),
1656 )
1657 );
1658 let mut used_random_object = false;
1659 let random_index = random_index.try_into().unwrap();
1660 for command in commands {
1661 if !used_random_object {
1662 used_random_object = command.is_input_arg_used(random_index);
1663 } else {
1664 fp_ensure!(
1665 matches!(
1666 command,
1667 Command::TransferObjects(_, _) | Command::MergeCoins(_, _)
1668 ),
1669 UserInputError::PostRandomCommandRestrictions
1670 );
1671 }
1672 }
1673 }
1674
1675 Ok(())
1676 }
1677
1678 pub fn coin_reservation_obj_refs(&self) -> impl Iterator<Item = ObjectRef> + '_ {
1680 self.inputs.iter().filter_map(|arg| match arg {
1681 CallArg::Object(ObjectArg::ImmOrOwnedObject(obj_ref))
1682 if ParsedDigest::is_coin_reservation_digest(&obj_ref.2) =>
1683 {
1684 Some(*obj_ref)
1685 }
1686 _ => None,
1687 })
1688 }
1689
1690 pub fn shared_input_objects(&self) -> impl Iterator<Item = SharedInputObject> + '_ {
1691 self.inputs.iter().filter_map(|arg| match arg {
1692 CallArg::Pure(_)
1693 | CallArg::Object(ObjectArg::Receiving(_))
1694 | CallArg::Object(ObjectArg::ImmOrOwnedObject(_))
1695 | CallArg::FundsWithdrawal(_) => None,
1696 CallArg::Object(ObjectArg::SharedObject {
1697 id,
1698 initial_shared_version,
1699 mutability,
1700 }) => Some(SharedInputObject {
1701 id: *id,
1702 initial_shared_version: *initial_shared_version,
1703 mutability: *mutability,
1704 }),
1705 })
1706 }
1707
1708 fn move_calls(&self) -> Vec<(usize, &ObjectID, &str, &str)> {
1709 self.commands
1710 .iter()
1711 .enumerate()
1712 .filter_map(|(idx, command)| match command {
1713 Command::MoveCall(m) => {
1714 Some((idx, &m.package, m.module.as_str(), m.function.as_str()))
1715 }
1716 _ => None,
1717 })
1718 .collect()
1719 }
1720
1721 pub fn non_system_packages_to_be_published(&self) -> impl Iterator<Item = &Vec<Vec<u8>>> + '_ {
1722 self.commands
1723 .iter()
1724 .filter_map(|q| q.non_system_packages_to_be_published())
1725 }
1726}
1727
1728impl Display for Argument {
1729 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
1730 match self {
1731 Argument::GasCoin => write!(f, "GasCoin"),
1732 Argument::Input(i) => write!(f, "Input({i})"),
1733 Argument::Result(i) => write!(f, "Result({i})"),
1734 Argument::NestedResult(i, j) => write!(f, "NestedResult({i},{j})"),
1735 }
1736 }
1737}
1738
1739impl Display for ProgrammableMoveCall {
1740 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
1741 let ProgrammableMoveCall {
1742 package,
1743 module,
1744 function,
1745 type_arguments,
1746 arguments,
1747 } = self;
1748 write!(f, "{package}::{module}::{function}")?;
1749 if !type_arguments.is_empty() {
1750 write!(f, "<")?;
1751 write_sep(f, type_arguments, ",")?;
1752 write!(f, ">")?;
1753 }
1754 write!(f, "(")?;
1755 write_sep(f, arguments, ",")?;
1756 write!(f, ")")
1757 }
1758}
1759
1760impl Display for Command {
1761 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
1762 match self {
1763 Command::MoveCall(p) => {
1764 write!(f, "MoveCall({p})")
1765 }
1766 Command::MakeMoveVec(ty_opt, elems) => {
1767 write!(f, "MakeMoveVec(")?;
1768 if let Some(ty) = ty_opt {
1769 write!(f, "Some{ty}")?;
1770 } else {
1771 write!(f, "None")?;
1772 }
1773 write!(f, ",[")?;
1774 write_sep(f, elems, ",")?;
1775 write!(f, "])")
1776 }
1777 Command::TransferObjects(objs, addr) => {
1778 write!(f, "TransferObjects([")?;
1779 write_sep(f, objs, ",")?;
1780 write!(f, "],{addr})")
1781 }
1782 Command::SplitCoins(coin, amounts) => {
1783 write!(f, "SplitCoins({coin}")?;
1784 write_sep(f, amounts, ",")?;
1785 write!(f, ")")
1786 }
1787 Command::MergeCoins(target, coins) => {
1788 write!(f, "MergeCoins({target},")?;
1789 write_sep(f, coins, ",")?;
1790 write!(f, ")")
1791 }
1792 Command::Publish(_bytes, deps) => {
1793 write!(f, "Publish(_,")?;
1794 write_sep(f, deps, ",")?;
1795 write!(f, ")")
1796 }
1797 Command::Upgrade(_bytes, deps, current_package_id, ticket) => {
1798 write!(f, "Upgrade(_,")?;
1799 write_sep(f, deps, ",")?;
1800 write!(f, ", {current_package_id}")?;
1801 write!(f, ", {ticket}")?;
1802 write!(f, ")")
1803 }
1804 }
1805 }
1806}
1807
1808impl Display for ProgrammableTransaction {
1809 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
1810 let ProgrammableTransaction { inputs, commands } = self;
1811 writeln!(f, "Inputs: {inputs:?}")?;
1812 writeln!(f, "Commands: [")?;
1813 for c in commands {
1814 writeln!(f, " {c},")?;
1815 }
1816 writeln!(f, "]")
1817 }
1818}
1819
1820#[derive(Debug, PartialEq, Eq)]
1821pub struct SharedInputObject {
1822 pub id: ObjectID,
1823 pub initial_shared_version: SequenceNumber,
1824 pub mutability: SharedObjectMutability,
1825}
1826
1827impl SharedInputObject {
1828 pub const SUI_SYSTEM_OBJ: Self = Self {
1829 id: SUI_SYSTEM_STATE_OBJECT_ID,
1830 initial_shared_version: SUI_SYSTEM_STATE_OBJECT_SHARED_VERSION,
1831 mutability: SharedObjectMutability::Mutable,
1832 };
1833
1834 pub fn id(&self) -> ObjectID {
1835 self.id
1836 }
1837
1838 pub fn id_and_version(&self) -> (ObjectID, SequenceNumber) {
1839 (self.id, self.initial_shared_version)
1840 }
1841
1842 pub fn into_id_and_version(self) -> (ObjectID, SequenceNumber) {
1843 (self.id, self.initial_shared_version)
1844 }
1845
1846 pub fn is_accessed_exclusively(&self) -> bool {
1847 self.mutability.is_exclusive()
1848 }
1849}
1850
1851impl TransactionKind {
1852 pub fn programmable(pt: ProgrammableTransaction) -> Self {
1855 TransactionKind::ProgrammableTransaction(pt)
1856 }
1857
1858 pub fn is_system_tx(&self) -> bool {
1859 match self {
1861 TransactionKind::ChangeEpoch(_)
1862 | TransactionKind::Genesis(_)
1863 | TransactionKind::ConsensusCommitPrologue(_)
1864 | TransactionKind::ConsensusCommitPrologueV2(_)
1865 | TransactionKind::ConsensusCommitPrologueV3(_)
1866 | TransactionKind::ConsensusCommitPrologueV4(_)
1867 | TransactionKind::AuthenticatorStateUpdate(_)
1868 | TransactionKind::RandomnessStateUpdate(_)
1869 | TransactionKind::EndOfEpochTransaction(_)
1870 | TransactionKind::ProgrammableSystemTransaction(_) => true,
1871 TransactionKind::ProgrammableTransaction(_) => false,
1872 }
1873 }
1874
1875 pub fn is_end_of_epoch_tx(&self) -> bool {
1876 matches!(
1877 self,
1878 TransactionKind::EndOfEpochTransaction(_) | TransactionKind::ChangeEpoch(_)
1879 )
1880 }
1881
1882 pub fn is_accumulator_barrier_settle_tx(&self) -> bool {
1883 matches!(self, TransactionKind::ProgrammableSystemTransaction(_))
1884 && self.shared_input_objects().any(|obj| {
1885 obj.id == SUI_ACCUMULATOR_ROOT_OBJECT_ID
1886 && obj.mutability == SharedObjectMutability::Mutable
1887 })
1888 }
1889
1890 pub fn accumulator_barrier_settlement_key(&self) -> Option<TransactionKey> {
1894 let TransactionKind::ProgrammableSystemTransaction(pt) = self else {
1895 return None;
1896 };
1897 let has_mutable_acc_root = pt.inputs.iter().any(|input| {
1898 matches!(
1899 input,
1900 CallArg::Object(ObjectArg::SharedObject {
1901 id,
1902 mutability: SharedObjectMutability::Mutable,
1903 ..
1904 }) if *id == SUI_ACCUMULATOR_ROOT_OBJECT_ID
1905 )
1906 });
1907 if !has_mutable_acc_root {
1908 return None;
1909 }
1910 let epoch = pt.inputs.get(1).and_then(|arg| match arg {
1913 CallArg::Pure(bytes) => bcs::from_bytes::<u64>(bytes).ok(),
1914 _ => None,
1915 })?;
1916 let checkpoint_height = pt.inputs.get(2).and_then(|arg| match arg {
1917 CallArg::Pure(bytes) => bcs::from_bytes::<u64>(bytes).ok(),
1918 _ => None,
1919 })?;
1920 Some(TransactionKey::AccumulatorSettlement(
1921 epoch,
1922 checkpoint_height,
1923 ))
1924 }
1925
1926 pub fn get_advance_epoch_tx_gas_summary(&self) -> Option<(u64, u64)> {
1930 let e = match self {
1931 Self::ChangeEpoch(e) => e,
1932 Self::EndOfEpochTransaction(txns) => {
1933 if let EndOfEpochTransactionKind::ChangeEpoch(e) =
1934 txns.last().expect("at least one end-of-epoch txn required")
1935 {
1936 e
1937 } else {
1938 panic!("final end-of-epoch txn must be ChangeEpoch")
1939 }
1940 }
1941 _ => return None,
1942 };
1943
1944 Some((e.computation_charge + e.storage_charge, e.storage_rebate))
1945 }
1946
1947 pub fn shared_input_objects(&self) -> impl Iterator<Item = SharedInputObject> + '_ {
1950 match &self {
1951 Self::ChangeEpoch(_) => {
1952 Either::Left(Either::Left(iter::once(SharedInputObject::SUI_SYSTEM_OBJ)))
1953 }
1954
1955 Self::ConsensusCommitPrologue(_)
1956 | Self::ConsensusCommitPrologueV2(_)
1957 | Self::ConsensusCommitPrologueV3(_)
1958 | Self::ConsensusCommitPrologueV4(_) => {
1959 Either::Left(Either::Left(iter::once(SharedInputObject {
1960 id: SUI_CLOCK_OBJECT_ID,
1961 initial_shared_version: SUI_CLOCK_OBJECT_SHARED_VERSION,
1962 mutability: SharedObjectMutability::Mutable,
1963 })))
1964 }
1965 Self::AuthenticatorStateUpdate(update) => {
1966 Either::Left(Either::Left(iter::once(SharedInputObject {
1967 id: SUI_AUTHENTICATOR_STATE_OBJECT_ID,
1968 initial_shared_version: update.authenticator_obj_initial_shared_version,
1969 mutability: SharedObjectMutability::Mutable,
1970 })))
1971 }
1972 Self::RandomnessStateUpdate(update) => {
1973 Either::Left(Either::Left(iter::once(SharedInputObject {
1974 id: SUI_RANDOMNESS_STATE_OBJECT_ID,
1975 initial_shared_version: update.randomness_obj_initial_shared_version,
1976 mutability: SharedObjectMutability::Mutable,
1977 })))
1978 }
1979 Self::EndOfEpochTransaction(txns) => Either::Left(Either::Right(
1980 txns.iter().flat_map(|txn| txn.shared_input_objects()),
1981 )),
1982 Self::ProgrammableTransaction(pt) | Self::ProgrammableSystemTransaction(pt) => {
1983 Either::Right(Either::Left(pt.shared_input_objects()))
1984 }
1985 Self::Genesis(_) => Either::Right(Either::Right(iter::empty())),
1986 }
1987 }
1988
1989 fn move_calls(&self) -> Vec<(usize, &ObjectID, &str, &str)> {
1990 match &self {
1991 Self::ProgrammableTransaction(pt) => pt.move_calls(),
1992 _ => vec![],
1993 }
1994 }
1995
1996 pub fn receiving_objects(&self) -> Vec<ObjectRef> {
1997 match &self {
1998 TransactionKind::ChangeEpoch(_)
1999 | TransactionKind::Genesis(_)
2000 | TransactionKind::ConsensusCommitPrologue(_)
2001 | TransactionKind::ConsensusCommitPrologueV2(_)
2002 | TransactionKind::ConsensusCommitPrologueV3(_)
2003 | TransactionKind::ConsensusCommitPrologueV4(_)
2004 | TransactionKind::AuthenticatorStateUpdate(_)
2005 | TransactionKind::RandomnessStateUpdate(_)
2006 | TransactionKind::EndOfEpochTransaction(_)
2007 | TransactionKind::ProgrammableSystemTransaction(_) => vec![],
2008 TransactionKind::ProgrammableTransaction(pt) => pt.receiving_objects(),
2009 }
2010 }
2011
2012 pub fn input_objects(&self) -> UserInputResult<Vec<InputObjectKind>> {
2017 let input_objects = match &self {
2018 Self::ChangeEpoch(_) => {
2019 vec![InputObjectKind::SharedMoveObject {
2020 id: SUI_SYSTEM_STATE_OBJECT_ID,
2021 initial_shared_version: SUI_SYSTEM_STATE_OBJECT_SHARED_VERSION,
2022 mutability: SharedObjectMutability::Mutable,
2023 }]
2024 }
2025 Self::Genesis(_) => {
2026 vec![]
2027 }
2028 Self::ConsensusCommitPrologue(_)
2029 | Self::ConsensusCommitPrologueV2(_)
2030 | Self::ConsensusCommitPrologueV3(_)
2031 | Self::ConsensusCommitPrologueV4(_) => {
2032 vec![InputObjectKind::SharedMoveObject {
2033 id: SUI_CLOCK_OBJECT_ID,
2034 initial_shared_version: SUI_CLOCK_OBJECT_SHARED_VERSION,
2035 mutability: SharedObjectMutability::Mutable,
2036 }]
2037 }
2038 Self::AuthenticatorStateUpdate(update) => {
2039 vec![InputObjectKind::SharedMoveObject {
2040 id: SUI_AUTHENTICATOR_STATE_OBJECT_ID,
2041 initial_shared_version: update.authenticator_obj_initial_shared_version(),
2042 mutability: SharedObjectMutability::Mutable,
2043 }]
2044 }
2045 Self::RandomnessStateUpdate(update) => {
2046 vec![InputObjectKind::SharedMoveObject {
2047 id: SUI_RANDOMNESS_STATE_OBJECT_ID,
2048 initial_shared_version: update.randomness_obj_initial_shared_version(),
2049 mutability: SharedObjectMutability::Mutable,
2050 }]
2051 }
2052 Self::EndOfEpochTransaction(txns) => {
2053 let before_dedup: Vec<_> =
2056 txns.iter().flat_map(|txn| txn.input_objects()).collect();
2057 let mut has_seen = HashSet::new();
2058 let mut after_dedup = vec![];
2059 for obj in before_dedup {
2060 if has_seen.insert(obj) {
2061 after_dedup.push(obj);
2062 }
2063 }
2064 after_dedup
2065 }
2066 Self::ProgrammableTransaction(p) | Self::ProgrammableSystemTransaction(p) => {
2067 return p.input_objects();
2068 }
2069 };
2070 let mut used = HashSet::new();
2078 if !input_objects.iter().all(|o| used.insert(o.object_id())) {
2079 return Err(UserInputError::DuplicateObjectRefInput);
2080 }
2081 Ok(input_objects)
2082 }
2083
2084 fn get_funds_withdrawals<'a>(&'a self) -> impl Iterator<Item = &'a FundsWithdrawalArg> + 'a {
2085 let TransactionKind::ProgrammableTransaction(pt) = &self else {
2086 return Either::Left(iter::empty());
2087 };
2088 Either::Right(pt.inputs.iter().filter_map(|input| {
2089 if let CallArg::FundsWithdrawal(withdraw) = input {
2090 Some(withdraw)
2091 } else {
2092 None
2093 }
2094 }))
2095 }
2096
2097 pub fn get_coin_reservation_obj_refs(&self) -> impl Iterator<Item = ObjectRef> + '_ {
2098 let TransactionKind::ProgrammableTransaction(pt) = &self else {
2099 return Either::Left(iter::empty());
2100 };
2101 Either::Right(pt.coin_reservation_obj_refs())
2102 }
2103
2104 pub fn has_coin_reservations(&self) -> bool {
2105 self.get_coin_reservation_obj_refs().next().is_some()
2106 }
2107
2108 pub fn validity_check(&self, config: &ProtocolConfig) -> UserInputResult {
2109 match self {
2110 TransactionKind::ProgrammableTransaction(p) => p.validity_check(config)?,
2111 TransactionKind::ChangeEpoch(_)
2114 | TransactionKind::Genesis(_)
2115 | TransactionKind::ConsensusCommitPrologue(_) => (),
2116 TransactionKind::ConsensusCommitPrologueV2(_) => {
2117 if !config.include_consensus_digest_in_prologue() {
2118 return Err(UserInputError::Unsupported(
2119 "ConsensusCommitPrologueV2 is not supported".to_string(),
2120 ));
2121 }
2122 }
2123 TransactionKind::ConsensusCommitPrologueV3(_) => {
2124 if !config.record_consensus_determined_version_assignments_in_prologue() {
2125 return Err(UserInputError::Unsupported(
2126 "ConsensusCommitPrologueV3 is not supported".to_string(),
2127 ));
2128 }
2129 }
2130 TransactionKind::ConsensusCommitPrologueV4(_) => {
2131 if !config.record_additional_state_digest_in_prologue() {
2132 return Err(UserInputError::Unsupported(
2133 "ConsensusCommitPrologueV4 is not supported".to_string(),
2134 ));
2135 }
2136 }
2137 TransactionKind::EndOfEpochTransaction(txns) => {
2138 if !config.end_of_epoch_transaction_supported() {
2139 return Err(UserInputError::Unsupported(
2140 "EndOfEpochTransaction is not supported".to_string(),
2141 ));
2142 }
2143
2144 for tx in txns {
2145 tx.validity_check(config)?;
2146 }
2147 }
2148
2149 TransactionKind::AuthenticatorStateUpdate(_) => {
2150 if !config.enable_jwk_consensus_updates() {
2151 return Err(UserInputError::Unsupported(
2152 "authenticator state updates not enabled".to_string(),
2153 ));
2154 }
2155 }
2156 TransactionKind::RandomnessStateUpdate(_) => {
2157 if !config.random_beacon() {
2158 return Err(UserInputError::Unsupported(
2159 "randomness state updates not enabled".to_string(),
2160 ));
2161 }
2162 }
2163 TransactionKind::ProgrammableSystemTransaction(_) => {
2164 if !config.enable_accumulators() {
2165 return Err(UserInputError::Unsupported(
2166 "accumulators not enabled".to_string(),
2167 ));
2168 }
2169 }
2170 };
2171 Ok(())
2172 }
2173
2174 pub fn num_commands(&self) -> usize {
2176 match self {
2177 TransactionKind::ProgrammableTransaction(pt) => pt.commands.len(),
2178 _ => 0,
2179 }
2180 }
2181
2182 pub fn iter_commands(&self) -> impl Iterator<Item = &Command> {
2183 match self {
2184 TransactionKind::ProgrammableTransaction(pt) => pt.commands.iter(),
2185 _ => [].iter(),
2186 }
2187 }
2188
2189 pub fn tx_count(&self) -> usize {
2191 match self {
2192 TransactionKind::ProgrammableTransaction(pt) => pt.commands.len(),
2193 _ => 1,
2194 }
2195 }
2196
2197 pub fn name(&self) -> &'static str {
2198 match self {
2199 Self::ChangeEpoch(_) => "ChangeEpoch",
2200 Self::Genesis(_) => "Genesis",
2201 Self::ConsensusCommitPrologue(_) => "ConsensusCommitPrologue",
2202 Self::ConsensusCommitPrologueV2(_) => "ConsensusCommitPrologueV2",
2203 Self::ConsensusCommitPrologueV3(_) => "ConsensusCommitPrologueV3",
2204 Self::ConsensusCommitPrologueV4(_) => "ConsensusCommitPrologueV4",
2205 Self::ProgrammableTransaction(_) => "ProgrammableTransaction",
2206 Self::ProgrammableSystemTransaction(_) => "ProgrammableSystemTransaction",
2207 Self::AuthenticatorStateUpdate(_) => "AuthenticatorStateUpdate",
2208 Self::RandomnessStateUpdate(_) => "RandomnessStateUpdate",
2209 Self::EndOfEpochTransaction(_) => "EndOfEpochTransaction",
2210 }
2211 }
2212}
2213
2214impl Display for TransactionKind {
2215 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
2216 let mut writer = String::new();
2217 match &self {
2218 Self::ChangeEpoch(e) => {
2219 writeln!(writer, "Transaction Kind : Epoch Change")?;
2220 writeln!(writer, "New epoch ID : {}", e.epoch)?;
2221 writeln!(writer, "Storage gas reward : {}", e.storage_charge)?;
2222 writeln!(writer, "Computation gas reward : {}", e.computation_charge)?;
2223 writeln!(writer, "Storage rebate : {}", e.storage_rebate)?;
2224 writeln!(writer, "Timestamp : {}", e.epoch_start_timestamp_ms)?;
2225 }
2226 Self::Genesis(_) => {
2227 writeln!(writer, "Transaction Kind : Genesis")?;
2228 }
2229 Self::ConsensusCommitPrologue(p) => {
2230 writeln!(writer, "Transaction Kind : Consensus Commit Prologue")?;
2231 writeln!(writer, "Timestamp : {}", p.commit_timestamp_ms)?;
2232 }
2233 Self::ConsensusCommitPrologueV2(p) => {
2234 writeln!(writer, "Transaction Kind : Consensus Commit Prologue V2")?;
2235 writeln!(writer, "Timestamp : {}", p.commit_timestamp_ms)?;
2236 writeln!(writer, "Consensus Digest: {}", p.consensus_commit_digest)?;
2237 }
2238 Self::ConsensusCommitPrologueV3(p) => {
2239 writeln!(writer, "Transaction Kind : Consensus Commit Prologue V3")?;
2240 writeln!(writer, "Timestamp : {}", p.commit_timestamp_ms)?;
2241 writeln!(writer, "Consensus Digest: {}", p.consensus_commit_digest)?;
2242 writeln!(
2243 writer,
2244 "Consensus determined version assignment: {:?}",
2245 p.consensus_determined_version_assignments
2246 )?;
2247 }
2248 Self::ConsensusCommitPrologueV4(p) => {
2249 writeln!(writer, "Transaction Kind : Consensus Commit Prologue V4")?;
2250 writeln!(writer, "Timestamp : {}", p.commit_timestamp_ms)?;
2251 writeln!(writer, "Consensus Digest: {}", p.consensus_commit_digest)?;
2252 writeln!(
2253 writer,
2254 "Consensus determined version assignment: {:?}",
2255 p.consensus_determined_version_assignments
2256 )?;
2257 writeln!(
2258 writer,
2259 "Additional State Digest: {}",
2260 p.additional_state_digest
2261 )?;
2262 }
2263 Self::ProgrammableTransaction(p) => {
2264 writeln!(writer, "Transaction Kind : Programmable")?;
2265 write!(writer, "{p}")?;
2266 }
2267 Self::ProgrammableSystemTransaction(p) => {
2268 writeln!(writer, "Transaction Kind : Programmable System")?;
2269 write!(writer, "{p}")?;
2270 }
2271 Self::AuthenticatorStateUpdate(_) => {
2272 writeln!(writer, "Transaction Kind : Authenticator State Update")?;
2273 }
2274 Self::RandomnessStateUpdate(_) => {
2275 writeln!(writer, "Transaction Kind : Randomness State Update")?;
2276 }
2277 Self::EndOfEpochTransaction(_) => {
2278 writeln!(writer, "Transaction Kind : End of Epoch Transaction")?;
2279 }
2280 }
2281 write!(f, "{}", writer)
2282 }
2283}
2284
2285#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)]
2286pub struct GasData {
2287 pub payment: Vec<ObjectRef>,
2288 pub owner: SuiAddress,
2289 pub price: u64,
2290 pub budget: u64,
2291}
2292
2293impl GasData {
2294 pub fn is_unmetered(&self) -> bool {
2295 self.payment.len() == 1
2296 && self.payment[0].0 == ObjectID::ZERO
2297 && self.payment[0].1 == SequenceNumber::default()
2298 && self.payment[0].2 == ObjectDigest::MIN
2299 }
2300}
2301
2302pub fn is_gas_paid_from_address_balance(
2303 gas_data: &GasData,
2304 transaction_kind: &TransactionKind,
2305) -> bool {
2306 gas_data.payment.is_empty()
2307 && matches!(
2308 transaction_kind,
2309 TransactionKind::ProgrammableTransaction(_)
2310 )
2311}
2312
2313pub fn is_gasless_transaction(gas_data: &GasData, transaction_kind: &TransactionKind) -> bool {
2314 is_gas_paid_from_address_balance(gas_data, transaction_kind) && gas_data.price == 0
2315}
2316
2317#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy, Serialize, Deserialize)]
2318pub enum TransactionExpiration {
2319 None,
2321 Epoch(EpochId),
2324 ValidDuring {
2334 min_epoch: Option<EpochId>,
2336 max_epoch: Option<EpochId>,
2338 min_timestamp: Option<u64>,
2340 max_timestamp: Option<u64>,
2342 chain: ChainIdentifier,
2344 nonce: u32,
2346 },
2347}
2348
2349impl TransactionExpiration {
2350 pub fn is_replay_protected(&self) -> bool {
2355 matches!(self, TransactionExpiration::ValidDuring {
2356 min_epoch: Some(min_epoch),
2357 max_epoch: Some(max_epoch),
2358 ..
2359 } if *max_epoch == *min_epoch || *max_epoch == min_epoch.saturating_add(1))
2360 }
2361}
2362
2363#[enum_dispatch(TransactionDataAPI)]
2364#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)]
2365pub enum TransactionData {
2366 V1(TransactionDataV1),
2367 }
2370
2371#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)]
2372pub struct TransactionDataV1 {
2373 pub kind: TransactionKind,
2374 pub sender: SuiAddress,
2375 pub gas_data: GasData,
2376 pub expiration: TransactionExpiration,
2377}
2378
2379impl TransactionData {
2380 pub fn as_v1(&self) -> &TransactionDataV1 {
2381 match self {
2382 TransactionData::V1(v1) => v1,
2383 }
2384 }
2385 fn new_system_transaction(kind: TransactionKind) -> Self {
2386 assert!(kind.is_system_tx());
2388 let sender = SuiAddress::default();
2389 TransactionData::V1(TransactionDataV1 {
2390 kind,
2391 sender,
2392 gas_data: GasData {
2393 price: GAS_PRICE_FOR_SYSTEM_TX,
2394 owner: sender,
2395 payment: vec![(ObjectID::ZERO, SequenceNumber::default(), ObjectDigest::MIN)],
2396 budget: 0,
2397 },
2398 expiration: TransactionExpiration::None,
2399 })
2400 }
2401
2402 pub fn new(
2403 kind: TransactionKind,
2404 sender: SuiAddress,
2405 gas_payment: ObjectRef,
2406 gas_budget: u64,
2407 gas_price: u64,
2408 ) -> Self {
2409 TransactionData::V1(TransactionDataV1 {
2410 kind,
2411 sender,
2412 gas_data: GasData {
2413 price: gas_price,
2414 owner: sender,
2415 payment: vec![gas_payment],
2416 budget: gas_budget,
2417 },
2418 expiration: TransactionExpiration::None,
2419 })
2420 }
2421
2422 pub fn new_with_gas_coins(
2423 kind: TransactionKind,
2424 sender: SuiAddress,
2425 gas_payment: Vec<ObjectRef>,
2426 gas_budget: u64,
2427 gas_price: u64,
2428 ) -> Self {
2429 Self::new_with_gas_coins_allow_sponsor(
2430 kind,
2431 sender,
2432 gas_payment,
2433 gas_budget,
2434 gas_price,
2435 sender,
2436 )
2437 }
2438
2439 pub fn new_with_gas_coins_allow_sponsor(
2440 kind: TransactionKind,
2441 sender: SuiAddress,
2442 gas_payment: Vec<ObjectRef>,
2443 gas_budget: u64,
2444 gas_price: u64,
2445 gas_sponsor: SuiAddress,
2446 ) -> Self {
2447 TransactionData::V1(TransactionDataV1 {
2448 kind,
2449 sender,
2450 gas_data: GasData {
2451 price: gas_price,
2452 owner: gas_sponsor,
2453 payment: gas_payment,
2454 budget: gas_budget,
2455 },
2456 expiration: TransactionExpiration::None,
2457 })
2458 }
2459
2460 pub fn new_with_gas_data(kind: TransactionKind, sender: SuiAddress, gas_data: GasData) -> Self {
2461 TransactionData::V1(TransactionDataV1 {
2462 kind,
2463 sender,
2464 gas_data,
2465 expiration: TransactionExpiration::None,
2466 })
2467 }
2468
2469 pub fn new_with_gas_data_and_expiration(
2470 kind: TransactionKind,
2471 sender: SuiAddress,
2472 gas_data: GasData,
2473 expiration: TransactionExpiration,
2474 ) -> Self {
2475 TransactionData::V1(TransactionDataV1 {
2476 kind,
2477 sender,
2478 gas_data,
2479 expiration,
2480 })
2481 }
2482
2483 pub fn new_move_call(
2484 sender: SuiAddress,
2485 package: ObjectID,
2486 module: Identifier,
2487 function: Identifier,
2488 type_arguments: Vec<TypeTag>,
2489 gas_payment: ObjectRef,
2490 arguments: Vec<CallArg>,
2491 gas_budget: u64,
2492 gas_price: u64,
2493 ) -> anyhow::Result<Self> {
2494 Self::new_move_call_with_gas_coins(
2495 sender,
2496 package,
2497 module,
2498 function,
2499 type_arguments,
2500 vec![gas_payment],
2501 arguments,
2502 gas_budget,
2503 gas_price,
2504 )
2505 }
2506
2507 pub fn new_move_call_with_gas_coins(
2508 sender: SuiAddress,
2509 package: ObjectID,
2510 module: Identifier,
2511 function: Identifier,
2512 type_arguments: Vec<TypeTag>,
2513 gas_payment: Vec<ObjectRef>,
2514 arguments: Vec<CallArg>,
2515 gas_budget: u64,
2516 gas_price: u64,
2517 ) -> anyhow::Result<Self> {
2518 let pt = {
2519 let mut builder = ProgrammableTransactionBuilder::new();
2520 builder.move_call(package, module, function, type_arguments, arguments)?;
2521 builder.finish()
2522 };
2523 Ok(Self::new_programmable(
2524 sender,
2525 gas_payment,
2526 pt,
2527 gas_budget,
2528 gas_price,
2529 ))
2530 }
2531
2532 pub fn new_transfer(
2533 recipient: SuiAddress,
2534 full_object_ref: FullObjectRef,
2535 sender: SuiAddress,
2536 gas_payment: ObjectRef,
2537 gas_budget: u64,
2538 gas_price: u64,
2539 ) -> Self {
2540 let pt = {
2541 let mut builder = ProgrammableTransactionBuilder::new();
2542 builder.transfer_object(recipient, full_object_ref).unwrap();
2543 builder.finish()
2544 };
2545 Self::new_programmable(sender, vec![gas_payment], pt, gas_budget, gas_price)
2546 }
2547
2548 pub fn new_transfer_sui(
2549 recipient: SuiAddress,
2550 sender: SuiAddress,
2551 amount: Option<u64>,
2552 gas_payment: ObjectRef,
2553 gas_budget: u64,
2554 gas_price: u64,
2555 ) -> Self {
2556 Self::new_transfer_sui_allow_sponsor(
2557 recipient,
2558 sender,
2559 amount,
2560 gas_payment,
2561 gas_budget,
2562 gas_price,
2563 sender,
2564 )
2565 }
2566
2567 pub fn new_transfer_sui_allow_sponsor(
2568 recipient: SuiAddress,
2569 sender: SuiAddress,
2570 amount: Option<u64>,
2571 gas_payment: ObjectRef,
2572 gas_budget: u64,
2573 gas_price: u64,
2574 gas_sponsor: SuiAddress,
2575 ) -> Self {
2576 let pt = {
2577 let mut builder = ProgrammableTransactionBuilder::new();
2578 builder.transfer_sui(recipient, amount);
2579 builder.finish()
2580 };
2581 Self::new_programmable_allow_sponsor(
2582 sender,
2583 vec![gas_payment],
2584 pt,
2585 gas_budget,
2586 gas_price,
2587 gas_sponsor,
2588 )
2589 }
2590
2591 pub fn new_pay(
2592 sender: SuiAddress,
2593 coins: Vec<ObjectRef>,
2594 recipients: Vec<SuiAddress>,
2595 amounts: Vec<u64>,
2596 gas_payment: ObjectRef,
2597 gas_budget: u64,
2598 gas_price: u64,
2599 ) -> anyhow::Result<Self> {
2600 let pt = {
2601 let mut builder = ProgrammableTransactionBuilder::new();
2602 builder.pay(coins, recipients, amounts)?;
2603 builder.finish()
2604 };
2605 Ok(Self::new_programmable(
2606 sender,
2607 vec![gas_payment],
2608 pt,
2609 gas_budget,
2610 gas_price,
2611 ))
2612 }
2613
2614 pub fn new_pay_sui(
2615 sender: SuiAddress,
2616 mut coins: Vec<ObjectRef>,
2617 recipients: Vec<SuiAddress>,
2618 amounts: Vec<u64>,
2619 gas_payment: ObjectRef,
2620 gas_budget: u64,
2621 gas_price: u64,
2622 ) -> anyhow::Result<Self> {
2623 coins.insert(0, gas_payment);
2624 let pt = {
2625 let mut builder = ProgrammableTransactionBuilder::new();
2626 builder.pay_sui(recipients, amounts)?;
2627 builder.finish()
2628 };
2629 Ok(Self::new_programmable(
2630 sender, coins, pt, gas_budget, gas_price,
2631 ))
2632 }
2633
2634 pub fn new_pay_all_sui(
2635 sender: SuiAddress,
2636 mut coins: Vec<ObjectRef>,
2637 recipient: SuiAddress,
2638 gas_payment: ObjectRef,
2639 gas_budget: u64,
2640 gas_price: u64,
2641 ) -> Self {
2642 coins.insert(0, gas_payment);
2643 let pt = {
2644 let mut builder = ProgrammableTransactionBuilder::new();
2645 builder.pay_all_sui(recipient);
2646 builder.finish()
2647 };
2648 Self::new_programmable(sender, coins, pt, gas_budget, gas_price)
2649 }
2650
2651 pub fn new_split_coin(
2652 sender: SuiAddress,
2653 coin: ObjectRef,
2654 amounts: Vec<u64>,
2655 gas_payment: ObjectRef,
2656 gas_budget: u64,
2657 gas_price: u64,
2658 ) -> Self {
2659 let pt = {
2660 let mut builder = ProgrammableTransactionBuilder::new();
2661 builder.split_coin(sender, coin, amounts);
2662 builder.finish()
2663 };
2664 Self::new_programmable(sender, vec![gas_payment], pt, gas_budget, gas_price)
2665 }
2666
2667 pub fn new_module(
2668 sender: SuiAddress,
2669 gas_payment: ObjectRef,
2670 modules: Vec<Vec<u8>>,
2671 dep_ids: Vec<ObjectID>,
2672 gas_budget: u64,
2673 gas_price: u64,
2674 ) -> Self {
2675 let pt = {
2676 let mut builder = ProgrammableTransactionBuilder::new();
2677 let upgrade_cap = builder.publish_upgradeable(modules, dep_ids);
2678 builder.transfer_arg(sender, upgrade_cap);
2679 builder.finish()
2680 };
2681 Self::new_programmable(sender, vec![gas_payment], pt, gas_budget, gas_price)
2682 }
2683
2684 pub fn new_upgrade(
2685 sender: SuiAddress,
2686 gas_payment: ObjectRef,
2687 package_id: ObjectID,
2688 modules: Vec<Vec<u8>>,
2689 dep_ids: Vec<ObjectID>,
2690 (upgrade_capability, capability_owner): (ObjectRef, Owner),
2691 upgrade_policy: u8,
2692 digest: Vec<u8>,
2693 gas_budget: u64,
2694 gas_price: u64,
2695 ) -> anyhow::Result<Self> {
2696 let pt = {
2697 let mut builder = ProgrammableTransactionBuilder::new();
2698 let capability_arg = match capability_owner {
2699 Owner::AddressOwner(_) => ObjectArg::ImmOrOwnedObject(upgrade_capability),
2700 Owner::Shared {
2701 initial_shared_version,
2702 }
2703 | Owner::ConsensusAddressOwner {
2704 start_version: initial_shared_version,
2705 ..
2706 } => ObjectArg::SharedObject {
2707 id: upgrade_capability.0,
2708 initial_shared_version,
2709 mutability: SharedObjectMutability::Mutable,
2710 },
2711 Owner::Immutable => {
2712 return Err(anyhow::anyhow!(
2713 "Upgrade capability is stored immutably and cannot be used for upgrades"
2714 ));
2715 }
2716 Owner::ObjectOwner(_) => {
2719 return Err(anyhow::anyhow!("Upgrade capability controlled by object"));
2720 }
2721 };
2722 builder.obj(capability_arg).unwrap();
2723 let upgrade_arg = builder.pure(upgrade_policy).unwrap();
2724 let digest_arg = builder.pure(digest).unwrap();
2725 let upgrade_ticket = builder.programmable_move_call(
2726 SUI_FRAMEWORK_PACKAGE_ID,
2727 ident_str!("package").to_owned(),
2728 ident_str!("authorize_upgrade").to_owned(),
2729 vec![],
2730 vec![Argument::Input(0), upgrade_arg, digest_arg],
2731 );
2732 let upgrade_receipt = builder.upgrade(package_id, upgrade_ticket, dep_ids, modules);
2733
2734 builder.programmable_move_call(
2735 SUI_FRAMEWORK_PACKAGE_ID,
2736 ident_str!("package").to_owned(),
2737 ident_str!("commit_upgrade").to_owned(),
2738 vec![],
2739 vec![Argument::Input(0), upgrade_receipt],
2740 );
2741
2742 builder.finish()
2743 };
2744 Ok(Self::new_programmable(
2745 sender,
2746 vec![gas_payment],
2747 pt,
2748 gas_budget,
2749 gas_price,
2750 ))
2751 }
2752
2753 pub fn new_programmable(
2754 sender: SuiAddress,
2755 gas_payment: Vec<ObjectRef>,
2756 pt: ProgrammableTransaction,
2757 gas_budget: u64,
2758 gas_price: u64,
2759 ) -> Self {
2760 Self::new_programmable_allow_sponsor(sender, gas_payment, pt, gas_budget, gas_price, sender)
2761 }
2762
2763 pub fn new_programmable_allow_sponsor(
2764 sender: SuiAddress,
2765 gas_payment: Vec<ObjectRef>,
2766 pt: ProgrammableTransaction,
2767 gas_budget: u64,
2768 gas_price: u64,
2769 sponsor: SuiAddress,
2770 ) -> Self {
2771 let kind = TransactionKind::ProgrammableTransaction(pt);
2772 Self::new_with_gas_coins_allow_sponsor(
2773 kind,
2774 sender,
2775 gas_payment,
2776 gas_budget,
2777 gas_price,
2778 sponsor,
2779 )
2780 }
2781
2782 pub fn new_programmable_with_address_balance_gas(
2783 sender: SuiAddress,
2784 pt: ProgrammableTransaction,
2785 gas_budget: u64,
2786 gas_price: u64,
2787 chain_identifier: ChainIdentifier,
2788 current_epoch: EpochId,
2789 nonce: u32,
2790 ) -> Self {
2791 TransactionData::V1(TransactionDataV1 {
2792 kind: TransactionKind::ProgrammableTransaction(pt),
2793 sender,
2794 gas_data: GasData {
2795 payment: vec![],
2796 owner: sender,
2797 price: gas_price,
2798 budget: gas_budget,
2799 },
2800 expiration: TransactionExpiration::ValidDuring {
2801 min_epoch: Some(current_epoch),
2802 max_epoch: Some(current_epoch + 1),
2803 min_timestamp: None,
2804 max_timestamp: None,
2805 chain: chain_identifier,
2806 nonce,
2807 },
2808 })
2809 }
2810
2811 pub fn message_version(&self) -> u64 {
2812 match self {
2813 TransactionData::V1(_) => 1,
2814 }
2815 }
2816
2817 pub fn execution_parts(&self) -> (TransactionKind, SuiAddress, GasData) {
2818 (self.kind().clone(), self.sender(), self.gas_data().clone())
2819 }
2820
2821 pub fn uses_randomness(&self) -> bool {
2822 self.kind()
2823 .shared_input_objects()
2824 .any(|obj| obj.id() == SUI_RANDOMNESS_STATE_OBJECT_ID)
2825 }
2826
2827 pub fn digest(&self) -> TransactionDigest {
2828 TransactionDigest::new(default_hash(self))
2829 }
2830}
2831
2832#[enum_dispatch]
2833pub trait TransactionDataAPI {
2834 fn sender(&self) -> SuiAddress;
2835
2836 fn kind(&self) -> &TransactionKind;
2839
2840 fn kind_mut(&mut self) -> &mut TransactionKind;
2842
2843 fn into_kind(self) -> TransactionKind;
2845
2846 fn required_signers(&self) -> NonEmpty<SuiAddress>;
2848
2849 fn gas_data(&self) -> &GasData;
2850
2851 fn gas_owner(&self) -> SuiAddress;
2852
2853 fn gas(&self) -> &[ObjectRef];
2854
2855 fn gas_price(&self) -> u64;
2856
2857 fn gas_budget(&self) -> u64;
2858
2859 fn expiration(&self) -> &TransactionExpiration;
2860
2861 fn expiration_mut(&mut self) -> &mut TransactionExpiration;
2862
2863 fn move_calls(&self) -> Vec<(usize, &ObjectID, &str, &str)>;
2864
2865 fn input_objects(&self) -> UserInputResult<Vec<InputObjectKind>>;
2866
2867 fn shared_input_objects(&self) -> Vec<SharedInputObject>;
2868
2869 fn receiving_objects(&self) -> Vec<ObjectRef>;
2870
2871 fn fastpath_dependency_objects(
2875 &self,
2876 ) -> UserInputResult<(Vec<ObjectRef>, Vec<ObjectID>, Vec<ObjectRef>)>;
2877
2878 fn process_funds_withdrawals_for_signing(
2886 &self,
2887 chain_identifier: ChainIdentifier,
2888 coin_resolver: &dyn CoinReservationResolverTrait,
2889 ) -> UserInputResult<BTreeMap<AccumulatorObjId, (u64, TypeTag)>>;
2890
2891 fn process_funds_withdrawals_for_estimation(
2895 &self,
2896 chain_identifier: ChainIdentifier,
2897 coin_resolver: &dyn CoinReservationResolverTrait,
2898 ) -> UserInputResult<BTreeMap<AccumulatorObjId, (u64, TypeTag)>>;
2899
2900 fn process_funds_withdrawals_for_execution(
2903 &self,
2904 chain_identifier: ChainIdentifier,
2905 ) -> BTreeMap<AccumulatorObjId, u64>;
2906
2907 fn has_funds_withdrawals(&self) -> bool;
2909
2910 fn coin_reservation_obj_refs(
2911 &self,
2912 chain_identifier: ChainIdentifier,
2913 ) -> Vec<ParsedObjectRefWithdrawal>;
2914
2915 fn validity_check(&self, context: &TxValidityCheckContext<'_>) -> SuiResult;
2916
2917 fn validity_check_no_gas_check(&self, config: &ProtocolConfig) -> UserInputResult;
2918
2919 fn check_sponsorship(&self) -> UserInputResult;
2921
2922 fn is_system_tx(&self) -> bool;
2923 fn is_genesis_tx(&self) -> bool;
2924
2925 fn is_end_of_epoch_tx(&self) -> bool;
2928
2929 fn is_consensus_commit_prologue(&self) -> bool;
2930
2931 fn is_sponsored_tx(&self) -> bool;
2933
2934 fn is_gas_paid_from_address_balance(&self) -> bool;
2935
2936 fn is_gasless_transaction(&self) -> bool;
2937
2938 fn sender_mut_for_testing(&mut self) -> &mut SuiAddress;
2939
2940 fn gas_data_mut(&mut self) -> &mut GasData;
2941
2942 fn expiration_mut_for_testing(&mut self) -> &mut TransactionExpiration;
2944}
2945
2946impl TransactionDataAPI for TransactionDataV1 {
2947 fn sender(&self) -> SuiAddress {
2948 self.sender
2949 }
2950
2951 fn kind(&self) -> &TransactionKind {
2952 &self.kind
2953 }
2954
2955 fn kind_mut(&mut self) -> &mut TransactionKind {
2956 &mut self.kind
2957 }
2958
2959 fn into_kind(self) -> TransactionKind {
2960 self.kind
2961 }
2962
2963 fn required_signers(&self) -> NonEmpty<SuiAddress> {
2965 let mut signers = nonempty![self.sender];
2966 if self.gas_owner() != self.sender {
2967 signers.push(self.gas_owner());
2968 }
2969 signers
2970 }
2971
2972 fn gas_data(&self) -> &GasData {
2973 &self.gas_data
2974 }
2975
2976 fn gas_owner(&self) -> SuiAddress {
2977 self.gas_data.owner
2978 }
2979
2980 fn gas(&self) -> &[ObjectRef] {
2981 &self.gas_data.payment
2982 }
2983
2984 fn gas_price(&self) -> u64 {
2985 self.gas_data.price
2986 }
2987
2988 fn gas_budget(&self) -> u64 {
2989 self.gas_data.budget
2990 }
2991
2992 fn expiration(&self) -> &TransactionExpiration {
2993 &self.expiration
2994 }
2995
2996 fn expiration_mut(&mut self) -> &mut TransactionExpiration {
2997 &mut self.expiration
2998 }
2999
3000 fn move_calls(&self) -> Vec<(usize, &ObjectID, &str, &str)> {
3001 self.kind.move_calls()
3002 }
3003
3004 fn input_objects(&self) -> UserInputResult<Vec<InputObjectKind>> {
3005 let mut inputs = self.kind.input_objects()?;
3006
3007 if !self.kind.is_system_tx() {
3008 inputs.extend(
3009 self.gas()
3010 .iter()
3011 .filter(|obj_ref| !ParsedDigest::is_coin_reservation_digest(&obj_ref.2))
3012 .map(|obj_ref| InputObjectKind::ImmOrOwnedMoveObject(*obj_ref)),
3013 );
3014 }
3015 Ok(inputs)
3016 }
3017
3018 fn shared_input_objects(&self) -> Vec<SharedInputObject> {
3019 self.kind.shared_input_objects().collect()
3020 }
3021
3022 fn receiving_objects(&self) -> Vec<ObjectRef> {
3023 self.kind.receiving_objects()
3024 }
3025
3026 fn fastpath_dependency_objects(
3027 &self,
3028 ) -> UserInputResult<(Vec<ObjectRef>, Vec<ObjectID>, Vec<ObjectRef>)> {
3029 let mut move_objects = vec![];
3030 let mut packages = vec![];
3031 let mut receiving_objects = vec![];
3032 self.input_objects()?.iter().for_each(|o| match o {
3033 InputObjectKind::ImmOrOwnedMoveObject(object_ref) => {
3034 move_objects.push(*object_ref);
3035 }
3036 InputObjectKind::MovePackage(package_id) => {
3037 packages.push(*package_id);
3038 }
3039 InputObjectKind::SharedMoveObject { .. } => {}
3040 });
3041 self.receiving_objects().iter().for_each(|object_ref| {
3042 receiving_objects.push(*object_ref);
3043 });
3044 Ok((move_objects, packages, receiving_objects))
3045 }
3046
3047 fn process_funds_withdrawals_for_signing(
3048 &self,
3049 chain_identifier: ChainIdentifier,
3050 coin_resolver: &dyn CoinReservationResolverTrait,
3051 ) -> UserInputResult<BTreeMap<AccumulatorObjId, (u64, TypeTag)>> {
3052 self.accumulate_funds_withdrawals(chain_identifier, coin_resolver, true)
3053 }
3054
3055 fn process_funds_withdrawals_for_estimation(
3056 &self,
3057 chain_identifier: ChainIdentifier,
3058 coin_resolver: &dyn CoinReservationResolverTrait,
3059 ) -> UserInputResult<BTreeMap<AccumulatorObjId, (u64, TypeTag)>> {
3060 self.accumulate_funds_withdrawals(chain_identifier, coin_resolver, false)
3061 }
3062
3063 fn process_funds_withdrawals_for_execution(
3064 &self,
3065 chain_identifier: ChainIdentifier,
3066 ) -> BTreeMap<AccumulatorObjId, u64> {
3067 let mut withdraws: Vec<_> = self.get_funds_withdrawals().collect();
3068 withdraws.extend(self.get_funds_withdrawal_for_gas_payment());
3069
3070 let mut withdraw_map: BTreeMap<AccumulatorObjId, u64> = BTreeMap::new();
3072 for withdraw in withdraws {
3073 let reserved_amount = match &withdraw.reservation {
3074 Reservation::MaxAmountU64(amount) => {
3075 assert!(*amount > 0, "verified in validity check");
3076 *amount
3077 }
3078 };
3079
3080 let withdrawal_owner = withdraw.owner_for_withdrawal(self);
3081
3082 let account_id =
3084 AccumulatorValue::get_field_id(withdrawal_owner, &withdraw.type_arg.to_type_tag())
3085 .unwrap();
3086
3087 let value = withdraw_map.entry(account_id).or_default();
3088 *value = value.checked_add(reserved_amount).unwrap();
3090 }
3091
3092 for obj in self.coin_reservation_obj_refs() {
3096 assert_reachable!("processing coin reservation withdrawal");
3097 let parsed = ParsedObjectRefWithdrawal::parse(&obj, chain_identifier).unwrap();
3099 let value = withdraw_map
3100 .entry(AccumulatorObjId::new_unchecked(parsed.unmasked_object_id))
3105 .or_default();
3106 *value = value.checked_add(parsed.reservation_amount()).unwrap();
3108 }
3109
3110 withdraw_map
3111 }
3112
3113 fn has_funds_withdrawals(&self) -> bool {
3114 if self.is_gas_paid_from_address_balance() && self.gas_data().budget > 0 {
3115 return true;
3116 }
3117 if let TransactionKind::ProgrammableTransaction(pt) = &self.kind {
3118 for input in &pt.inputs {
3119 if matches!(input, CallArg::FundsWithdrawal(_)) {
3120 return true;
3121 }
3122 }
3123 }
3124 if self.coin_reservation_obj_refs().next().is_some() {
3125 return true;
3126 }
3127 false
3128 }
3129
3130 fn coin_reservation_obj_refs(
3131 &self,
3132 chain_identifier: ChainIdentifier,
3133 ) -> Vec<ParsedObjectRefWithdrawal> {
3134 self.coin_reservation_obj_refs()
3135 .filter_map(|obj_ref| ParsedObjectRefWithdrawal::parse(&obj_ref, chain_identifier))
3136 .collect()
3137 }
3138
3139 fn validity_check(&self, context: &TxValidityCheckContext<'_>) -> SuiResult {
3140 let config = context.config;
3141
3142 match self.expiration() {
3144 TransactionExpiration::None => (), TransactionExpiration::Epoch(max_epoch) => {
3146 if context.epoch > *max_epoch {
3147 return Err(SuiErrorKind::TransactionExpired.into());
3148 }
3149 }
3150 TransactionExpiration::ValidDuring {
3151 min_epoch,
3152 max_epoch,
3153 min_timestamp,
3154 max_timestamp,
3155 chain,
3156 nonce: _,
3157 } => {
3158 if min_timestamp.is_some() || max_timestamp.is_some() {
3159 return Err(UserInputError::Unsupported(
3160 "Timestamp-based transaction expiration is not yet supported".to_string(),
3161 )
3162 .into());
3163 }
3164
3165 match (min_epoch, max_epoch) {
3170 _ if config.relax_valid_during_for_owned_inputs() => (),
3171 (Some(min), Some(max)) => {
3172 if config.enable_multi_epoch_transaction_expiration() {
3173 if !(*max == *min || *max == min.saturating_add(1)) {
3174 return Err(UserInputError::Unsupported(
3175 "max_epoch must be at most min_epoch + 1".to_string(),
3176 )
3177 .into());
3178 }
3179 } else if min != max {
3180 return Err(UserInputError::Unsupported(
3181 "min_epoch must equal max_epoch".to_string(),
3182 )
3183 .into());
3184 }
3185 }
3186 _ => {
3187 return Err(UserInputError::Unsupported(
3188 "Both min_epoch and max_epoch must be specified".to_string(),
3189 )
3190 .into());
3191 }
3192 }
3193
3194 if *chain != context.chain_identifier {
3195 return Err(UserInputError::InvalidChainId {
3196 provided: format!("{:?}", chain),
3197 expected: format!("{:?}", context.chain_identifier),
3198 }
3199 .into());
3200 }
3201
3202 if let Some(min) = min_epoch
3203 && context.epoch < *min
3204 {
3205 return Err(SuiErrorKind::TransactionExpired.into());
3206 }
3207 if let Some(max) = max_epoch
3208 && context.epoch > *max
3209 {
3210 return Err(SuiErrorKind::TransactionExpired.into());
3211 }
3212 }
3213 }
3214
3215 if self.has_funds_withdrawals() {
3216 fp_ensure!(
3219 !self.gas().is_empty() || config.enable_address_balance_gas_payments(),
3220 UserInputError::MissingGasPayment.into()
3221 );
3222
3223 fp_ensure!(
3224 config.enable_accumulators(),
3225 UserInputError::Unsupported("Address balance withdraw is not enabled".to_string())
3226 .into()
3227 );
3228
3229 let max_withdraws = 10;
3231 let mut num_reservations = 0;
3232
3233 for withdraw in self.kind.get_funds_withdrawals() {
3234 num_reservations += 1;
3235 match withdraw.withdraw_from {
3236 WithdrawFrom::Sender => (),
3237 WithdrawFrom::Sponsor => {
3238 return Err(UserInputError::InvalidWithdrawReservation {
3239 error: "Explicit sponsor withdrawals are not yet supported".to_string(),
3240 }
3241 .into());
3242 }
3243 }
3244
3245 match withdraw.reservation {
3246 Reservation::MaxAmountU64(amount) => {
3247 fp_ensure!(
3248 amount > 0,
3249 UserInputError::InvalidWithdrawReservation {
3250 error: "Balance withdraw reservation amount must be non-zero"
3251 .to_string(),
3252 }
3253 .into()
3254 );
3255 }
3256 };
3257 }
3258
3259 for parsed in self.parsed_coin_reservations(context.chain_identifier) {
3260 num_reservations += 1;
3261 if parsed.epoch_id() != context.epoch && parsed.epoch_id() + 1 != context.epoch {
3265 return Err(SuiErrorKind::TransactionExpired.into());
3266 }
3267 if parsed.reservation_amount() == 0 {
3268 return Err(UserInputError::InvalidWithdrawReservation {
3269 error: "Balance withdraw reservation amount must be non-zero".to_string(),
3270 }
3271 .into());
3272 }
3273 }
3274
3275 if config.enable_address_balance_gas_payments()
3277 && self.is_gas_paid_from_address_balance()
3278 {
3279 num_reservations += 1;
3280 }
3281
3282 fp_ensure!(
3283 num_reservations <= max_withdraws,
3284 UserInputError::InvalidWithdrawReservation {
3285 error: format!(
3286 "Maximum number of balance withdraw reservations is {max_withdraws}"
3287 ),
3288 }
3289 .into()
3290 );
3291 }
3292
3293 if config.enable_accumulators()
3294 && config.enable_address_balance_gas_payments()
3295 && self.is_gas_paid_from_address_balance()
3296 {
3297 if config.address_balance_gas_reject_gas_coin_arg()
3298 && let TransactionKind::ProgrammableTransaction(pt) = &self.kind
3299 {
3300 fp_ensure!(
3301 !pt.commands.iter().any(|cmd| cmd.is_gas_coin_used()),
3302 UserInputError::Unsupported(
3303 "Argument::GasCoin is not supported with address balance gas payments"
3304 .to_string(),
3305 )
3306 .into()
3307 );
3308 }
3309
3310 let is_gasless = config.enable_gasless() && self.is_gasless_transaction();
3311 if config.address_balance_gas_check_rgp_at_signing() && !is_gasless {
3312 fp_ensure!(
3313 self.gas_data.price >= context.reference_gas_price,
3314 UserInputError::GasPriceUnderRGP {
3315 gas_price: self.gas_data.price,
3316 reference_gas_price: context.reference_gas_price,
3317 }
3318 .into()
3319 );
3320 }
3321
3322 if !config.relax_valid_during_for_owned_inputs() {
3327 if matches!(self.expiration(), TransactionExpiration::None) {
3328 return Err(UserInputError::MissingGasPayment.into());
3331 }
3332
3333 if !self.expiration().is_replay_protected() {
3334 return Err(UserInputError::InvalidExpiration {
3335 error: "Address balance gas payments require ValidDuring expiration"
3336 .to_string(),
3337 }
3338 .into());
3339 }
3340 }
3341 } else {
3342 fp_ensure!(
3343 !self.gas().is_empty(),
3344 UserInputError::MissingGasPayment.into()
3345 );
3346 }
3347
3348 let gas_len = self.gas().len();
3349 let max_gas_objects = config.max_gas_payment_objects() as usize;
3350
3351 let within_limit = if config.correct_gas_payment_limit_check() {
3352 gas_len <= max_gas_objects
3353 } else {
3354 gas_len < max_gas_objects
3355 };
3356
3357 fp_ensure!(
3358 within_limit,
3359 UserInputError::SizeLimitExceeded {
3360 limit: "maximum number of gas payment objects".to_string(),
3361 value: config.max_gas_payment_objects().to_string()
3362 }
3363 .into()
3364 );
3365
3366 if !config.enable_coin_reservation_obj_refs() {
3367 for (_, _, gas_digest) in self.gas() {
3368 fp_ensure!(
3369 !ParsedDigest::is_coin_reservation_digest(gas_digest),
3370 UserInputError::GasObjectNotOwnedObject {
3371 owner: Owner::AddressOwner(self.sender)
3372 }
3373 .into()
3374 );
3375 }
3376 } else {
3377 let sui_accumulator_id =
3380 *AccumulatorValue::get_field_id(self.sender, &Balance::type_tag(GAS::type_tag()))?
3381 .inner();
3382
3383 for gas_ref in self.gas() {
3384 if let Some(parsed) =
3385 ParsedObjectRefWithdrawal::parse(gas_ref, context.chain_identifier)
3386 {
3387 fp_ensure!(
3390 self.gas_owner() == self.sender,
3391 UserInputError::GasObjectNotOwnedObject {
3392 owner: Owner::AddressOwner(self.sender)
3393 }
3394 .into()
3395 );
3396 fp_ensure!(
3397 parsed.unmasked_object_id == sui_accumulator_id,
3398 UserInputError::GasObjectNotOwnedObject {
3399 owner: Owner::AddressOwner(self.sender)
3400 }
3401 .into()
3402 );
3403 }
3404 }
3405 }
3406
3407 if !self.is_system_tx() {
3408 fp_ensure!(
3409 !check_for_gas_price_too_high(config.gas_model_version())
3410 || self.gas_data.price < config.max_gas_price(),
3411 UserInputError::GasPriceTooHigh {
3412 max_gas_price: config.max_gas_price(),
3413 }
3414 .into()
3415 );
3416 let cost_table = SuiCostTable::new(config, self.gas_data.price);
3417
3418 fp_ensure!(
3419 self.gas_data.budget <= cost_table.max_gas_budget,
3420 UserInputError::GasBudgetTooHigh {
3421 gas_budget: self.gas_data().budget,
3422 max_budget: cost_table.max_gas_budget,
3423 }
3424 .into()
3425 );
3426 let is_gasless = config.enable_gasless() && self.is_gasless_transaction();
3427 if is_gasless {
3428 fp_ensure!(
3429 self.gas_data.budget == 0,
3430 UserInputError::Unsupported(
3431 "gas_budget must be 0 for gasless transactions".to_string()
3432 )
3433 .into()
3434 );
3435 } else {
3436 fp_ensure!(
3437 self.gas_data.budget >= cost_table.min_transaction_cost,
3438 UserInputError::GasBudgetTooLow {
3439 gas_budget: self.gas_data.budget,
3440 min_budget: cost_table.min_transaction_cost,
3441 }
3442 .into()
3443 );
3444 }
3445 }
3446
3447 self.validity_check_no_gas_check(config)?;
3448 Ok(())
3449 }
3450
3451 fn validity_check_no_gas_check(&self, config: &ProtocolConfig) -> UserInputResult {
3454 self.kind().validity_check(config)?;
3455
3456 if config.enable_gasless() && self.is_gasless_transaction() {
3457 let TransactionKind::ProgrammableTransaction(pt) = &self.kind else {
3458 debug_fatal!("gasless transaction is not a ProgrammableTransaction");
3459 return Err(UserInputError::Unsupported(
3460 "Gasless transactions must be programmable transactions".to_string(),
3461 ));
3462 };
3463 pt.validate_gasless_transaction(config)?;
3464 }
3465
3466 self.check_sponsorship()
3467 }
3468
3469 fn is_sponsored_tx(&self) -> bool {
3471 self.gas_owner() != self.sender
3472 }
3473
3474 fn is_gas_paid_from_address_balance(&self) -> bool {
3478 is_gas_paid_from_address_balance(&self.gas_data, &self.kind)
3479 }
3480
3481 fn is_gasless_transaction(&self) -> bool {
3482 is_gasless_transaction(&self.gas_data, &self.kind)
3483 }
3484
3485 fn check_sponsorship(&self) -> UserInputResult {
3487 if self.gas_owner() == self.sender() {
3489 return Ok(());
3490 }
3491 if matches!(&self.kind, TransactionKind::ProgrammableTransaction(_)) {
3492 return Ok(());
3493 }
3494 Err(UserInputError::UnsupportedSponsoredTransactionKind)
3495 }
3496
3497 fn is_end_of_epoch_tx(&self) -> bool {
3498 matches!(
3499 self.kind,
3500 TransactionKind::ChangeEpoch(_) | TransactionKind::EndOfEpochTransaction(_)
3501 )
3502 }
3503
3504 fn is_consensus_commit_prologue(&self) -> bool {
3505 match &self.kind {
3506 TransactionKind::ConsensusCommitPrologue(_)
3507 | TransactionKind::ConsensusCommitPrologueV2(_)
3508 | TransactionKind::ConsensusCommitPrologueV3(_)
3509 | TransactionKind::ConsensusCommitPrologueV4(_) => true,
3510
3511 TransactionKind::ProgrammableTransaction(_)
3512 | TransactionKind::ProgrammableSystemTransaction(_)
3513 | TransactionKind::ChangeEpoch(_)
3514 | TransactionKind::Genesis(_)
3515 | TransactionKind::AuthenticatorStateUpdate(_)
3516 | TransactionKind::EndOfEpochTransaction(_)
3517 | TransactionKind::RandomnessStateUpdate(_) => false,
3518 }
3519 }
3520
3521 fn is_system_tx(&self) -> bool {
3522 self.kind.is_system_tx()
3523 }
3524
3525 fn is_genesis_tx(&self) -> bool {
3526 matches!(self.kind, TransactionKind::Genesis(_))
3527 }
3528
3529 fn sender_mut_for_testing(&mut self) -> &mut SuiAddress {
3530 &mut self.sender
3531 }
3532
3533 fn gas_data_mut(&mut self) -> &mut GasData {
3534 &mut self.gas_data
3535 }
3536
3537 fn expiration_mut_for_testing(&mut self) -> &mut TransactionExpiration {
3538 &mut self.expiration
3539 }
3540}
3541
3542impl TransactionDataV1 {
3543 fn accumulate_funds_withdrawals(
3544 &self,
3545 chain_identifier: ChainIdentifier,
3546 coin_resolver: &dyn CoinReservationResolverTrait,
3547 include_gas_payment: bool,
3548 ) -> UserInputResult<BTreeMap<AccumulatorObjId, (u64, TypeTag)>> {
3549 let mut withdraws: Vec<_> = self.get_funds_withdrawals().collect();
3550
3551 for withdraw in self.parsed_coin_reservations(chain_identifier) {
3552 let withdrawal_arg =
3553 coin_resolver.resolve_funds_withdrawal(self.sender(), withdraw, None)?;
3554 withdraws.push(withdrawal_arg);
3555 }
3556
3557 if include_gas_payment {
3558 withdraws.extend(self.get_funds_withdrawal_for_gas_payment());
3559 }
3560
3561 let mut withdraw_map: BTreeMap<AccumulatorObjId, (u64, TypeTag)> = BTreeMap::new();
3562 for withdraw in withdraws {
3563 let reserved_amount = match &withdraw.reservation {
3564 Reservation::MaxAmountU64(amount) => {
3565 assert!(*amount > 0, "verified in validity check");
3566 *amount
3567 }
3568 };
3569
3570 let account_address = withdraw.owner_for_withdrawal(self);
3571 let type_tag = withdraw.type_arg.to_type_tag();
3572 let account_id =
3573 AccumulatorValue::get_field_id(account_address, &type_tag).map_err(|e| {
3574 UserInputError::InvalidWithdrawReservation {
3575 error: e.to_string(),
3576 }
3577 })?;
3578
3579 let (current_amount, _) = withdraw_map
3580 .entry(account_id)
3581 .or_insert_with(|| (0, type_tag));
3582 *current_amount = current_amount.checked_add(reserved_amount).ok_or(
3583 UserInputError::InvalidWithdrawReservation {
3584 error: "Balance withdraw reservation overflow".to_string(),
3585 },
3586 )?;
3587 }
3588
3589 Ok(withdraw_map)
3590 }
3591
3592 fn get_funds_withdrawal_for_gas_payment(&self) -> Option<FundsWithdrawalArg> {
3593 if self.is_gas_paid_from_address_balance() && self.gas_data().budget > 0 {
3594 Some(if self.sender() != self.gas_owner() {
3595 FundsWithdrawalArg::balance_from_sponsor(self.gas_data().budget, GAS::type_tag())
3596 } else {
3597 FundsWithdrawalArg::balance_from_sender(self.gas_data().budget, GAS::type_tag())
3598 })
3599 } else {
3600 None
3601 }
3602 }
3603
3604 fn get_funds_withdrawals(&self) -> impl Iterator<Item = FundsWithdrawalArg> + '_ {
3605 self.kind.get_funds_withdrawals().cloned()
3606 }
3607
3608 fn coin_reservation_obj_refs(&self) -> impl Iterator<Item = ObjectRef> {
3609 self.kind
3610 .get_coin_reservation_obj_refs()
3611 .chain(self.gas().iter().filter_map(|gas_ref| {
3612 if ParsedDigest::is_coin_reservation_digest(&gas_ref.2) {
3613 Some(*gas_ref)
3614 } else {
3615 None
3616 }
3617 }))
3618 }
3619
3620 fn parsed_coin_reservations(
3621 &self,
3622 chain_identifier: ChainIdentifier,
3623 ) -> impl Iterator<Item = ParsedObjectRefWithdrawal> {
3624 self.coin_reservation_obj_refs().map(move |obj_ref| {
3625 ParsedObjectRefWithdrawal::parse(&obj_ref, chain_identifier).unwrap()
3626 })
3627 }
3628}
3629
3630pub struct TxValidityCheckContext<'a> {
3631 pub config: &'a ProtocolConfig,
3632 pub epoch: EpochId,
3633 pub chain_identifier: ChainIdentifier,
3634 pub reference_gas_price: u64,
3635}
3636
3637impl<'a> TxValidityCheckContext<'a> {
3638 pub fn from_cfg_for_testing(config: &'a ProtocolConfig) -> Self {
3639 Self {
3640 config,
3641 epoch: 0,
3642 chain_identifier: ChainIdentifier::default(),
3643 reference_gas_price: 1000,
3644 }
3645 }
3646}
3647
3648#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
3649pub struct SenderSignedData(SizeOneVec<SenderSignedTransaction>);
3650
3651#[derive(Debug, Clone, PartialEq, Eq, Hash)]
3652pub struct SenderSignedTransaction {
3653 pub intent_message: IntentMessage<TransactionData>,
3654 pub tx_signatures: Vec<GenericSignature>,
3658}
3659
3660impl Serialize for SenderSignedTransaction {
3661 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
3662 where
3663 S: serde::Serializer,
3664 {
3665 #[derive(Serialize)]
3666 #[serde(rename = "SenderSignedTransaction")]
3667 struct SignedTxn<'a> {
3668 intent_message: &'a IntentMessage<TransactionData>,
3669 tx_signatures: &'a Vec<GenericSignature>,
3670 }
3671
3672 if self.intent_message().intent != Intent::sui_transaction() {
3673 return Err(serde::ser::Error::custom("invalid Intent for Transaction"));
3674 }
3675
3676 let txn = SignedTxn {
3677 intent_message: self.intent_message(),
3678 tx_signatures: &self.tx_signatures,
3679 };
3680 txn.serialize(serializer)
3681 }
3682}
3683
3684impl<'de> Deserialize<'de> for SenderSignedTransaction {
3685 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
3686 where
3687 D: serde::Deserializer<'de>,
3688 {
3689 #[derive(Deserialize)]
3690 #[serde(rename = "SenderSignedTransaction")]
3691 struct SignedTxn {
3692 intent_message: IntentMessage<TransactionData>,
3693 tx_signatures: Vec<GenericSignature>,
3694 }
3695
3696 let SignedTxn {
3697 intent_message,
3698 tx_signatures,
3699 } = Deserialize::deserialize(deserializer)?;
3700
3701 if intent_message.intent != Intent::sui_transaction() {
3702 return Err(serde::de::Error::custom("invalid Intent for Transaction"));
3703 }
3704
3705 Ok(Self {
3706 intent_message,
3707 tx_signatures,
3708 })
3709 }
3710}
3711
3712impl SenderSignedTransaction {
3713 pub(crate) fn get_signer_sig_mapping(
3715 &self,
3716 verify_legacy_zklogin_address: bool,
3717 ) -> SuiResult<BTreeMap<SuiAddress, (u8, &GenericSignature)>> {
3718 let mut mapping = BTreeMap::new();
3719 for (idx, sig) in self.tx_signatures.iter().enumerate() {
3720 if verify_legacy_zklogin_address && let GenericSignature::ZkLoginAuthenticator(z) = sig
3721 {
3722 mapping.insert(SuiAddress::try_from_padded(&z.inputs)?, (idx as u8, sig));
3724 }
3725 let address = sig.try_into()?;
3726 mapping.insert(address, (idx as u8, sig));
3727 }
3728 Ok(mapping)
3729 }
3730
3731 pub fn intent_message(&self) -> &IntentMessage<TransactionData> {
3732 &self.intent_message
3733 }
3734}
3735
3736impl SenderSignedData {
3737 pub fn new(tx_data: TransactionData, tx_signatures: Vec<GenericSignature>) -> Self {
3738 Self(SizeOneVec::new(SenderSignedTransaction {
3739 intent_message: IntentMessage::new(Intent::sui_transaction(), tx_data),
3740 tx_signatures,
3741 }))
3742 }
3743
3744 pub fn new_from_sender_signature(tx_data: TransactionData, tx_signature: Signature) -> Self {
3745 Self(SizeOneVec::new(SenderSignedTransaction {
3746 intent_message: IntentMessage::new(Intent::sui_transaction(), tx_data),
3747 tx_signatures: vec![tx_signature.into()],
3748 }))
3749 }
3750
3751 pub fn inner(&self) -> &SenderSignedTransaction {
3752 self.0.element()
3753 }
3754
3755 pub fn into_inner(self) -> SenderSignedTransaction {
3756 self.0.into_inner()
3757 }
3758
3759 pub fn inner_mut(&mut self) -> &mut SenderSignedTransaction {
3760 self.0.element_mut()
3761 }
3762
3763 pub fn add_signature(&mut self, new_signature: Signature) {
3766 self.inner_mut().tx_signatures.push(new_signature.into());
3767 }
3768
3769 pub(crate) fn get_signer_sig_mapping(
3770 &self,
3771 verify_legacy_zklogin_address: bool,
3772 ) -> SuiResult<BTreeMap<SuiAddress, (u8, &GenericSignature)>> {
3773 self.inner()
3774 .get_signer_sig_mapping(verify_legacy_zklogin_address)
3775 }
3776
3777 pub fn transaction_data(&self) -> &TransactionData {
3778 &self.intent_message().value
3779 }
3780
3781 pub fn intent_message(&self) -> &IntentMessage<TransactionData> {
3782 self.inner().intent_message()
3783 }
3784
3785 pub fn tx_signatures(&self) -> &[GenericSignature] {
3786 &self.inner().tx_signatures
3787 }
3788
3789 pub fn has_zklogin_sig(&self) -> bool {
3790 self.tx_signatures().iter().any(|sig| sig.is_zklogin())
3791 }
3792
3793 pub fn has_upgraded_multisig(&self) -> bool {
3794 self.tx_signatures()
3795 .iter()
3796 .any(|sig| sig.is_upgraded_multisig())
3797 }
3798
3799 #[cfg(test)]
3800 pub fn intent_message_mut_for_testing(&mut self) -> &mut IntentMessage<TransactionData> {
3801 &mut self.inner_mut().intent_message
3802 }
3803
3804 pub fn tx_signatures_mut_for_testing(&mut self) -> &mut Vec<GenericSignature> {
3806 &mut self.inner_mut().tx_signatures
3807 }
3808
3809 pub fn full_message_digest_with_alias_versions(
3811 &self,
3812 alias_versions: &Vec<(SuiAddress, Option<SequenceNumber>)>,
3813 ) -> SenderSignedDataDigest {
3814 let mut digest = DefaultHash::default();
3815 bcs::serialize_into(&mut digest, self).expect("serialization should not fail");
3816 bcs::serialize_into(&mut digest, alias_versions).expect("serialization should not fail");
3817 let hash = digest.finalize();
3818 SenderSignedDataDigest::new(hash.into())
3819 }
3820
3821 pub fn serialized_size(&self) -> SuiResult<usize> {
3822 bcs::serialized_size(self).map_err(|e| {
3823 SuiErrorKind::TransactionSerializationError {
3824 error: e.to_string(),
3825 }
3826 .into()
3827 })
3828 }
3829
3830 fn check_user_signature_protocol_compatibility(&self, config: &ProtocolConfig) -> SuiResult {
3831 for sig in &self.inner().tx_signatures {
3832 match sig {
3833 GenericSignature::MultiSig(_) => {
3834 if !config.supports_upgraded_multisig() {
3835 return Err(SuiErrorKind::UserInputError {
3836 error: UserInputError::Unsupported(
3837 "upgraded multisig format not enabled on this network".to_string(),
3838 ),
3839 }
3840 .into());
3841 }
3842 }
3843 GenericSignature::ZkLoginAuthenticator(_) => {
3844 if !config.zklogin_auth() {
3845 return Err(SuiErrorKind::UserInputError {
3846 error: UserInputError::Unsupported(
3847 "zklogin is not enabled on this network".to_string(),
3848 ),
3849 }
3850 .into());
3851 }
3852 }
3853 GenericSignature::PasskeyAuthenticator(_) => {
3854 if !config.passkey_auth() {
3855 return Err(SuiErrorKind::UserInputError {
3856 error: UserInputError::Unsupported(
3857 "passkey is not enabled on this network".to_string(),
3858 ),
3859 }
3860 .into());
3861 }
3862 }
3863 GenericSignature::Signature(_) | GenericSignature::MultiSigLegacy(_) => (),
3864 }
3865 }
3866
3867 Ok(())
3868 }
3869
3870 pub fn validity_check(&self, context: &TxValidityCheckContext<'_>) -> Result<usize, SuiError> {
3873 self.check_user_signature_protocol_compatibility(context.config)?;
3875
3876 let tx_data = &self.transaction_data();
3881 fp_ensure!(
3882 !tx_data.is_system_tx(),
3883 SuiErrorKind::UserInputError {
3884 error: UserInputError::Unsupported(
3885 "SenderSignedData must not contain system transaction".to_string()
3886 )
3887 }
3888 .into()
3889 );
3890
3891 let tx_size = self.serialized_size()?;
3893 let max_tx_size_bytes = context.config.max_tx_size_bytes();
3894 fp_ensure!(
3895 tx_size as u64 <= max_tx_size_bytes,
3896 SuiErrorKind::UserInputError {
3897 error: UserInputError::SizeLimitExceeded {
3898 limit: format!(
3899 "serialized transaction size exceeded maximum of {max_tx_size_bytes}"
3900 ),
3901 value: tx_size.to_string(),
3902 }
3903 }
3904 .into()
3905 );
3906
3907 if context.config.enable_gasless() && tx_data.is_gasless_transaction() {
3908 let gasless_max = context.config.get_gasless_max_tx_size_bytes();
3909 fp_ensure!(
3910 tx_size as u64 <= gasless_max,
3911 SuiErrorKind::UserInputError {
3912 error: UserInputError::SizeLimitExceeded {
3913 limit: format!(
3914 "serialized gasless transaction size exceeded maximum of {gasless_max}"
3915 ),
3916 value: tx_size.to_string(),
3917 }
3918 }
3919 .into()
3920 );
3921 }
3922
3923 tx_data.validity_check(context)?;
3924
3925 Ok(tx_size)
3926 }
3927}
3928
3929impl Message for SenderSignedData {
3930 type DigestType = TransactionDigest;
3931 const SCOPE: IntentScope = IntentScope::SenderSignedTransaction;
3932
3933 fn digest(&self) -> Self::DigestType {
3935 self.intent_message().value.digest()
3936 }
3937}
3938
3939impl<S> Envelope<SenderSignedData, S> {
3940 pub fn sender_address(&self) -> SuiAddress {
3941 self.data().intent_message().value.sender()
3942 }
3943
3944 pub fn gas_owner(&self) -> SuiAddress {
3945 self.data().intent_message().value.gas_owner()
3946 }
3947
3948 pub fn gas(&self) -> &[ObjectRef] {
3949 self.data().intent_message().value.gas()
3950 }
3951
3952 pub fn is_consensus_tx(&self) -> bool {
3953 self.transaction_data().has_funds_withdrawals()
3954 || self.shared_input_objects().next().is_some()
3955 }
3956
3957 pub fn shared_input_objects(&self) -> impl Iterator<Item = SharedInputObject> + '_ {
3958 self.data()
3959 .inner()
3960 .intent_message
3961 .value
3962 .shared_input_objects()
3963 .into_iter()
3964 }
3965
3966 pub fn key(&self) -> TransactionKey {
3968 match &self.data().intent_message().value.kind() {
3969 TransactionKind::RandomnessStateUpdate(rsu) => {
3970 TransactionKey::RandomnessRound(rsu.epoch, rsu.randomness_round)
3971 }
3972 _ => TransactionKey::Digest(*self.digest()),
3973 }
3974 }
3975
3976 pub fn non_digest_key(&self) -> Option<TransactionKey> {
3981 match &self.data().intent_message().value.kind() {
3982 TransactionKind::RandomnessStateUpdate(rsu) => Some(TransactionKey::RandomnessRound(
3983 rsu.epoch,
3984 rsu.randomness_round,
3985 )),
3986 _ => None,
3987 }
3988 }
3989
3990 pub fn is_system_tx(&self) -> bool {
3991 self.data().intent_message().value.is_system_tx()
3992 }
3993
3994 pub fn is_sponsored_tx(&self) -> bool {
3995 self.data().intent_message().value.is_sponsored_tx()
3996 }
3997}
3998
3999impl Transaction {
4000 pub fn from_data_and_signer(
4001 data: TransactionData,
4002 signers: Vec<&dyn Signer<Signature>>,
4003 ) -> Self {
4004 let signatures = {
4005 let intent_msg = IntentMessage::new(Intent::sui_transaction(), &data);
4006 signers
4007 .into_iter()
4008 .map(|s| Signature::new_secure(&intent_msg, s))
4009 .collect()
4010 };
4011 Self::from_data(data, signatures)
4012 }
4013
4014 pub fn from_data(data: TransactionData, signatures: Vec<Signature>) -> Self {
4016 Self::from_generic_sig_data(data, signatures.into_iter().map(|s| s.into()).collect())
4017 }
4018
4019 pub fn signature_from_signer(
4020 data: TransactionData,
4021 intent: Intent,
4022 signer: &dyn Signer<Signature>,
4023 ) -> Signature {
4024 let intent_msg = IntentMessage::new(intent, data);
4025 Signature::new_secure(&intent_msg, signer)
4026 }
4027
4028 pub fn from_generic_sig_data(data: TransactionData, signatures: Vec<GenericSignature>) -> Self {
4029 Self::new(SenderSignedData::new(data, signatures))
4030 }
4031
4032 pub fn to_tx_bytes_and_signatures(&self) -> (Base64, Vec<Base64>) {
4035 (
4036 Base64::from_bytes(&bcs::to_bytes(&self.data().intent_message().value).unwrap()),
4037 self.data()
4038 .inner()
4039 .tx_signatures
4040 .iter()
4041 .map(|s| Base64::from_bytes(s.as_ref()))
4042 .collect(),
4043 )
4044 }
4045}
4046
4047impl VerifiedTransaction {
4048 pub fn new_change_epoch(
4049 next_epoch: EpochId,
4050 protocol_version: ProtocolVersion,
4051 storage_charge: u64,
4052 computation_charge: u64,
4053 storage_rebate: u64,
4054 non_refundable_storage_fee: u64,
4055 epoch_start_timestamp_ms: u64,
4056 system_packages: Vec<(SequenceNumber, Vec<Vec<u8>>, Vec<ObjectID>)>,
4057 ) -> Self {
4058 ChangeEpoch {
4059 epoch: next_epoch,
4060 protocol_version,
4061 storage_charge,
4062 computation_charge,
4063 storage_rebate,
4064 non_refundable_storage_fee,
4065 epoch_start_timestamp_ms,
4066 system_packages,
4067 }
4068 .pipe(TransactionKind::ChangeEpoch)
4069 .pipe(Self::new_system_transaction)
4070 }
4071
4072 pub fn new_genesis_transaction(objects: Vec<GenesisObject>) -> Self {
4073 GenesisTransaction { objects }
4074 .pipe(TransactionKind::Genesis)
4075 .pipe(Self::new_system_transaction)
4076 }
4077
4078 pub fn new_consensus_commit_prologue(
4079 epoch: u64,
4080 round: u64,
4081 commit_timestamp_ms: CheckpointTimestamp,
4082 ) -> Self {
4083 ConsensusCommitPrologue {
4084 epoch,
4085 round,
4086 commit_timestamp_ms,
4087 }
4088 .pipe(TransactionKind::ConsensusCommitPrologue)
4089 .pipe(Self::new_system_transaction)
4090 }
4091
4092 pub fn new_consensus_commit_prologue_v2(
4093 epoch: u64,
4094 round: u64,
4095 commit_timestamp_ms: CheckpointTimestamp,
4096 consensus_commit_digest: ConsensusCommitDigest,
4097 ) -> Self {
4098 ConsensusCommitPrologueV2 {
4099 epoch,
4100 round,
4101 commit_timestamp_ms,
4102 consensus_commit_digest,
4103 }
4104 .pipe(TransactionKind::ConsensusCommitPrologueV2)
4105 .pipe(Self::new_system_transaction)
4106 }
4107
4108 pub fn new_consensus_commit_prologue_v3(
4109 epoch: u64,
4110 round: u64,
4111 commit_timestamp_ms: CheckpointTimestamp,
4112 consensus_commit_digest: ConsensusCommitDigest,
4113 consensus_determined_version_assignments: ConsensusDeterminedVersionAssignments,
4114 ) -> Self {
4115 ConsensusCommitPrologueV3 {
4116 epoch,
4117 round,
4118 sub_dag_index: None,
4120 commit_timestamp_ms,
4121 consensus_commit_digest,
4122 consensus_determined_version_assignments,
4123 }
4124 .pipe(TransactionKind::ConsensusCommitPrologueV3)
4125 .pipe(Self::new_system_transaction)
4126 }
4127
4128 pub fn new_consensus_commit_prologue_v4(
4129 epoch: u64,
4130 round: u64,
4131 commit_timestamp_ms: CheckpointTimestamp,
4132 consensus_commit_digest: ConsensusCommitDigest,
4133 consensus_determined_version_assignments: ConsensusDeterminedVersionAssignments,
4134 additional_state_digest: AdditionalConsensusStateDigest,
4135 ) -> Self {
4136 ConsensusCommitPrologueV4 {
4137 epoch,
4138 round,
4139 sub_dag_index: None,
4141 commit_timestamp_ms,
4142 consensus_commit_digest,
4143 consensus_determined_version_assignments,
4144 additional_state_digest,
4145 }
4146 .pipe(TransactionKind::ConsensusCommitPrologueV4)
4147 .pipe(Self::new_system_transaction)
4148 }
4149
4150 pub fn new_authenticator_state_update(
4151 epoch: u64,
4152 round: u64,
4153 new_active_jwks: Vec<ActiveJwk>,
4154 authenticator_obj_initial_shared_version: SequenceNumber,
4155 ) -> Self {
4156 AuthenticatorStateUpdate {
4157 epoch,
4158 round,
4159 new_active_jwks,
4160 authenticator_obj_initial_shared_version,
4161 }
4162 .pipe(TransactionKind::AuthenticatorStateUpdate)
4163 .pipe(Self::new_system_transaction)
4164 }
4165
4166 pub fn new_randomness_state_update(
4167 epoch: u64,
4168 randomness_round: RandomnessRound,
4169 random_bytes: Vec<u8>,
4170 randomness_obj_initial_shared_version: SequenceNumber,
4171 ) -> Self {
4172 RandomnessStateUpdate {
4173 epoch,
4174 randomness_round,
4175 random_bytes,
4176 randomness_obj_initial_shared_version,
4177 }
4178 .pipe(TransactionKind::RandomnessStateUpdate)
4179 .pipe(Self::new_system_transaction)
4180 }
4181
4182 pub fn new_end_of_epoch_transaction(txns: Vec<EndOfEpochTransactionKind>) -> Self {
4183 TransactionKind::EndOfEpochTransaction(txns).pipe(Self::new_system_transaction)
4184 }
4185
4186 pub fn new_system_transaction(system_transaction: TransactionKind) -> Self {
4187 system_transaction
4188 .pipe(TransactionData::new_system_transaction)
4189 .pipe(|data| {
4190 SenderSignedData::new_from_sender_signature(
4191 data,
4192 Ed25519SuiSignature::from_bytes(&[0; Ed25519SuiSignature::LENGTH])
4193 .unwrap()
4194 .into(),
4195 )
4196 })
4197 .pipe(Transaction::new)
4198 .pipe(Self::new_from_verified)
4199 }
4200}
4201
4202impl VerifiedSignedTransaction {
4203 pub fn new(
4205 epoch: EpochId,
4206 transaction: VerifiedTransaction,
4207 authority: AuthorityName,
4208 secret: &dyn Signer<AuthoritySignature>,
4209 ) -> Self {
4210 Self::new_from_verified(SignedTransaction::new(
4211 epoch,
4212 transaction.into_inner().into_data(),
4213 secret,
4214 authority,
4215 ))
4216 }
4217}
4218
4219pub type Transaction = Envelope<SenderSignedData, EmptySignInfo>;
4221pub type VerifiedTransaction = VerifiedEnvelope<SenderSignedData, EmptySignInfo>;
4222pub type TrustedTransaction = TrustedEnvelope<SenderSignedData, EmptySignInfo>;
4223
4224pub type SignedTransaction = Envelope<SenderSignedData, AuthoritySignInfo>;
4226pub type VerifiedSignedTransaction = VerifiedEnvelope<SenderSignedData, AuthoritySignInfo>;
4227
4228impl Transaction {
4229 pub fn verify_signature_for_testing(
4230 &self,
4231 current_epoch: EpochId,
4232 verify_params: &VerifyParams,
4233 ) -> SuiResult {
4234 verify_sender_signed_data_message_signatures(
4235 self.data(),
4236 current_epoch,
4237 verify_params,
4238 Arc::new(VerifiedDigestCache::new_empty()),
4239 vec![],
4240 )?;
4241 Ok(())
4242 }
4243
4244 pub fn try_into_verified_for_testing(
4245 self,
4246 current_epoch: EpochId,
4247 verify_params: &VerifyParams,
4248 ) -> SuiResult<VerifiedTransaction> {
4249 self.verify_signature_for_testing(current_epoch, verify_params)?;
4250 Ok(VerifiedTransaction::new_from_verified(self))
4251 }
4252}
4253
4254impl SignedTransaction {
4255 pub fn verify_signatures_authenticated_for_testing(
4256 &self,
4257 committee: &Committee,
4258 verify_params: &VerifyParams,
4259 ) -> SuiResult {
4260 verify_sender_signed_data_message_signatures(
4261 self.data(),
4262 committee.epoch(),
4263 verify_params,
4264 Arc::new(VerifiedDigestCache::new_empty()),
4265 vec![],
4266 )?;
4267
4268 self.auth_sig().verify_secure(
4269 self.data(),
4270 Intent::sui_app(IntentScope::SenderSignedTransaction),
4271 committee,
4272 )
4273 }
4274
4275 pub fn try_into_verified_for_testing(
4276 self,
4277 committee: &Committee,
4278 verify_params: &VerifyParams,
4279 ) -> SuiResult<VerifiedSignedTransaction> {
4280 self.verify_signatures_authenticated_for_testing(committee, verify_params)?;
4281 Ok(VerifiedSignedTransaction::new_from_verified(self))
4282 }
4283}
4284
4285pub type CertifiedTransaction = Envelope<SenderSignedData, AuthorityStrongQuorumSignInfo>;
4286
4287impl CertifiedTransaction {
4288 pub fn certificate_digest(&self) -> CertificateDigest {
4289 let mut digest = DefaultHash::default();
4290 bcs::serialize_into(&mut digest, self).expect("serialization should not fail");
4291 let hash = digest.finalize();
4292 CertificateDigest::new(hash.into())
4293 }
4294
4295 pub fn gas_price(&self) -> u64 {
4296 self.data().transaction_data().gas_price()
4297 }
4298
4299 pub fn verify_signatures_authenticated(
4302 &self,
4303 committee: &Committee,
4304 verify_params: &VerifyParams,
4305 zklogin_inputs_cache: Arc<VerifiedDigestCache<ZKLoginInputsDigest>>,
4306 ) -> SuiResult {
4307 verify_sender_signed_data_message_signatures(
4308 self.data(),
4309 committee.epoch(),
4310 verify_params,
4311 zklogin_inputs_cache,
4312 vec![],
4313 )?;
4314 self.auth_sig().verify_secure(
4315 self.data(),
4316 Intent::sui_app(IntentScope::SenderSignedTransaction),
4317 committee,
4318 )
4319 }
4320
4321 pub fn try_into_verified_for_testing(
4322 self,
4323 committee: &Committee,
4324 verify_params: &VerifyParams,
4325 ) -> SuiResult<VerifiedCertificate> {
4326 self.verify_signatures_authenticated(
4327 committee,
4328 verify_params,
4329 Arc::new(VerifiedDigestCache::new_empty()),
4330 )?;
4331 Ok(VerifiedCertificate::new_from_verified(self))
4332 }
4333
4334 pub fn verify_committee_sigs_only(&self, committee: &Committee) -> SuiResult {
4335 self.auth_sig().verify_secure(
4336 self.data(),
4337 Intent::sui_app(IntentScope::SenderSignedTransaction),
4338 committee,
4339 )
4340 }
4341}
4342
4343pub type VerifiedCertificate = VerifiedEnvelope<SenderSignedData, AuthorityStrongQuorumSignInfo>;
4344pub type TrustedCertificate = TrustedEnvelope<SenderSignedData, AuthorityStrongQuorumSignInfo>;
4345
4346#[derive(Clone, Debug, Serialize, Deserialize)]
4347pub struct WithAliases<T>(
4348 T,
4349 #[serde(with = "nonempty_as_vec")] NonEmpty<(u8, Option<SequenceNumber>)>,
4350);
4351
4352impl<T> WithAliases<T> {
4353 pub fn new(tx: T, aliases: NonEmpty<(u8, Option<SequenceNumber>)>) -> Self {
4354 Self(tx, aliases)
4355 }
4356
4357 pub fn tx(&self) -> &T {
4358 &self.0
4359 }
4360
4361 pub fn aliases(&self) -> &NonEmpty<(u8, Option<SequenceNumber>)> {
4362 &self.1
4363 }
4364
4365 pub fn into_tx(self) -> T {
4366 self.0
4367 }
4368
4369 pub fn into_aliases(self) -> NonEmpty<(u8, Option<SequenceNumber>)> {
4370 self.1
4371 }
4372
4373 pub fn into_inner(self) -> (T, NonEmpty<(u8, Option<SequenceNumber>)>) {
4374 (self.0, self.1)
4375 }
4376}
4377
4378impl<T: Message, S> WithAliases<VerifiedEnvelope<T, S>> {
4379 pub fn serializable(self) -> WithAliases<TrustedEnvelope<T, S>> {
4381 WithAliases(self.0.serializable(), self.1)
4382 }
4383}
4384
4385impl<S> WithAliases<Envelope<SenderSignedData, S>> {
4386 pub fn no_aliases(tx: Envelope<SenderSignedData, S>) -> Self {
4389 let required_signers = tx.intent_message().value.required_signers();
4390 assert_eq!(required_signers.len(), tx.tx_signatures().len());
4391 let no_aliases = required_signers
4392 .iter()
4393 .enumerate()
4394 .map(|(idx, _)| (idx as u8, None))
4395 .collect::<Vec<_>>();
4396 Self::new(
4397 tx,
4398 NonEmpty::from_vec(no_aliases).expect("must have at least one required_signer"),
4399 )
4400 }
4401}
4402
4403impl<S> WithAliases<VerifiedEnvelope<SenderSignedData, S>> {
4404 pub fn no_aliases(tx: VerifiedEnvelope<SenderSignedData, S>) -> Self {
4407 let required_signers = tx.intent_message().value.required_signers();
4408 assert_eq!(required_signers.len(), tx.tx_signatures().len());
4409 let no_aliases = required_signers
4410 .iter()
4411 .enumerate()
4412 .map(|(idx, _)| (idx as u8, None))
4413 .collect::<Vec<_>>();
4414 Self::new(
4415 tx,
4416 NonEmpty::from_vec(no_aliases).expect("must have at least one required_signer"),
4417 )
4418 }
4419}
4420
4421pub type TransactionWithAliases = WithAliases<Transaction>;
4422pub type VerifiedTransactionWithAliases = WithAliases<VerifiedTransaction>;
4423pub type TrustedTransactionWithAliases = WithAliases<TrustedTransaction>;
4424
4425#[derive(Clone, Debug, Serialize, Deserialize)]
4430pub struct DeprecatedWithAliases<T>(
4431 T,
4432 #[serde(with = "nonempty_as_vec")] NonEmpty<(SuiAddress, Option<SequenceNumber>)>,
4433);
4434
4435impl<T> DeprecatedWithAliases<T> {
4436 pub fn into_inner(self) -> (T, NonEmpty<(SuiAddress, Option<SequenceNumber>)>) {
4437 (self.0, self.1)
4438 }
4439}
4440
4441impl<T: Message, S> From<WithAliases<VerifiedEnvelope<T, S>>> for WithAliases<Envelope<T, S>> {
4442 fn from(value: WithAliases<VerifiedEnvelope<T, S>>) -> Self {
4443 Self(value.0.into(), value.1)
4444 }
4445}
4446
4447impl<T: Message, S> From<WithAliases<TrustedEnvelope<T, S>>>
4448 for WithAliases<VerifiedEnvelope<T, S>>
4449{
4450 fn from(value: WithAliases<TrustedEnvelope<T, S>>) -> Self {
4451 Self(value.0.into(), value.1)
4452 }
4453}
4454
4455mod nonempty_as_vec {
4456 use super::*;
4457 use serde::{Deserialize, Deserializer, Serialize, Serializer};
4458
4459 pub fn serialize<S, T>(value: &NonEmpty<T>, serializer: S) -> Result<S::Ok, S::Error>
4460 where
4461 S: Serializer,
4462 T: Serialize,
4463 {
4464 let vec: Vec<&T> = value.iter().collect();
4465 vec.serialize(serializer)
4466 }
4467
4468 pub fn deserialize<'de, D, T>(deserializer: D) -> Result<NonEmpty<T>, D::Error>
4469 where
4470 D: Deserializer<'de>,
4471 T: Deserialize<'de> + Clone,
4472 {
4473 use serde::de::{SeqAccess, Visitor};
4474 use std::fmt;
4475 use std::marker::PhantomData;
4476
4477 struct NonEmptyVisitor<T>(PhantomData<T>);
4478
4479 impl<'de, T> Visitor<'de> for NonEmptyVisitor<T>
4480 where
4481 T: Deserialize<'de> + Clone,
4482 {
4483 type Value = NonEmpty<T>;
4484
4485 fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
4486 formatter.write_str("a non-empty sequence")
4487 }
4488
4489 fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
4490 where
4491 A: SeqAccess<'de>,
4492 {
4493 let head = seq
4494 .next_element()?
4495 .ok_or_else(|| serde::de::Error::custom("empty vector"))?;
4496
4497 let mut tail = Vec::new();
4498 while let Some(elem) = seq.next_element()? {
4499 tail.push(elem);
4500 }
4501
4502 Ok(NonEmpty { head, tail })
4503 }
4504 }
4505
4506 deserializer.deserialize_seq(NonEmptyVisitor(PhantomData))
4507 }
4508}
4509
4510#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
4520pub enum TransactionClaim {
4521 #[deprecated(note = "Use AddressAliasesV2")]
4523 AddressAliases(
4524 #[serde(with = "nonempty_as_vec")] NonEmpty<(SuiAddress, Option<SequenceNumber>)>,
4525 ),
4526
4527 ImmutableInputObjects(Vec<ObjectID>),
4530
4531 AddressAliasesV2(#[serde(with = "nonempty_as_vec")] NonEmpty<(u8, Option<SequenceNumber>)>),
4535}
4536
4537#[derive(Clone, Debug, Serialize, Deserialize)]
4539pub struct TransactionWithClaims<T> {
4540 tx: T,
4541 claims: Vec<TransactionClaim>,
4542}
4543
4544impl<T> TransactionWithClaims<T> {
4545 pub fn new(tx: T, claims: Vec<TransactionClaim>) -> Self {
4546 Self { tx, claims }
4547 }
4548
4549 pub fn from_aliases(tx: T, aliases: NonEmpty<(u8, Option<SequenceNumber>)>) -> Self {
4551 Self {
4552 tx,
4553 claims: vec![TransactionClaim::AddressAliasesV2(aliases)],
4554 }
4555 }
4556
4557 pub fn no_aliases(tx: T) -> Self {
4559 Self { tx, claims: vec![] }
4560 }
4561
4562 pub fn tx(&self) -> &T {
4563 &self.tx
4564 }
4565
4566 pub fn into_tx(self) -> T {
4567 self.tx
4568 }
4569
4570 pub fn aliases(&self) -> Option<NonEmpty<(u8, Option<SequenceNumber>)>> {
4572 self.claims
4573 .iter()
4574 .find_map(|c| match c {
4575 TransactionClaim::AddressAliasesV2(aliases) => Some(aliases),
4576 _ => None,
4577 })
4578 .cloned()
4579 }
4580
4581 #[allow(deprecated)]
4583 pub fn aliases_v1(&self) -> Option<NonEmpty<(SuiAddress, Option<SequenceNumber>)>> {
4584 self.claims
4585 .iter()
4586 .find_map(|c| match c {
4587 TransactionClaim::AddressAliases(aliases) => Some(aliases),
4588 _ => None,
4589 })
4590 .cloned()
4591 }
4592
4593 pub fn get_immutable_objects(&self) -> Vec<ObjectID> {
4595 self.claims
4596 .iter()
4597 .find_map(|c| match c {
4598 TransactionClaim::ImmutableInputObjects(objs) => Some(objs.clone()),
4599 _ => None,
4600 })
4601 .unwrap_or_default()
4602 }
4603}
4604
4605pub type PlainTransactionWithClaims = TransactionWithClaims<Transaction>;
4606
4607impl<T: Message, S> From<WithAliases<VerifiedEnvelope<T, S>>>
4610 for TransactionWithClaims<Envelope<T, S>>
4611{
4612 fn from(value: WithAliases<VerifiedEnvelope<T, S>>) -> Self {
4613 let (tx, aliases) = value.into_inner();
4614 Self::from_aliases(tx.into(), aliases)
4615 }
4616}
4617
4618#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize, PartialOrd, Ord, Hash)]
4619pub enum InputObjectKind {
4620 MovePackage(ObjectID),
4622 ImmOrOwnedMoveObject(ObjectRef),
4624 SharedMoveObject {
4626 id: ObjectID,
4627 initial_shared_version: SequenceNumber,
4628 mutability: SharedObjectMutability,
4629 },
4630}
4631
4632#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize, PartialOrd, Ord, Hash)]
4633pub enum SharedObjectMutability {
4634 Immutable,
4636 Mutable,
4637 NonExclusiveWrite,
4641}
4642
4643impl SharedObjectMutability {
4644 pub fn is_exclusive(&self) -> bool {
4645 match self {
4646 SharedObjectMutability::Mutable => true,
4647 SharedObjectMutability::Immutable => false,
4648 SharedObjectMutability::NonExclusiveWrite => false,
4649 }
4650 }
4651}
4652
4653impl InputObjectKind {
4654 pub fn object_id(&self) -> ObjectID {
4655 self.full_object_id().id()
4656 }
4657
4658 pub fn full_object_id(&self) -> FullObjectID {
4659 match self {
4660 Self::MovePackage(id) => FullObjectID::Fastpath(*id),
4661 Self::ImmOrOwnedMoveObject((id, _, _)) => FullObjectID::Fastpath(*id),
4662 Self::SharedMoveObject {
4663 id,
4664 initial_shared_version,
4665 ..
4666 } => FullObjectID::Consensus((*id, *initial_shared_version)),
4667 }
4668 }
4669
4670 pub fn version(&self) -> Option<SequenceNumber> {
4671 match self {
4672 Self::MovePackage(..) => None,
4673 Self::ImmOrOwnedMoveObject((_, version, _)) => Some(*version),
4674 Self::SharedMoveObject { .. } => None,
4675 }
4676 }
4677
4678 pub fn object_not_found_error(&self) -> UserInputError {
4679 match *self {
4680 Self::MovePackage(package_id) => {
4681 UserInputError::DependentPackageNotFound { package_id }
4682 }
4683 Self::ImmOrOwnedMoveObject((object_id, version, _)) => UserInputError::ObjectNotFound {
4684 object_id,
4685 version: Some(version),
4686 },
4687 Self::SharedMoveObject { id, .. } => UserInputError::ObjectNotFound {
4688 object_id: id,
4689 version: None,
4690 },
4691 }
4692 }
4693
4694 pub fn is_shared_object(&self) -> bool {
4695 matches!(self, Self::SharedMoveObject { .. })
4696 }
4697}
4698
4699#[derive(Clone, Debug)]
4702pub struct ObjectReadResult {
4703 pub input_object_kind: InputObjectKind,
4704 pub object: ObjectReadResultKind,
4705}
4706
4707#[derive(Clone)]
4708pub enum ObjectReadResultKind {
4709 Object(Object),
4710 ObjectConsensusStreamEnded(SequenceNumber, TransactionDigest),
4713 CancelledTransactionSharedObject(SequenceNumber),
4715}
4716
4717impl ObjectReadResultKind {
4718 pub fn is_cancelled(&self) -> bool {
4719 matches!(
4720 self,
4721 ObjectReadResultKind::CancelledTransactionSharedObject(_)
4722 )
4723 }
4724
4725 pub fn version(&self) -> SequenceNumber {
4726 match self {
4727 ObjectReadResultKind::Object(object) => object.version(),
4728 ObjectReadResultKind::ObjectConsensusStreamEnded(seq, _) => *seq,
4729 ObjectReadResultKind::CancelledTransactionSharedObject(seq) => *seq,
4730 }
4731 }
4732}
4733
4734impl std::fmt::Debug for ObjectReadResultKind {
4735 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
4736 match self {
4737 ObjectReadResultKind::Object(obj) => {
4738 write!(f, "Object({:?})", obj.compute_object_reference())
4739 }
4740 ObjectReadResultKind::ObjectConsensusStreamEnded(seq, digest) => {
4741 write!(f, "ObjectConsensusStreamEnded({}, {:?})", seq, digest)
4742 }
4743 ObjectReadResultKind::CancelledTransactionSharedObject(seq) => {
4744 write!(f, "CancelledTransactionSharedObject({})", seq)
4745 }
4746 }
4747 }
4748}
4749
4750impl From<Object> for ObjectReadResultKind {
4751 fn from(object: Object) -> Self {
4752 Self::Object(object)
4753 }
4754}
4755
4756impl ObjectReadResult {
4757 pub fn new(input_object_kind: InputObjectKind, object: ObjectReadResultKind) -> Self {
4758 if let (
4759 InputObjectKind::ImmOrOwnedMoveObject(_),
4760 ObjectReadResultKind::ObjectConsensusStreamEnded(_, _),
4761 ) = (&input_object_kind, &object)
4762 {
4763 panic!("only consensus objects can be ObjectConsensusStreamEnded");
4764 }
4765
4766 if let (
4767 InputObjectKind::ImmOrOwnedMoveObject(_),
4768 ObjectReadResultKind::CancelledTransactionSharedObject(_),
4769 ) = (&input_object_kind, &object)
4770 {
4771 panic!("only consensus objects can be CancelledTransactionSharedObject");
4772 }
4773
4774 Self {
4775 input_object_kind,
4776 object,
4777 }
4778 }
4779
4780 pub fn id(&self) -> ObjectID {
4781 self.input_object_kind.object_id()
4782 }
4783
4784 pub fn as_object(&self) -> Option<&Object> {
4785 match &self.object {
4786 ObjectReadResultKind::Object(object) => Some(object),
4787 ObjectReadResultKind::ObjectConsensusStreamEnded(_, _) => None,
4788 ObjectReadResultKind::CancelledTransactionSharedObject(_) => None,
4789 }
4790 }
4791
4792 pub fn new_from_gas_object(gas: &Object) -> Self {
4793 let objref = gas.compute_object_reference();
4794 Self {
4795 input_object_kind: InputObjectKind::ImmOrOwnedMoveObject(objref),
4796 object: ObjectReadResultKind::Object(gas.clone()),
4797 }
4798 }
4799
4800 pub fn is_mutable(&self) -> bool {
4801 match (&self.input_object_kind, &self.object) {
4802 (InputObjectKind::MovePackage(_), _) => false,
4803 (InputObjectKind::ImmOrOwnedMoveObject(_), ObjectReadResultKind::Object(object)) => {
4804 !object.is_immutable()
4805 }
4806 (
4807 InputObjectKind::ImmOrOwnedMoveObject(_),
4808 ObjectReadResultKind::ObjectConsensusStreamEnded(_, _),
4809 ) => unreachable!(),
4810 (
4811 InputObjectKind::ImmOrOwnedMoveObject(_),
4812 ObjectReadResultKind::CancelledTransactionSharedObject(_),
4813 ) => unreachable!(),
4814 (InputObjectKind::SharedMoveObject { mutability, .. }, _) => match mutability {
4815 SharedObjectMutability::Mutable => true,
4816 SharedObjectMutability::Immutable => false,
4817 SharedObjectMutability::NonExclusiveWrite => false,
4818 },
4819 }
4820 }
4821
4822 pub fn is_shared_object(&self) -> bool {
4823 self.input_object_kind.is_shared_object()
4824 }
4825
4826 pub fn is_consensus_stream_ended(&self) -> bool {
4827 self.consensus_stream_end_info().is_some()
4828 }
4829
4830 pub fn consensus_stream_end_info(&self) -> Option<(SequenceNumber, TransactionDigest)> {
4831 match &self.object {
4832 ObjectReadResultKind::ObjectConsensusStreamEnded(v, tx) => Some((*v, *tx)),
4833 _ => None,
4834 }
4835 }
4836
4837 pub fn get_address_owned_objref(&self) -> Option<ObjectRef> {
4839 match (&self.input_object_kind, &self.object) {
4840 (InputObjectKind::MovePackage(_), _) => None,
4841 (
4842 InputObjectKind::ImmOrOwnedMoveObject(objref),
4843 ObjectReadResultKind::Object(object),
4844 ) => {
4845 if object.is_immutable() {
4846 None
4847 } else {
4848 Some(*objref)
4849 }
4850 }
4851 (
4852 InputObjectKind::ImmOrOwnedMoveObject(_),
4853 ObjectReadResultKind::ObjectConsensusStreamEnded(_, _),
4854 ) => unreachable!(),
4855 (
4856 InputObjectKind::ImmOrOwnedMoveObject(_),
4857 ObjectReadResultKind::CancelledTransactionSharedObject(_),
4858 ) => unreachable!(),
4859 (InputObjectKind::SharedMoveObject { .. }, _) => None,
4860 }
4861 }
4862
4863 pub fn is_address_owned(&self) -> bool {
4864 self.get_address_owned_objref().is_some()
4865 }
4866
4867 pub fn is_replay_protected_input(&self) -> bool {
4868 if let InputObjectKind::ImmOrOwnedMoveObject(obj_ref) = &self.input_object_kind
4869 && ParsedDigest::is_coin_reservation_digest(&obj_ref.2)
4870 {
4871 true
4872 } else {
4873 self.is_address_owned()
4874 }
4875 }
4876
4877 pub fn to_shared_input(&self) -> Option<SharedInput> {
4878 match self.input_object_kind {
4879 InputObjectKind::MovePackage(_) => None,
4880 InputObjectKind::ImmOrOwnedMoveObject(_) => None,
4881 InputObjectKind::SharedMoveObject { id, mutability, .. } => Some(match &self.object {
4882 ObjectReadResultKind::Object(obj) => {
4883 SharedInput::Existing(obj.compute_object_reference())
4884 }
4885 ObjectReadResultKind::ObjectConsensusStreamEnded(seq, digest) => {
4886 SharedInput::ConsensusStreamEnded((id, *seq, mutability, *digest))
4887 }
4888 ObjectReadResultKind::CancelledTransactionSharedObject(seq) => {
4889 SharedInput::Cancelled((id, *seq))
4890 }
4891 }),
4892 }
4893 }
4894
4895 pub fn get_previous_transaction(&self) -> Option<TransactionDigest> {
4896 match &self.object {
4897 ObjectReadResultKind::Object(obj) => Some(obj.previous_transaction),
4898 ObjectReadResultKind::ObjectConsensusStreamEnded(_, digest) => Some(*digest),
4899 ObjectReadResultKind::CancelledTransactionSharedObject(_) => None,
4900 }
4901 }
4902}
4903
4904#[derive(Clone)]
4905pub struct InputObjects {
4906 objects: Vec<ObjectReadResult>,
4907}
4908
4909impl std::fmt::Debug for InputObjects {
4910 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
4911 f.debug_list().entries(self.objects.iter()).finish()
4912 }
4913}
4914
4915#[derive(Clone)]
4918pub struct CheckedInputObjects(InputObjects);
4919
4920impl CheckedInputObjects {
4926 pub fn new_with_checked_transaction_inputs(inputs: InputObjects) -> Self {
4928 Self(inputs)
4929 }
4930
4931 pub fn new_for_genesis(input_objects: Vec<ObjectReadResult>) -> Self {
4933 Self(InputObjects::new(input_objects))
4934 }
4935
4936 pub fn new_for_replay(input_objects: InputObjects) -> Self {
4938 Self(input_objects)
4939 }
4940
4941 pub fn inner(&self) -> &InputObjects {
4942 &self.0
4943 }
4944
4945 pub fn into_inner(self) -> InputObjects {
4946 self.0
4947 }
4948}
4949
4950impl From<Vec<ObjectReadResult>> for InputObjects {
4951 fn from(objects: Vec<ObjectReadResult>) -> Self {
4952 Self::new(objects)
4953 }
4954}
4955
4956impl InputObjects {
4957 pub fn new(objects: Vec<ObjectReadResult>) -> Self {
4958 Self { objects }
4959 }
4960
4961 pub fn len(&self) -> usize {
4962 self.objects.len()
4963 }
4964
4965 pub fn is_empty(&self) -> bool {
4966 self.objects.is_empty()
4967 }
4968
4969 pub fn contains_consensus_stream_ended_objects(&self) -> bool {
4970 self.objects
4971 .iter()
4972 .any(|obj| obj.is_consensus_stream_ended())
4973 }
4974
4975 pub fn get_cancelled_objects(&self) -> Option<(Vec<ObjectID>, SequenceNumber)> {
4978 let mut contains_cancelled = false;
4979 let mut cancel_reason = None;
4980 let mut cancelled_objects = Vec::new();
4981 for obj in &self.objects {
4982 if let ObjectReadResultKind::CancelledTransactionSharedObject(version) = obj.object {
4983 contains_cancelled = true;
4984 if version == SequenceNumber::CONGESTED
4985 || version == SequenceNumber::RANDOMNESS_UNAVAILABLE
4986 {
4987 assert!(cancel_reason.is_none() || cancel_reason == Some(version));
4989 cancel_reason = Some(version);
4990 cancelled_objects.push(obj.id());
4991 }
4992 }
4993 }
4994
4995 if !cancelled_objects.is_empty() {
4996 Some((
4997 cancelled_objects,
4998 cancel_reason
4999 .expect("there should be a cancel reason if there are cancelled objects"),
5000 ))
5001 } else {
5002 assert!(!contains_cancelled);
5003 None
5004 }
5005 }
5006
5007 pub fn filter_owned_objects(&self) -> Vec<ObjectRef> {
5008 let owned_objects: Vec<_> = self
5009 .objects
5010 .iter()
5011 .filter_map(|obj| obj.get_address_owned_objref())
5012 .collect();
5013
5014 trace!(
5015 num_mutable_objects = owned_objects.len(),
5016 "Checked locks and found mutable objects"
5017 );
5018
5019 owned_objects
5020 }
5021
5022 pub fn filter_shared_objects(&self) -> Vec<SharedInput> {
5023 self.objects
5024 .iter()
5025 .filter(|obj| obj.is_shared_object())
5026 .map(|obj| {
5027 obj.to_shared_input()
5028 .expect("already filtered for shared objects")
5029 })
5030 .collect()
5031 }
5032
5033 pub fn transaction_dependencies(&self) -> BTreeSet<TransactionDigest> {
5034 self.objects
5035 .iter()
5036 .filter_map(|obj| obj.get_previous_transaction())
5037 .collect()
5038 }
5039
5040 pub fn exclusive_mutable_inputs(&self) -> BTreeMap<ObjectID, (VersionDigest, Owner)> {
5043 self.mutables_with_input_kinds()
5044 .filter_map(|(id, (version, owner, kind))| match kind {
5045 InputObjectKind::SharedMoveObject { mutability, .. } => match mutability {
5046 SharedObjectMutability::Mutable => Some((id, (version, owner))),
5047 SharedObjectMutability::Immutable => None,
5048 SharedObjectMutability::NonExclusiveWrite => None,
5049 },
5050 _ => Some((id, (version, owner))),
5051 })
5052 .collect()
5053 }
5054
5055 pub fn non_exclusive_input_objects(&self) -> BTreeMap<ObjectID, Object> {
5056 self.objects
5057 .iter()
5058 .filter_map(|read_result| {
5059 match (read_result.as_object(), read_result.input_object_kind) {
5060 (
5061 Some(object),
5062 InputObjectKind::SharedMoveObject {
5063 mutability: SharedObjectMutability::NonExclusiveWrite,
5064 ..
5065 },
5066 ) => Some((read_result.id(), object.clone())),
5067 _ => None,
5068 }
5069 })
5070 .collect()
5071 }
5072
5073 pub fn all_mutable_inputs(&self) -> BTreeMap<ObjectID, (VersionDigest, Owner)> {
5076 self.mutables_with_input_kinds()
5077 .filter_map(|(id, (version, owner, kind))| match kind {
5078 InputObjectKind::SharedMoveObject { mutability, .. } => match mutability {
5079 SharedObjectMutability::Mutable => Some((id, (version, owner))),
5080 SharedObjectMutability::Immutable => None,
5081 SharedObjectMutability::NonExclusiveWrite => Some((id, (version, owner))),
5082 },
5083 _ => Some((id, (version, owner))),
5084 })
5085 .collect()
5086 }
5087
5088 fn mutables_with_input_kinds(
5089 &self,
5090 ) -> impl Iterator<Item = (ObjectID, (VersionDigest, Owner, InputObjectKind))> + '_ {
5091 self.objects.iter().filter_map(
5092 |ObjectReadResult {
5093 input_object_kind,
5094 object,
5095 }| match (input_object_kind, object) {
5096 (InputObjectKind::MovePackage(_), _) => None,
5097 (
5098 InputObjectKind::ImmOrOwnedMoveObject(object_ref),
5099 ObjectReadResultKind::Object(object),
5100 ) => {
5101 if object.is_immutable() {
5102 None
5103 } else {
5104 Some((
5105 object_ref.0,
5106 (
5107 (object_ref.1, object_ref.2),
5108 object.owner.clone(),
5109 *input_object_kind,
5110 ),
5111 ))
5112 }
5113 }
5114 (
5115 InputObjectKind::ImmOrOwnedMoveObject(_),
5116 ObjectReadResultKind::ObjectConsensusStreamEnded(_, _),
5117 ) => {
5118 unreachable!()
5119 }
5120 (
5121 InputObjectKind::SharedMoveObject { .. },
5122 ObjectReadResultKind::ObjectConsensusStreamEnded(_, _),
5123 ) => None,
5124 (
5125 InputObjectKind::SharedMoveObject { mutability, .. },
5126 ObjectReadResultKind::Object(object),
5127 ) => match *mutability {
5128 SharedObjectMutability::Mutable => {
5129 let oref = object.compute_object_reference();
5130 Some((
5131 oref.0,
5132 ((oref.1, oref.2), object.owner.clone(), *input_object_kind),
5133 ))
5134 }
5135 SharedObjectMutability::Immutable => None,
5136 SharedObjectMutability::NonExclusiveWrite => {
5137 let oref = object.compute_object_reference();
5138 Some((
5139 oref.0,
5140 ((oref.1, oref.2), object.owner.clone(), *input_object_kind),
5141 ))
5142 }
5143 },
5144 (
5145 InputObjectKind::ImmOrOwnedMoveObject(_),
5146 ObjectReadResultKind::CancelledTransactionSharedObject(_),
5147 ) => {
5148 unreachable!()
5149 }
5150 (
5151 InputObjectKind::SharedMoveObject { .. },
5152 ObjectReadResultKind::CancelledTransactionSharedObject(_),
5153 ) => None,
5154 },
5155 )
5156 }
5157
5158 pub fn lamport_timestamp(&self, receiving_objects: &[ObjectRef]) -> SequenceNumber {
5162 let input_versions = self
5163 .objects
5164 .iter()
5165 .filter_map(|object| match &object.object {
5166 ObjectReadResultKind::Object(object) => {
5167 object.data.try_as_move().map(MoveObject::version)
5168 }
5169 ObjectReadResultKind::ObjectConsensusStreamEnded(v, _) => Some(*v),
5170 ObjectReadResultKind::CancelledTransactionSharedObject(_) => None,
5171 })
5172 .chain(receiving_objects.iter().map(|object_ref| object_ref.1));
5173
5174 SequenceNumber::lamport_increment(input_versions)
5175 }
5176
5177 pub fn object_kinds(&self) -> impl Iterator<Item = &InputObjectKind> {
5178 self.objects.iter().map(
5179 |ObjectReadResult {
5180 input_object_kind, ..
5181 }| input_object_kind,
5182 )
5183 }
5184
5185 pub fn consensus_stream_ended_objects(&self) -> BTreeMap<ObjectID, SequenceNumber> {
5186 self.objects
5187 .iter()
5188 .filter_map(|obj| {
5189 if let InputObjectKind::SharedMoveObject {
5190 id,
5191 initial_shared_version,
5192 ..
5193 } = obj.input_object_kind
5194 {
5195 obj.is_consensus_stream_ended()
5196 .then_some((id, initial_shared_version))
5197 } else {
5198 None
5199 }
5200 })
5201 .collect()
5202 }
5203
5204 pub fn into_object_map(self) -> BTreeMap<ObjectID, Object> {
5205 self.objects
5206 .into_iter()
5207 .filter_map(|o| o.as_object().map(|object| (o.id(), object.clone())))
5208 .collect()
5209 }
5210
5211 pub fn push(&mut self, object: ObjectReadResult) {
5212 self.objects.push(object);
5213 }
5214
5215 pub fn iter(&self) -> impl Iterator<Item = &ObjectReadResult> {
5216 self.objects.iter()
5217 }
5218
5219 pub fn iter_objects(&self) -> impl Iterator<Item = &Object> {
5220 self.objects.iter().filter_map(|o| o.as_object())
5221 }
5222
5223 pub fn non_exclusive_mutable_inputs(
5224 &self,
5225 ) -> impl Iterator<Item = (ObjectID, SequenceNumber)> + '_ {
5226 self.objects.iter().filter_map(
5227 |ObjectReadResult {
5228 input_object_kind,
5229 object,
5230 }| match input_object_kind {
5231 InputObjectKind::SharedMoveObject {
5235 id,
5236 mutability: SharedObjectMutability::NonExclusiveWrite,
5237 ..
5238 } if !object.is_cancelled() => Some((*id, object.version())),
5239 _ => None,
5240 },
5241 )
5242 }
5243}
5244
5245#[derive(Clone, Debug)]
5249pub enum ReceivingObjectReadResultKind {
5250 Object(Object),
5251 PreviouslyReceivedObject,
5253}
5254
5255impl ReceivingObjectReadResultKind {
5256 pub fn as_object(&self) -> Option<&Object> {
5257 match &self {
5258 Self::Object(object) => Some(object),
5259 Self::PreviouslyReceivedObject => None,
5260 }
5261 }
5262}
5263
5264pub struct ReceivingObjectReadResult {
5265 pub object_ref: ObjectRef,
5266 pub object: ReceivingObjectReadResultKind,
5267}
5268
5269impl ReceivingObjectReadResult {
5270 pub fn new(object_ref: ObjectRef, object: ReceivingObjectReadResultKind) -> Self {
5271 Self { object_ref, object }
5272 }
5273
5274 pub fn is_previously_received(&self) -> bool {
5275 matches!(
5276 self.object,
5277 ReceivingObjectReadResultKind::PreviouslyReceivedObject
5278 )
5279 }
5280}
5281
5282impl From<Object> for ReceivingObjectReadResultKind {
5283 fn from(object: Object) -> Self {
5284 Self::Object(object)
5285 }
5286}
5287
5288pub struct ReceivingObjects {
5289 pub objects: Vec<ReceivingObjectReadResult>,
5290}
5291
5292impl ReceivingObjects {
5293 pub fn iter(&self) -> impl Iterator<Item = &ReceivingObjectReadResult> {
5294 self.objects.iter()
5295 }
5296
5297 pub fn iter_objects(&self) -> impl Iterator<Item = &Object> {
5298 self.objects.iter().filter_map(|o| o.object.as_object())
5299 }
5300}
5301
5302impl From<Vec<ReceivingObjectReadResult>> for ReceivingObjects {
5303 fn from(objects: Vec<ReceivingObjectReadResult>) -> Self {
5304 Self { objects }
5305 }
5306}
5307
5308impl Display for CertifiedTransaction {
5309 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
5310 let mut writer = String::new();
5311 writeln!(writer, "Transaction Hash: {:?}", self.digest())?;
5312 writeln!(
5313 writer,
5314 "Signed Authorities Bitmap : {:?}",
5315 self.auth_sig().signers_map
5316 )?;
5317 write!(writer, "{}", &self.data().intent_message().value.kind())?;
5318 write!(f, "{}", writer)
5319 }
5320}
5321
5322#[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
5326pub enum TransactionKey {
5327 Digest(TransactionDigest),
5328 RandomnessRound(EpochId, RandomnessRound),
5329 AccumulatorSettlement(EpochId, u64 ),
5330 ConsensusCommitPrologue(EpochId, u64 , u32 ),
5331}
5332
5333impl TransactionKey {
5334 pub fn unwrap_digest(&self) -> &TransactionDigest {
5335 match self {
5336 TransactionKey::Digest(d) => d,
5337 _ => panic!("called unwrap_digest on a non-Digest TransactionKey: {self:?}"),
5338 }
5339 }
5340
5341 pub fn as_digest(&self) -> Option<&TransactionDigest> {
5342 match self {
5343 TransactionKey::Digest(d) => Some(d),
5344 _ => None,
5345 }
5346 }
5347}