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