1use super::{SUI_BRIDGE_OBJECT_ID, base_types::*, error::*};
6use crate::accumulator_root::{AccumulatorObjId, AccumulatorValue};
7use crate::authenticator_state::ActiveJwk;
8use crate::balance::Balance;
9use crate::committee::{Committee, EpochId, ProtocolVersion};
10use crate::crypto::{
11 AuthoritySignInfo, AuthoritySignInfoTrait, AuthoritySignature, AuthorityStrongQuorumSignInfo,
12 DefaultHash, Ed25519SuiSignature, EmptySignInfo, RandomnessRound, Signature, Signer,
13 SuiSignatureInner, ToFromBytes, default_hash,
14};
15use crate::digests::{AdditionalConsensusStateDigest, CertificateDigest, SenderSignedDataDigest};
16use crate::digests::{ChainIdentifier, ConsensusCommitDigest, ZKLoginInputsDigest};
17use crate::execution::{ExecutionTimeObservationKey, SharedInput};
18use crate::gas_coin::GAS;
19use crate::gas_model::gas_predicates::check_for_gas_price_too_high;
20use crate::gas_model::gas_v2::SuiCostTable;
21use crate::message_envelope::{Envelope, Message, TrustedEnvelope, VerifiedEnvelope};
22use crate::messages_checkpoint::CheckpointTimestamp;
23use crate::messages_consensus::{
24 ConsensusCommitPrologue, ConsensusCommitPrologueV2, ConsensusCommitPrologueV3,
25 ConsensusCommitPrologueV4, ConsensusDeterminedVersionAssignments,
26};
27use crate::object::{MoveObject, Object, Owner};
28use crate::programmable_transaction_builder::ProgrammableTransactionBuilder;
29use crate::signature::{GenericSignature, VerifyParams};
30use crate::signature_verification::{
31 VerifiedDigestCache, verify_sender_signed_data_message_signatures,
32};
33use crate::type_input::TypeInput;
34use crate::{
35 SUI_AUTHENTICATOR_STATE_OBJECT_ID, SUI_CLOCK_OBJECT_ID, SUI_CLOCK_OBJECT_SHARED_VERSION,
36 SUI_FRAMEWORK_PACKAGE_ID, SUI_RANDOMNESS_STATE_OBJECT_ID, SUI_SYSTEM_STATE_OBJECT_ID,
37 SUI_SYSTEM_STATE_OBJECT_SHARED_VERSION,
38};
39use enum_dispatch::enum_dispatch;
40use fastcrypto::{encoding::Base64, hash::HashFunction};
41use itertools::{Either, Itertools};
42use move_core_types::{ident_str, identifier};
43use move_core_types::{identifier::Identifier, language_storage::TypeTag};
44use nonempty::{NonEmpty, nonempty};
45use serde::{Deserialize, Serialize};
46use shared_crypto::intent::{Intent, IntentMessage, IntentScope};
47use std::fmt::Write;
48use std::fmt::{Debug, Display, Formatter};
49use std::iter::once;
50use std::sync::Arc;
51use std::time::Duration;
52use std::{
53 collections::{BTreeMap, BTreeSet, HashSet},
54 hash::Hash,
55 iter,
56};
57use strum::IntoStaticStr;
58use sui_protocol_config::{PerObjectCongestionControlMode, ProtocolConfig};
59use tap::Pipe;
60use tracing::trace;
61
62#[cfg(test)]
63#[path = "unit_tests/transaction_serialization_tests.rs"]
64mod transaction_serialization_tests;
65
66pub const TEST_ONLY_GAS_UNIT_FOR_TRANSFER: u64 = 10_000;
67pub const TEST_ONLY_GAS_UNIT_FOR_OBJECT_BASICS: u64 = 50_000;
68pub const TEST_ONLY_GAS_UNIT_FOR_PUBLISH: u64 = 70_000;
69pub const TEST_ONLY_GAS_UNIT_FOR_STAKING: u64 = 50_000;
70pub const TEST_ONLY_GAS_UNIT_FOR_GENERIC: u64 = 50_000;
71pub const TEST_ONLY_GAS_UNIT_FOR_SPLIT_COIN: u64 = 10_000;
72pub const TEST_ONLY_GAS_UNIT_FOR_HEAVY_COMPUTATION_STORAGE: u64 = 5_000_000;
77
78pub const GAS_PRICE_FOR_SYSTEM_TX: u64 = 1;
79
80pub const DEFAULT_VALIDATOR_GAS_PRICE: u64 = 1000;
81
82const BLOCKED_MOVE_FUNCTIONS: [(ObjectID, &str, &str); 0] = [];
83
84#[cfg(test)]
85#[path = "unit_tests/messages_tests.rs"]
86mod messages_tests;
87
88#[cfg(test)]
89#[path = "unit_tests/balance_withdraw_tests.rs"]
90mod balance_withdraw_tests;
91
92#[cfg(test)]
93#[path = "unit_tests/address_balance_gas_tests.rs"]
94mod address_balance_gas_tests;
95
96#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)]
97pub enum CallArg {
98 Pure(Vec<u8>),
100 Object(ObjectArg),
102 FundsWithdrawal(FundsWithdrawalArg),
106}
107
108impl CallArg {
109 pub const SUI_SYSTEM_MUT: Self = Self::Object(ObjectArg::SUI_SYSTEM_MUT);
110 pub const CLOCK_IMM: Self = Self::Object(ObjectArg::SharedObject {
111 id: SUI_CLOCK_OBJECT_ID,
112 initial_shared_version: SUI_CLOCK_OBJECT_SHARED_VERSION,
113 mutability: SharedObjectMutability::Immutable,
114 });
115 pub const CLOCK_MUT: Self = Self::Object(ObjectArg::SharedObject {
116 id: SUI_CLOCK_OBJECT_ID,
117 initial_shared_version: SUI_CLOCK_OBJECT_SHARED_VERSION,
118 mutability: SharedObjectMutability::Mutable,
119 });
120}
121
122#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy, Serialize, Deserialize)]
123pub enum ObjectArg {
124 ImmOrOwnedObject(ObjectRef),
126 SharedObject {
129 id: ObjectID,
130 initial_shared_version: SequenceNumber,
131 mutability: SharedObjectMutability,
134 },
135 Receiving(ObjectRef),
137}
138
139#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)]
140pub enum Reservation {
141 EntireBalance,
144 MaxAmountU64(u64),
146}
147
148#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)]
149pub enum WithdrawalTypeArg {
150 Balance(TypeInput),
151}
152
153impl WithdrawalTypeArg {
154 pub fn to_type_tag(&self) -> UserInputResult<TypeTag> {
157 match self {
158 WithdrawalTypeArg::Balance(type_param) => {
159 Ok(Balance::type_tag(type_param.to_type_tag().map_err(
160 |e| UserInputError::InvalidWithdrawReservation {
161 error: e.to_string(),
162 },
163 )?))
164 }
165 }
166 }
167
168 pub fn get_balance_type_param(&self) -> anyhow::Result<Option<TypeTag>> {
172 match self {
173 WithdrawalTypeArg::Balance(type_param) => type_param.to_type_tag().map(Some),
174 }
175 }
176}
177
178#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)]
180pub struct FundsWithdrawalArg {
181 pub reservation: Reservation,
183 pub type_arg: WithdrawalTypeArg,
185 pub withdraw_from: WithdrawFrom,
187}
188
189#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)]
190pub enum WithdrawFrom {
191 Sender,
193 Sponsor,
195 }
197
198impl FundsWithdrawalArg {
199 pub fn balance_from_sender(amount: u64, balance_type: TypeInput) -> Self {
201 Self {
202 reservation: Reservation::MaxAmountU64(amount),
203 type_arg: WithdrawalTypeArg::Balance(balance_type),
204 withdraw_from: WithdrawFrom::Sender,
205 }
206 }
207
208 pub fn balance_from_sponsor(amount: u64, balance_type: TypeInput) -> Self {
210 Self {
211 reservation: Reservation::MaxAmountU64(amount),
212 type_arg: WithdrawalTypeArg::Balance(balance_type),
213 withdraw_from: WithdrawFrom::Sponsor,
214 }
215 }
216
217 fn owner_for_withdrawal(&self, tx: &impl TransactionDataAPI) -> SuiAddress {
218 match self.withdraw_from {
219 WithdrawFrom::Sender => tx.sender(),
220 WithdrawFrom::Sponsor => tx.gas_owner(),
221 }
222 }
223}
224
225fn type_input_validity_check(
226 tag: &TypeInput,
227 config: &ProtocolConfig,
228 starting_count: &mut usize,
229) -> UserInputResult<()> {
230 let mut stack = vec![(tag, 1)];
231 while let Some((tag, depth)) = stack.pop() {
232 *starting_count += 1;
233 fp_ensure!(
234 *starting_count < config.max_type_arguments() as usize,
235 UserInputError::SizeLimitExceeded {
236 limit: "maximum type arguments in a call transaction".to_string(),
237 value: config.max_type_arguments().to_string()
238 }
239 );
240 fp_ensure!(
241 depth < config.max_type_argument_depth(),
242 UserInputError::SizeLimitExceeded {
243 limit: "maximum type argument depth in a call transaction".to_string(),
244 value: config.max_type_argument_depth().to_string()
245 }
246 );
247 match tag {
248 TypeInput::Bool
249 | TypeInput::U8
250 | TypeInput::U64
251 | TypeInput::U128
252 | TypeInput::Address
253 | TypeInput::Signer
254 | TypeInput::U16
255 | TypeInput::U32
256 | TypeInput::U256 => (),
257 TypeInput::Vector(t) => {
258 stack.push((t, depth + 1));
259 }
260 TypeInput::Struct(s) => {
261 let next_depth = depth + 1;
262 if config.validate_identifier_inputs() {
263 fp_ensure!(
264 identifier::is_valid(&s.module),
265 UserInputError::InvalidIdentifier {
266 error: s.module.clone()
267 }
268 );
269 fp_ensure!(
270 identifier::is_valid(&s.name),
271 UserInputError::InvalidIdentifier {
272 error: s.name.clone()
273 }
274 );
275 }
276 stack.extend(s.type_params.iter().map(|t| (t, next_depth)));
277 }
278 }
279 }
280 Ok(())
281}
282
283#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)]
284pub struct ChangeEpoch {
285 pub epoch: EpochId,
287 pub protocol_version: ProtocolVersion,
289 pub storage_charge: u64,
291 pub computation_charge: u64,
293 pub storage_rebate: u64,
295 pub non_refundable_storage_fee: u64,
297 pub epoch_start_timestamp_ms: u64,
299 pub system_packages: Vec<(SequenceNumber, Vec<Vec<u8>>, Vec<ObjectID>)>,
305}
306
307#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)]
308pub struct GenesisTransaction {
309 pub objects: Vec<GenesisObject>,
310}
311
312#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)]
313pub enum GenesisObject {
314 RawObject {
315 data: crate::object::Data,
316 owner: crate::object::Owner,
317 },
318}
319
320impl GenesisObject {
321 pub fn id(&self) -> ObjectID {
322 match self {
323 GenesisObject::RawObject { data, .. } => data.id(),
324 }
325 }
326}
327
328#[derive(Debug, Hash, PartialEq, Eq, Clone, Serialize, Deserialize)]
329pub struct AuthenticatorStateExpire {
330 pub min_epoch: u64,
332 pub authenticator_obj_initial_shared_version: SequenceNumber,
334}
335
336impl AuthenticatorStateExpire {
337 pub fn authenticator_obj_initial_shared_version(&self) -> SequenceNumber {
338 self.authenticator_obj_initial_shared_version
339 }
340}
341
342#[derive(Debug, Hash, PartialEq, Eq, Clone, Serialize, Deserialize)]
343pub enum StoredExecutionTimeObservations {
344 V1(Vec<(ExecutionTimeObservationKey, Vec<(AuthorityName, Duration)>)>),
345}
346
347impl StoredExecutionTimeObservations {
348 pub fn unwrap_v1(self) -> Vec<(ExecutionTimeObservationKey, Vec<(AuthorityName, Duration)>)> {
349 match self {
350 Self::V1(observations) => observations,
351 }
352 }
353
354 pub fn filter_and_sort_v1<P>(&self, predicate: P, limit: usize) -> Self
355 where
356 P: FnMut(&&(ExecutionTimeObservationKey, Vec<(AuthorityName, Duration)>)) -> bool,
357 {
358 match self {
359 Self::V1(observations) => Self::V1(
360 observations
361 .iter()
362 .filter(predicate)
363 .sorted_by_key(|(key, _)| key)
364 .take(limit)
365 .cloned()
366 .collect(),
367 ),
368 }
369 }
370
371 pub fn chunk_observations(&self, chunk_size: usize) -> Vec<Self> {
374 match self {
375 Self::V1(observations) => {
376 if chunk_size == 0 {
377 return vec![];
378 }
379 observations
380 .chunks(chunk_size)
381 .map(|chunk| Self::V1(chunk.to_vec()))
382 .collect()
383 }
384 }
385 }
386
387 pub fn merge_sorted_chunks(chunks: Vec<Self>) -> Self {
390 let mut all_observations = Vec::new();
391
392 for chunk in chunks {
393 match chunk {
394 Self::V1(observations) => {
395 all_observations.extend(observations);
396 }
397 }
398 }
399
400 Self::V1(all_observations)
401 }
402}
403
404#[derive(Debug, Hash, PartialEq, Eq, Clone, Serialize, Deserialize)]
405pub struct AuthenticatorStateUpdate {
406 pub epoch: u64,
408 pub round: u64,
410 pub new_active_jwks: Vec<ActiveJwk>,
412 pub authenticator_obj_initial_shared_version: SequenceNumber,
414 }
417
418impl AuthenticatorStateUpdate {
419 pub fn authenticator_obj_initial_shared_version(&self) -> SequenceNumber {
420 self.authenticator_obj_initial_shared_version
421 }
422}
423
424#[derive(Debug, Hash, PartialEq, Eq, Clone, Serialize, Deserialize)]
425pub struct RandomnessStateUpdate {
426 pub epoch: u64,
428 pub randomness_round: RandomnessRound,
430 pub random_bytes: Vec<u8>,
432 pub randomness_obj_initial_shared_version: SequenceNumber,
434 }
437
438impl RandomnessStateUpdate {
439 pub fn randomness_obj_initial_shared_version(&self) -> SequenceNumber {
440 self.randomness_obj_initial_shared_version
441 }
442}
443
444#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize, IntoStaticStr)]
445pub enum TransactionKind {
446 ProgrammableTransaction(ProgrammableTransaction),
448 ChangeEpoch(ChangeEpoch),
460 Genesis(GenesisTransaction),
461 ConsensusCommitPrologue(ConsensusCommitPrologue),
462 AuthenticatorStateUpdate(AuthenticatorStateUpdate),
463
464 EndOfEpochTransaction(Vec<EndOfEpochTransactionKind>),
467
468 RandomnessStateUpdate(RandomnessStateUpdate),
469 ConsensusCommitPrologueV2(ConsensusCommitPrologueV2),
471
472 ConsensusCommitPrologueV3(ConsensusCommitPrologueV3),
473 ConsensusCommitPrologueV4(ConsensusCommitPrologueV4),
474
475 ProgrammableSystemTransaction(ProgrammableTransaction),
477 }
479
480#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize, IntoStaticStr)]
482pub enum EndOfEpochTransactionKind {
483 ChangeEpoch(ChangeEpoch),
484 AuthenticatorStateCreate,
485 AuthenticatorStateExpire(AuthenticatorStateExpire),
486 RandomnessStateCreate,
487 DenyListStateCreate,
488 BridgeStateCreate(ChainIdentifier),
489 BridgeCommitteeInit(SequenceNumber),
490 StoreExecutionTimeObservations(StoredExecutionTimeObservations),
491 AccumulatorRootCreate,
492 CoinRegistryCreate,
493 DisplayRegistryCreate,
494}
495
496impl EndOfEpochTransactionKind {
497 pub fn new_change_epoch(
498 next_epoch: EpochId,
499 protocol_version: ProtocolVersion,
500 storage_charge: u64,
501 computation_charge: u64,
502 storage_rebate: u64,
503 non_refundable_storage_fee: u64,
504 epoch_start_timestamp_ms: u64,
505 system_packages: Vec<(SequenceNumber, Vec<Vec<u8>>, Vec<ObjectID>)>,
506 ) -> Self {
507 Self::ChangeEpoch(ChangeEpoch {
508 epoch: next_epoch,
509 protocol_version,
510 storage_charge,
511 computation_charge,
512 storage_rebate,
513 non_refundable_storage_fee,
514 epoch_start_timestamp_ms,
515 system_packages,
516 })
517 }
518
519 pub fn new_authenticator_state_expire(
520 min_epoch: u64,
521 authenticator_obj_initial_shared_version: SequenceNumber,
522 ) -> Self {
523 Self::AuthenticatorStateExpire(AuthenticatorStateExpire {
524 min_epoch,
525 authenticator_obj_initial_shared_version,
526 })
527 }
528
529 pub fn new_authenticator_state_create() -> Self {
530 Self::AuthenticatorStateCreate
531 }
532
533 pub fn new_randomness_state_create() -> Self {
534 Self::RandomnessStateCreate
535 }
536
537 pub fn new_accumulator_root_create() -> Self {
538 Self::AccumulatorRootCreate
539 }
540
541 pub fn new_coin_registry_create() -> Self {
542 Self::CoinRegistryCreate
543 }
544
545 pub fn new_display_registry_create() -> Self {
546 Self::DisplayRegistryCreate
547 }
548
549 pub fn new_deny_list_state_create() -> Self {
550 Self::DenyListStateCreate
551 }
552
553 pub fn new_bridge_create(chain_identifier: ChainIdentifier) -> Self {
554 Self::BridgeStateCreate(chain_identifier)
555 }
556
557 pub fn init_bridge_committee(bridge_shared_version: SequenceNumber) -> Self {
558 Self::BridgeCommitteeInit(bridge_shared_version)
559 }
560
561 pub fn new_store_execution_time_observations(
562 estimates: StoredExecutionTimeObservations,
563 ) -> Self {
564 Self::StoreExecutionTimeObservations(estimates)
565 }
566
567 fn input_objects(&self) -> Vec<InputObjectKind> {
568 match self {
569 Self::ChangeEpoch(_) => {
570 vec![InputObjectKind::SharedMoveObject {
571 id: SUI_SYSTEM_STATE_OBJECT_ID,
572 initial_shared_version: SUI_SYSTEM_STATE_OBJECT_SHARED_VERSION,
573 mutability: SharedObjectMutability::Mutable,
574 }]
575 }
576 Self::AuthenticatorStateCreate => vec![],
577 Self::AuthenticatorStateExpire(expire) => {
578 vec![InputObjectKind::SharedMoveObject {
579 id: SUI_AUTHENTICATOR_STATE_OBJECT_ID,
580 initial_shared_version: expire.authenticator_obj_initial_shared_version(),
581 mutability: SharedObjectMutability::Mutable,
582 }]
583 }
584 Self::RandomnessStateCreate => vec![],
585 Self::DenyListStateCreate => vec![],
586 Self::BridgeStateCreate(_) => vec![],
587 Self::BridgeCommitteeInit(bridge_version) => vec![
588 InputObjectKind::SharedMoveObject {
589 id: SUI_BRIDGE_OBJECT_ID,
590 initial_shared_version: *bridge_version,
591 mutability: SharedObjectMutability::Mutable,
592 },
593 InputObjectKind::SharedMoveObject {
594 id: SUI_SYSTEM_STATE_OBJECT_ID,
595 initial_shared_version: SUI_SYSTEM_STATE_OBJECT_SHARED_VERSION,
596 mutability: SharedObjectMutability::Mutable,
597 },
598 ],
599 Self::StoreExecutionTimeObservations(_) => {
600 vec![InputObjectKind::SharedMoveObject {
601 id: SUI_SYSTEM_STATE_OBJECT_ID,
602 initial_shared_version: SUI_SYSTEM_STATE_OBJECT_SHARED_VERSION,
603 mutability: SharedObjectMutability::Mutable,
604 }]
605 }
606 Self::AccumulatorRootCreate => vec![],
607 Self::CoinRegistryCreate => vec![],
608 Self::DisplayRegistryCreate => vec![],
609 }
610 }
611
612 fn shared_input_objects(&self) -> impl Iterator<Item = SharedInputObject> + '_ {
613 match self {
614 Self::ChangeEpoch(_) => {
615 Either::Left(vec![SharedInputObject::SUI_SYSTEM_OBJ].into_iter())
616 }
617 Self::AuthenticatorStateExpire(expire) => Either::Left(
618 vec![SharedInputObject {
619 id: SUI_AUTHENTICATOR_STATE_OBJECT_ID,
620 initial_shared_version: expire.authenticator_obj_initial_shared_version(),
621 mutability: SharedObjectMutability::Mutable,
622 }]
623 .into_iter(),
624 ),
625 Self::AuthenticatorStateCreate => Either::Right(iter::empty()),
626 Self::RandomnessStateCreate => Either::Right(iter::empty()),
627 Self::DenyListStateCreate => Either::Right(iter::empty()),
628 Self::BridgeStateCreate(_) => Either::Right(iter::empty()),
629 Self::BridgeCommitteeInit(bridge_version) => Either::Left(
630 vec![
631 SharedInputObject {
632 id: SUI_BRIDGE_OBJECT_ID,
633 initial_shared_version: *bridge_version,
634 mutability: SharedObjectMutability::Mutable,
635 },
636 SharedInputObject::SUI_SYSTEM_OBJ,
637 ]
638 .into_iter(),
639 ),
640 Self::StoreExecutionTimeObservations(_) => {
641 Either::Left(vec![SharedInputObject::SUI_SYSTEM_OBJ].into_iter())
642 }
643 Self::AccumulatorRootCreate => Either::Right(iter::empty()),
644 Self::CoinRegistryCreate => Either::Right(iter::empty()),
645 Self::DisplayRegistryCreate => Either::Right(iter::empty()),
646 }
647 }
648
649 fn validity_check(&self, config: &ProtocolConfig) -> UserInputResult {
650 match self {
651 Self::ChangeEpoch(_) => (),
652 Self::AuthenticatorStateCreate | Self::AuthenticatorStateExpire(_) => {
653 if !config.enable_jwk_consensus_updates() {
654 return Err(UserInputError::Unsupported(
655 "authenticator state updates not enabled".to_string(),
656 ));
657 }
658 }
659 Self::RandomnessStateCreate => {
660 if !config.random_beacon() {
661 return Err(UserInputError::Unsupported(
662 "random beacon not enabled".to_string(),
663 ));
664 }
665 }
666 Self::DenyListStateCreate => {
667 if !config.enable_coin_deny_list_v1() {
668 return Err(UserInputError::Unsupported(
669 "coin deny list not enabled".to_string(),
670 ));
671 }
672 }
673 Self::BridgeStateCreate(_) => {
674 if !config.enable_bridge() {
675 return Err(UserInputError::Unsupported(
676 "bridge not enabled".to_string(),
677 ));
678 }
679 }
680 Self::BridgeCommitteeInit(_) => {
681 if !config.enable_bridge() {
682 return Err(UserInputError::Unsupported(
683 "bridge not enabled".to_string(),
684 ));
685 }
686 if !config.should_try_to_finalize_bridge_committee() {
687 return Err(UserInputError::Unsupported(
688 "should not try to finalize committee yet".to_string(),
689 ));
690 }
691 }
692 Self::StoreExecutionTimeObservations(_) => {
693 if !matches!(
694 config.per_object_congestion_control_mode(),
695 PerObjectCongestionControlMode::ExecutionTimeEstimate(_)
696 ) {
697 return Err(UserInputError::Unsupported(
698 "execution time estimation not enabled".to_string(),
699 ));
700 }
701 }
702 Self::AccumulatorRootCreate => {
703 if !config.create_root_accumulator_object() {
704 return Err(UserInputError::Unsupported(
705 "accumulators not enabled".to_string(),
706 ));
707 }
708 }
709 Self::CoinRegistryCreate => {
710 if !config.enable_coin_registry() {
711 return Err(UserInputError::Unsupported(
712 "coin registry not enabled".to_string(),
713 ));
714 }
715 }
716 Self::DisplayRegistryCreate => {
717 if !config.enable_display_registry() {
718 return Err(UserInputError::Unsupported(
719 "display registry not enabled".to_string(),
720 ));
721 }
722 }
723 }
724 Ok(())
725 }
726}
727
728impl CallArg {
729 fn input_objects(&self) -> Vec<InputObjectKind> {
730 match self {
731 CallArg::Pure(_) => vec![],
732 CallArg::Object(ObjectArg::ImmOrOwnedObject(object_ref)) => {
733 vec![InputObjectKind::ImmOrOwnedMoveObject(*object_ref)]
734 }
735 CallArg::Object(ObjectArg::SharedObject {
736 id,
737 initial_shared_version,
738 mutability,
739 }) => vec![InputObjectKind::SharedMoveObject {
740 id: *id,
741 initial_shared_version: *initial_shared_version,
742 mutability: *mutability,
743 }],
744 CallArg::Object(ObjectArg::Receiving(_)) => vec![],
746 CallArg::FundsWithdrawal(_) => vec![],
750 }
751 }
752
753 fn receiving_objects(&self) -> Vec<ObjectRef> {
754 match self {
755 CallArg::Pure(_) => vec![],
756 CallArg::Object(o) => match o {
757 ObjectArg::ImmOrOwnedObject(_) => vec![],
758 ObjectArg::SharedObject { .. } => vec![],
759 ObjectArg::Receiving(obj_ref) => vec![*obj_ref],
760 },
761 CallArg::FundsWithdrawal(_) => vec![],
762 }
763 }
764
765 pub fn validity_check(&self, config: &ProtocolConfig) -> UserInputResult {
766 match self {
767 CallArg::Pure(p) => {
768 fp_ensure!(
769 p.len() < config.max_pure_argument_size() as usize,
770 UserInputError::SizeLimitExceeded {
771 limit: "maximum pure argument size".to_string(),
772 value: config.max_pure_argument_size().to_string()
773 }
774 );
775 }
776 CallArg::Object(o) => match o {
777 ObjectArg::ImmOrOwnedObject(_) => (),
778 ObjectArg::SharedObject { mutability, .. } => match mutability {
779 SharedObjectMutability::Mutable | SharedObjectMutability::Immutable => (),
780 SharedObjectMutability::NonExclusiveWrite => {
781 if !config.enable_non_exclusive_writes() {
782 return Err(UserInputError::Unsupported(
783 "User transactions cannot use SharedObjectMutability::NonExclusiveWrite".to_string(),
784 ));
785 }
786 }
787 },
788
789 ObjectArg::Receiving(_) => {
790 if !config.receiving_objects_supported() {
791 return Err(UserInputError::Unsupported(format!(
792 "receiving objects is not supported at {:?}",
793 config.version
794 )));
795 }
796 }
797 },
798 CallArg::FundsWithdrawal(_) => {}
799 }
800 Ok(())
801 }
802}
803
804impl From<bool> for CallArg {
805 fn from(b: bool) -> Self {
806 CallArg::Pure(bcs::to_bytes(&b).unwrap())
808 }
809}
810
811impl From<u8> for CallArg {
812 fn from(n: u8) -> Self {
813 CallArg::Pure(bcs::to_bytes(&n).unwrap())
815 }
816}
817
818impl From<u16> for CallArg {
819 fn from(n: u16) -> Self {
820 CallArg::Pure(bcs::to_bytes(&n).unwrap())
822 }
823}
824
825impl From<u32> for CallArg {
826 fn from(n: u32) -> Self {
827 CallArg::Pure(bcs::to_bytes(&n).unwrap())
829 }
830}
831
832impl From<u64> for CallArg {
833 fn from(n: u64) -> Self {
834 CallArg::Pure(bcs::to_bytes(&n).unwrap())
836 }
837}
838
839impl From<u128> for CallArg {
840 fn from(n: u128) -> Self {
841 CallArg::Pure(bcs::to_bytes(&n).unwrap())
843 }
844}
845
846impl From<&Vec<u8>> for CallArg {
847 fn from(v: &Vec<u8>) -> Self {
848 CallArg::Pure(bcs::to_bytes(v).unwrap())
850 }
851}
852
853impl From<ObjectRef> for CallArg {
854 fn from(obj: ObjectRef) -> Self {
855 CallArg::Object(ObjectArg::ImmOrOwnedObject(obj))
856 }
857}
858
859impl ObjectArg {
860 pub const SUI_SYSTEM_MUT: Self = Self::SharedObject {
861 id: SUI_SYSTEM_STATE_OBJECT_ID,
862 initial_shared_version: SUI_SYSTEM_STATE_OBJECT_SHARED_VERSION,
863 mutability: SharedObjectMutability::Mutable,
864 };
865
866 pub fn id(&self) -> ObjectID {
867 match self {
868 ObjectArg::Receiving((id, _, _))
869 | ObjectArg::ImmOrOwnedObject((id, _, _))
870 | ObjectArg::SharedObject { id, .. } => *id,
871 }
872 }
873}
874
875fn add_type_input_packages(packages: &mut BTreeSet<ObjectID>, type_argument: &TypeInput) {
877 let mut stack = vec![type_argument];
878 while let Some(cur) = stack.pop() {
879 match cur {
880 TypeInput::Bool
881 | TypeInput::U8
882 | TypeInput::U64
883 | TypeInput::U128
884 | TypeInput::Address
885 | TypeInput::Signer
886 | TypeInput::U16
887 | TypeInput::U32
888 | TypeInput::U256 => (),
889 TypeInput::Vector(inner) => stack.push(inner),
890 TypeInput::Struct(struct_tag) => {
891 packages.insert(struct_tag.address.into());
892 stack.extend(struct_tag.type_params.iter())
893 }
894 }
895 }
896}
897
898#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)]
901pub struct ProgrammableTransaction {
902 pub inputs: Vec<CallArg>,
904 pub commands: Vec<Command>,
907}
908
909impl ProgrammableTransaction {
910 pub fn has_shared_inputs(&self) -> bool {
911 self.inputs
912 .iter()
913 .any(|input| matches!(input, CallArg::Object(ObjectArg::SharedObject { .. })))
914 }
915}
916
917#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)]
919pub enum Command {
920 MoveCall(Box<ProgrammableMoveCall>),
922 TransferObjects(Vec<Argument>, Argument),
927 SplitCoins(Argument, Vec<Argument>),
930 MergeCoins(Argument, Vec<Argument>),
933 Publish(Vec<Vec<u8>>, Vec<ObjectID>),
936 MakeMoveVec(Option<TypeInput>, Vec<Argument>),
940 Upgrade(Vec<Vec<u8>>, Vec<ObjectID>, ObjectID, Argument),
948}
949
950#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy, Serialize, Deserialize)]
952pub enum Argument {
953 GasCoin,
956 Input(u16),
959 Result(u16),
961 NestedResult(u16, u16),
964}
965
966#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)]
969pub struct ProgrammableMoveCall {
970 pub package: ObjectID,
972 pub module: String,
974 pub function: String,
976 pub type_arguments: Vec<TypeInput>,
978 pub arguments: Vec<Argument>,
980}
981
982impl ProgrammableMoveCall {
983 fn input_objects(&self) -> Vec<InputObjectKind> {
984 let ProgrammableMoveCall {
985 package,
986 type_arguments,
987 ..
988 } = self;
989 let mut packages = BTreeSet::from([*package]);
990 for type_argument in type_arguments {
991 add_type_input_packages(&mut packages, type_argument)
992 }
993 packages
994 .into_iter()
995 .map(InputObjectKind::MovePackage)
996 .collect()
997 }
998
999 pub fn validity_check(&self, config: &ProtocolConfig) -> UserInputResult {
1000 let is_blocked = BLOCKED_MOVE_FUNCTIONS.contains(&(
1001 self.package,
1002 self.module.as_str(),
1003 self.function.as_str(),
1004 ));
1005 fp_ensure!(!is_blocked, UserInputError::BlockedMoveFunction);
1006 let mut type_arguments_count = 0;
1007 for tag in &self.type_arguments {
1008 type_input_validity_check(tag, config, &mut type_arguments_count)?;
1009 }
1010 fp_ensure!(
1011 self.arguments.len() < config.max_arguments() as usize,
1012 UserInputError::SizeLimitExceeded {
1013 limit: "maximum arguments in a move call".to_string(),
1014 value: config.max_arguments().to_string()
1015 }
1016 );
1017 if config.validate_identifier_inputs() {
1018 fp_ensure!(
1019 identifier::is_valid(&self.module),
1020 UserInputError::InvalidIdentifier {
1021 error: self.module.clone()
1022 }
1023 );
1024 fp_ensure!(
1025 identifier::is_valid(&self.function),
1026 UserInputError::InvalidIdentifier {
1027 error: self.module.clone()
1028 }
1029 );
1030 }
1031 Ok(())
1032 }
1033
1034 fn is_input_arg_used(&self, arg: u16) -> bool {
1035 self.arguments
1036 .iter()
1037 .any(|a| matches!(a, Argument::Input(inp) if *inp == arg))
1038 }
1039}
1040
1041impl Command {
1042 pub fn move_call(
1043 package: ObjectID,
1044 module: Identifier,
1045 function: Identifier,
1046 type_arguments: Vec<TypeTag>,
1047 arguments: Vec<Argument>,
1048 ) -> Self {
1049 let module = module.to_string();
1050 let function = function.to_string();
1051 let type_arguments = type_arguments.into_iter().map(TypeInput::from).collect();
1052 Command::MoveCall(Box::new(ProgrammableMoveCall {
1053 package,
1054 module,
1055 function,
1056 type_arguments,
1057 arguments,
1058 }))
1059 }
1060
1061 pub fn make_move_vec(ty: Option<TypeTag>, args: Vec<Argument>) -> Self {
1062 Command::MakeMoveVec(ty.map(TypeInput::from), args)
1063 }
1064
1065 fn input_objects(&self) -> Vec<InputObjectKind> {
1066 match self {
1067 Command::Upgrade(_, deps, package_id, _) => deps
1068 .iter()
1069 .map(|id| InputObjectKind::MovePackage(*id))
1070 .chain(Some(InputObjectKind::MovePackage(*package_id)))
1071 .collect(),
1072 Command::Publish(_, deps) => deps
1073 .iter()
1074 .map(|id| InputObjectKind::MovePackage(*id))
1075 .collect(),
1076 Command::MoveCall(c) => c.input_objects(),
1077 Command::MakeMoveVec(Some(t), _) => {
1078 let mut packages = BTreeSet::new();
1079 add_type_input_packages(&mut packages, t);
1080 packages
1081 .into_iter()
1082 .map(InputObjectKind::MovePackage)
1083 .collect()
1084 }
1085 Command::MakeMoveVec(None, _)
1086 | Command::TransferObjects(_, _)
1087 | Command::SplitCoins(_, _)
1088 | Command::MergeCoins(_, _) => vec![],
1089 }
1090 }
1091
1092 fn non_system_packages_to_be_published(&self) -> Option<&Vec<Vec<u8>>> {
1093 match self {
1094 Command::Upgrade(v, _, _, _) => Some(v),
1095 Command::Publish(v, _) => Some(v),
1096 Command::MoveCall(_)
1097 | Command::TransferObjects(_, _)
1098 | Command::SplitCoins(_, _)
1099 | Command::MergeCoins(_, _)
1100 | Command::MakeMoveVec(_, _) => None,
1101 }
1102 }
1103
1104 fn validity_check(&self, config: &ProtocolConfig) -> UserInputResult {
1105 match self {
1106 Command::MoveCall(call) => call.validity_check(config)?,
1107 Command::TransferObjects(args, _)
1108 | Command::MergeCoins(_, args)
1109 | Command::SplitCoins(_, args) => {
1110 fp_ensure!(!args.is_empty(), UserInputError::EmptyCommandInput);
1111 fp_ensure!(
1112 args.len() < config.max_arguments() as usize,
1113 UserInputError::SizeLimitExceeded {
1114 limit: "maximum arguments in a programmable transaction command"
1115 .to_string(),
1116 value: config.max_arguments().to_string()
1117 }
1118 );
1119 }
1120 Command::MakeMoveVec(ty_opt, args) => {
1121 fp_ensure!(
1123 ty_opt.is_some() || !args.is_empty(),
1124 UserInputError::EmptyCommandInput
1125 );
1126 if let Some(ty) = ty_opt {
1127 let mut type_arguments_count = 0;
1128 type_input_validity_check(ty, config, &mut type_arguments_count)?;
1129 }
1130 fp_ensure!(
1131 args.len() < config.max_arguments() as usize,
1132 UserInputError::SizeLimitExceeded {
1133 limit: "maximum arguments in a programmable transaction command"
1134 .to_string(),
1135 value: config.max_arguments().to_string()
1136 }
1137 );
1138 }
1139 Command::Publish(modules, deps) | Command::Upgrade(modules, deps, _, _) => {
1140 fp_ensure!(!modules.is_empty(), UserInputError::EmptyCommandInput);
1141 fp_ensure!(
1142 modules.len() < config.max_modules_in_publish() as usize,
1143 UserInputError::SizeLimitExceeded {
1144 limit: "maximum modules in a programmable transaction upgrade command"
1145 .to_string(),
1146 value: config.max_modules_in_publish().to_string()
1147 }
1148 );
1149 if let Some(max_package_dependencies) = config.max_package_dependencies_as_option()
1150 {
1151 fp_ensure!(
1152 deps.len() < max_package_dependencies as usize,
1153 UserInputError::SizeLimitExceeded {
1154 limit: "maximum package dependencies".to_string(),
1155 value: max_package_dependencies.to_string()
1156 }
1157 );
1158 };
1159 }
1160 };
1161 Ok(())
1162 }
1163
1164 fn is_input_arg_used(&self, input_arg: u16) -> bool {
1165 match self {
1166 Command::MoveCall(c) => c.is_input_arg_used(input_arg),
1167 Command::TransferObjects(args, arg)
1168 | Command::MergeCoins(arg, args)
1169 | Command::SplitCoins(arg, args) => args
1170 .iter()
1171 .chain(once(arg))
1172 .any(|a| matches!(a, Argument::Input(inp) if *inp == input_arg)),
1173 Command::MakeMoveVec(_, args) => args
1174 .iter()
1175 .any(|a| matches!(a, Argument::Input(inp) if *inp == input_arg)),
1176 Command::Upgrade(_, _, _, arg) => {
1177 matches!(arg, Argument::Input(inp) if *inp == input_arg)
1178 }
1179 Command::Publish(_, _) => false,
1180 }
1181 }
1182}
1183
1184pub fn write_sep<T: Display>(
1185 f: &mut Formatter<'_>,
1186 items: impl IntoIterator<Item = T>,
1187 sep: &str,
1188) -> std::fmt::Result {
1189 let mut xs = items.into_iter();
1190 let Some(x) = xs.next() else {
1191 return Ok(());
1192 };
1193 write!(f, "{x}")?;
1194 for x in xs {
1195 write!(f, "{sep}{x}")?;
1196 }
1197 Ok(())
1198}
1199
1200impl ProgrammableTransaction {
1201 pub fn input_objects(&self) -> UserInputResult<Vec<InputObjectKind>> {
1202 let ProgrammableTransaction { inputs, commands } = self;
1203 let input_arg_objects = inputs
1204 .iter()
1205 .flat_map(|arg| arg.input_objects())
1206 .collect::<Vec<_>>();
1207 let mut used = HashSet::new();
1209 if !input_arg_objects.iter().all(|o| used.insert(o.object_id())) {
1210 return Err(UserInputError::DuplicateObjectRefInput);
1211 }
1212 let command_input_objects: BTreeSet<InputObjectKind> = commands
1214 .iter()
1215 .flat_map(|command| command.input_objects())
1216 .collect();
1217 Ok(input_arg_objects
1218 .into_iter()
1219 .chain(command_input_objects)
1220 .collect())
1221 }
1222
1223 fn receiving_objects(&self) -> Vec<ObjectRef> {
1224 let ProgrammableTransaction { inputs, .. } = self;
1225 inputs
1226 .iter()
1227 .flat_map(|arg| arg.receiving_objects())
1228 .collect()
1229 }
1230
1231 fn validity_check(&self, config: &ProtocolConfig) -> UserInputResult {
1232 let ProgrammableTransaction { inputs, commands } = self;
1233 fp_ensure!(
1234 commands.len() < config.max_programmable_tx_commands() as usize,
1235 UserInputError::SizeLimitExceeded {
1236 limit: "maximum commands in a programmable transaction".to_string(),
1237 value: config.max_programmable_tx_commands().to_string()
1238 }
1239 );
1240 let total_inputs = self.input_objects()?.len() + self.receiving_objects().len();
1241 fp_ensure!(
1242 total_inputs <= config.max_input_objects() as usize,
1243 UserInputError::SizeLimitExceeded {
1244 limit: "maximum input + receiving objects in a transaction".to_string(),
1245 value: config.max_input_objects().to_string()
1246 }
1247 );
1248 for input in inputs {
1249 input.validity_check(config)?
1250 }
1251 if let Some(max_publish_commands) = config.max_publish_or_upgrade_per_ptb_as_option() {
1252 let publish_count = commands
1253 .iter()
1254 .filter(|c| matches!(c, Command::Publish(_, _) | Command::Upgrade(_, _, _, _)))
1255 .count() as u64;
1256 fp_ensure!(
1257 publish_count <= max_publish_commands,
1258 UserInputError::MaxPublishCountExceeded {
1259 max_publish_commands,
1260 publish_count,
1261 }
1262 );
1263 }
1264 for command in commands {
1265 command.validity_check(config)?;
1266 }
1267
1268 if let Some(random_index) = inputs.iter().position(|obj| {
1271 matches!(
1272 obj,
1273 CallArg::Object(ObjectArg::SharedObject { id, .. }) if *id == SUI_RANDOMNESS_STATE_OBJECT_ID
1274 )
1275 }) {
1276 fp_ensure!(
1277 config.random_beacon(),
1278 UserInputError::Unsupported(
1279 "randomness is not enabled on this network".to_string(),
1280 )
1281 );
1282 let mut used_random_object = false;
1283 let random_index = random_index.try_into().unwrap();
1284 for command in commands {
1285 if !used_random_object {
1286 used_random_object = command.is_input_arg_used(random_index);
1287 } else {
1288 fp_ensure!(
1289 matches!(
1290 command,
1291 Command::TransferObjects(_, _) | Command::MergeCoins(_, _)
1292 ),
1293 UserInputError::PostRandomCommandRestrictions
1294 );
1295 }
1296 }
1297 }
1298
1299 Ok(())
1300 }
1301
1302 pub fn shared_input_objects(&self) -> impl Iterator<Item = SharedInputObject> + '_ {
1303 self.inputs.iter().filter_map(|arg| match arg {
1304 CallArg::Pure(_)
1305 | CallArg::Object(ObjectArg::Receiving(_))
1306 | CallArg::Object(ObjectArg::ImmOrOwnedObject(_))
1307 | CallArg::FundsWithdrawal(_) => None,
1308 CallArg::Object(ObjectArg::SharedObject {
1309 id,
1310 initial_shared_version,
1311 mutability,
1312 }) => Some(SharedInputObject {
1313 id: *id,
1314 initial_shared_version: *initial_shared_version,
1315 mutability: *mutability,
1316 }),
1317 })
1318 }
1319
1320 fn move_calls(&self) -> Vec<(&ObjectID, &str, &str)> {
1321 self.commands
1322 .iter()
1323 .filter_map(|command| match command {
1324 Command::MoveCall(m) => Some((&m.package, m.module.as_str(), m.function.as_str())),
1325 _ => None,
1326 })
1327 .collect()
1328 }
1329
1330 pub fn non_system_packages_to_be_published(&self) -> impl Iterator<Item = &Vec<Vec<u8>>> + '_ {
1331 self.commands
1332 .iter()
1333 .filter_map(|q| q.non_system_packages_to_be_published())
1334 }
1335}
1336
1337impl Display for Argument {
1338 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
1339 match self {
1340 Argument::GasCoin => write!(f, "GasCoin"),
1341 Argument::Input(i) => write!(f, "Input({i})"),
1342 Argument::Result(i) => write!(f, "Result({i})"),
1343 Argument::NestedResult(i, j) => write!(f, "NestedResult({i},{j})"),
1344 }
1345 }
1346}
1347
1348impl Display for ProgrammableMoveCall {
1349 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
1350 let ProgrammableMoveCall {
1351 package,
1352 module,
1353 function,
1354 type_arguments,
1355 arguments,
1356 } = self;
1357 write!(f, "{package}::{module}::{function}")?;
1358 if !type_arguments.is_empty() {
1359 write!(f, "<")?;
1360 write_sep(f, type_arguments, ",")?;
1361 write!(f, ">")?;
1362 }
1363 write!(f, "(")?;
1364 write_sep(f, arguments, ",")?;
1365 write!(f, ")")
1366 }
1367}
1368
1369impl Display for Command {
1370 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
1371 match self {
1372 Command::MoveCall(p) => {
1373 write!(f, "MoveCall({p})")
1374 }
1375 Command::MakeMoveVec(ty_opt, elems) => {
1376 write!(f, "MakeMoveVec(")?;
1377 if let Some(ty) = ty_opt {
1378 write!(f, "Some{ty}")?;
1379 } else {
1380 write!(f, "None")?;
1381 }
1382 write!(f, ",[")?;
1383 write_sep(f, elems, ",")?;
1384 write!(f, "])")
1385 }
1386 Command::TransferObjects(objs, addr) => {
1387 write!(f, "TransferObjects([")?;
1388 write_sep(f, objs, ",")?;
1389 write!(f, "],{addr})")
1390 }
1391 Command::SplitCoins(coin, amounts) => {
1392 write!(f, "SplitCoins({coin}")?;
1393 write_sep(f, amounts, ",")?;
1394 write!(f, ")")
1395 }
1396 Command::MergeCoins(target, coins) => {
1397 write!(f, "MergeCoins({target},")?;
1398 write_sep(f, coins, ",")?;
1399 write!(f, ")")
1400 }
1401 Command::Publish(_bytes, deps) => {
1402 write!(f, "Publish(_,")?;
1403 write_sep(f, deps, ",")?;
1404 write!(f, ")")
1405 }
1406 Command::Upgrade(_bytes, deps, current_package_id, ticket) => {
1407 write!(f, "Upgrade(_,")?;
1408 write_sep(f, deps, ",")?;
1409 write!(f, ", {current_package_id}")?;
1410 write!(f, ", {ticket}")?;
1411 write!(f, ")")
1412 }
1413 }
1414 }
1415}
1416
1417impl Display for ProgrammableTransaction {
1418 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
1419 let ProgrammableTransaction { inputs, commands } = self;
1420 writeln!(f, "Inputs: {inputs:?}")?;
1421 writeln!(f, "Commands: [")?;
1422 for c in commands {
1423 writeln!(f, " {c},")?;
1424 }
1425 writeln!(f, "]")
1426 }
1427}
1428
1429#[derive(Debug, PartialEq, Eq)]
1430pub struct SharedInputObject {
1431 pub id: ObjectID,
1432 pub initial_shared_version: SequenceNumber,
1433 pub mutability: SharedObjectMutability,
1434}
1435
1436impl SharedInputObject {
1437 pub const SUI_SYSTEM_OBJ: Self = Self {
1438 id: SUI_SYSTEM_STATE_OBJECT_ID,
1439 initial_shared_version: SUI_SYSTEM_STATE_OBJECT_SHARED_VERSION,
1440 mutability: SharedObjectMutability::Mutable,
1441 };
1442
1443 pub fn id(&self) -> ObjectID {
1444 self.id
1445 }
1446
1447 pub fn id_and_version(&self) -> (ObjectID, SequenceNumber) {
1448 (self.id, self.initial_shared_version)
1449 }
1450
1451 pub fn into_id_and_version(self) -> (ObjectID, SequenceNumber) {
1452 (self.id, self.initial_shared_version)
1453 }
1454
1455 pub fn is_accessed_exclusively(&self) -> bool {
1456 self.mutability.is_exclusive()
1457 }
1458}
1459
1460impl TransactionKind {
1461 pub fn programmable(pt: ProgrammableTransaction) -> Self {
1464 TransactionKind::ProgrammableTransaction(pt)
1465 }
1466
1467 pub fn is_system_tx(&self) -> bool {
1468 match self {
1470 TransactionKind::ChangeEpoch(_)
1471 | TransactionKind::Genesis(_)
1472 | TransactionKind::ConsensusCommitPrologue(_)
1473 | TransactionKind::ConsensusCommitPrologueV2(_)
1474 | TransactionKind::ConsensusCommitPrologueV3(_)
1475 | TransactionKind::ConsensusCommitPrologueV4(_)
1476 | TransactionKind::AuthenticatorStateUpdate(_)
1477 | TransactionKind::RandomnessStateUpdate(_)
1478 | TransactionKind::EndOfEpochTransaction(_)
1479 | TransactionKind::ProgrammableSystemTransaction(_) => true,
1480 TransactionKind::ProgrammableTransaction(_) => false,
1481 }
1482 }
1483
1484 pub fn is_end_of_epoch_tx(&self) -> bool {
1485 matches!(
1486 self,
1487 TransactionKind::EndOfEpochTransaction(_) | TransactionKind::ChangeEpoch(_)
1488 )
1489 }
1490
1491 pub fn get_advance_epoch_tx_gas_summary(&self) -> Option<(u64, u64)> {
1495 let e = match self {
1496 Self::ChangeEpoch(e) => e,
1497 Self::EndOfEpochTransaction(txns) => {
1498 if let EndOfEpochTransactionKind::ChangeEpoch(e) =
1499 txns.last().expect("at least one end-of-epoch txn required")
1500 {
1501 e
1502 } else {
1503 panic!("final end-of-epoch txn must be ChangeEpoch")
1504 }
1505 }
1506 _ => return None,
1507 };
1508
1509 Some((e.computation_charge + e.storage_charge, e.storage_rebate))
1510 }
1511
1512 pub fn shared_input_objects(&self) -> impl Iterator<Item = SharedInputObject> + '_ {
1515 match &self {
1516 Self::ChangeEpoch(_) => {
1517 Either::Left(Either::Left(iter::once(SharedInputObject::SUI_SYSTEM_OBJ)))
1518 }
1519
1520 Self::ConsensusCommitPrologue(_)
1521 | Self::ConsensusCommitPrologueV2(_)
1522 | Self::ConsensusCommitPrologueV3(_)
1523 | Self::ConsensusCommitPrologueV4(_) => {
1524 Either::Left(Either::Left(iter::once(SharedInputObject {
1525 id: SUI_CLOCK_OBJECT_ID,
1526 initial_shared_version: SUI_CLOCK_OBJECT_SHARED_VERSION,
1527 mutability: SharedObjectMutability::Mutable,
1528 })))
1529 }
1530 Self::AuthenticatorStateUpdate(update) => {
1531 Either::Left(Either::Left(iter::once(SharedInputObject {
1532 id: SUI_AUTHENTICATOR_STATE_OBJECT_ID,
1533 initial_shared_version: update.authenticator_obj_initial_shared_version,
1534 mutability: SharedObjectMutability::Mutable,
1535 })))
1536 }
1537 Self::RandomnessStateUpdate(update) => {
1538 Either::Left(Either::Left(iter::once(SharedInputObject {
1539 id: SUI_RANDOMNESS_STATE_OBJECT_ID,
1540 initial_shared_version: update.randomness_obj_initial_shared_version,
1541 mutability: SharedObjectMutability::Mutable,
1542 })))
1543 }
1544 Self::EndOfEpochTransaction(txns) => Either::Left(Either::Right(
1545 txns.iter().flat_map(|txn| txn.shared_input_objects()),
1546 )),
1547 Self::ProgrammableTransaction(pt) | Self::ProgrammableSystemTransaction(pt) => {
1548 Either::Right(Either::Left(pt.shared_input_objects()))
1549 }
1550 Self::Genesis(_) => Either::Right(Either::Right(iter::empty())),
1551 }
1552 }
1553
1554 fn move_calls(&self) -> Vec<(&ObjectID, &str, &str)> {
1555 match &self {
1556 Self::ProgrammableTransaction(pt) => pt.move_calls(),
1557 _ => vec![],
1558 }
1559 }
1560
1561 pub fn receiving_objects(&self) -> Vec<ObjectRef> {
1562 match &self {
1563 TransactionKind::ChangeEpoch(_)
1564 | TransactionKind::Genesis(_)
1565 | TransactionKind::ConsensusCommitPrologue(_)
1566 | TransactionKind::ConsensusCommitPrologueV2(_)
1567 | TransactionKind::ConsensusCommitPrologueV3(_)
1568 | TransactionKind::ConsensusCommitPrologueV4(_)
1569 | TransactionKind::AuthenticatorStateUpdate(_)
1570 | TransactionKind::RandomnessStateUpdate(_)
1571 | TransactionKind::EndOfEpochTransaction(_)
1572 | TransactionKind::ProgrammableSystemTransaction(_) => vec![],
1573 TransactionKind::ProgrammableTransaction(pt) => pt.receiving_objects(),
1574 }
1575 }
1576
1577 pub fn input_objects(&self) -> UserInputResult<Vec<InputObjectKind>> {
1582 let input_objects = match &self {
1583 Self::ChangeEpoch(_) => {
1584 vec![InputObjectKind::SharedMoveObject {
1585 id: SUI_SYSTEM_STATE_OBJECT_ID,
1586 initial_shared_version: SUI_SYSTEM_STATE_OBJECT_SHARED_VERSION,
1587 mutability: SharedObjectMutability::Mutable,
1588 }]
1589 }
1590 Self::Genesis(_) => {
1591 vec![]
1592 }
1593 Self::ConsensusCommitPrologue(_)
1594 | Self::ConsensusCommitPrologueV2(_)
1595 | Self::ConsensusCommitPrologueV3(_)
1596 | Self::ConsensusCommitPrologueV4(_) => {
1597 vec![InputObjectKind::SharedMoveObject {
1598 id: SUI_CLOCK_OBJECT_ID,
1599 initial_shared_version: SUI_CLOCK_OBJECT_SHARED_VERSION,
1600 mutability: SharedObjectMutability::Mutable,
1601 }]
1602 }
1603 Self::AuthenticatorStateUpdate(update) => {
1604 vec![InputObjectKind::SharedMoveObject {
1605 id: SUI_AUTHENTICATOR_STATE_OBJECT_ID,
1606 initial_shared_version: update.authenticator_obj_initial_shared_version(),
1607 mutability: SharedObjectMutability::Mutable,
1608 }]
1609 }
1610 Self::RandomnessStateUpdate(update) => {
1611 vec![InputObjectKind::SharedMoveObject {
1612 id: SUI_RANDOMNESS_STATE_OBJECT_ID,
1613 initial_shared_version: update.randomness_obj_initial_shared_version(),
1614 mutability: SharedObjectMutability::Mutable,
1615 }]
1616 }
1617 Self::EndOfEpochTransaction(txns) => {
1618 let before_dedup: Vec<_> =
1621 txns.iter().flat_map(|txn| txn.input_objects()).collect();
1622 let mut has_seen = HashSet::new();
1623 let mut after_dedup = vec![];
1624 for obj in before_dedup {
1625 if has_seen.insert(obj) {
1626 after_dedup.push(obj);
1627 }
1628 }
1629 after_dedup
1630 }
1631 Self::ProgrammableTransaction(p) | Self::ProgrammableSystemTransaction(p) => {
1632 return p.input_objects();
1633 }
1634 };
1635 let mut used = HashSet::new();
1643 if !input_objects.iter().all(|o| used.insert(o.object_id())) {
1644 return Err(UserInputError::DuplicateObjectRefInput);
1645 }
1646 Ok(input_objects)
1647 }
1648
1649 fn get_funds_withdrawals<'a>(&'a self) -> impl Iterator<Item = &'a FundsWithdrawalArg> + 'a {
1650 let TransactionKind::ProgrammableTransaction(pt) = &self else {
1651 return Either::Left(iter::empty());
1652 };
1653 Either::Right(pt.inputs.iter().filter_map(|input| {
1654 if let CallArg::FundsWithdrawal(withdraw) = input {
1655 Some(withdraw)
1656 } else {
1657 None
1658 }
1659 }))
1660 }
1661
1662 pub fn validity_check(&self, config: &ProtocolConfig) -> UserInputResult {
1663 match self {
1664 TransactionKind::ProgrammableTransaction(p) => p.validity_check(config)?,
1665 TransactionKind::ChangeEpoch(_)
1668 | TransactionKind::Genesis(_)
1669 | TransactionKind::ConsensusCommitPrologue(_) => (),
1670 TransactionKind::ConsensusCommitPrologueV2(_) => {
1671 if !config.include_consensus_digest_in_prologue() {
1672 return Err(UserInputError::Unsupported(
1673 "ConsensusCommitPrologueV2 is not supported".to_string(),
1674 ));
1675 }
1676 }
1677 TransactionKind::ConsensusCommitPrologueV3(_) => {
1678 if !config.record_consensus_determined_version_assignments_in_prologue() {
1679 return Err(UserInputError::Unsupported(
1680 "ConsensusCommitPrologueV3 is not supported".to_string(),
1681 ));
1682 }
1683 }
1684 TransactionKind::ConsensusCommitPrologueV4(_) => {
1685 if !config.record_additional_state_digest_in_prologue() {
1686 return Err(UserInputError::Unsupported(
1687 "ConsensusCommitPrologueV4 is not supported".to_string(),
1688 ));
1689 }
1690 }
1691 TransactionKind::EndOfEpochTransaction(txns) => {
1692 if !config.end_of_epoch_transaction_supported() {
1693 return Err(UserInputError::Unsupported(
1694 "EndOfEpochTransaction is not supported".to_string(),
1695 ));
1696 }
1697
1698 for tx in txns {
1699 tx.validity_check(config)?;
1700 }
1701 }
1702
1703 TransactionKind::AuthenticatorStateUpdate(_) => {
1704 if !config.enable_jwk_consensus_updates() {
1705 return Err(UserInputError::Unsupported(
1706 "authenticator state updates not enabled".to_string(),
1707 ));
1708 }
1709 }
1710 TransactionKind::RandomnessStateUpdate(_) => {
1711 if !config.random_beacon() {
1712 return Err(UserInputError::Unsupported(
1713 "randomness state updates not enabled".to_string(),
1714 ));
1715 }
1716 }
1717 TransactionKind::ProgrammableSystemTransaction(_) => {
1718 if !config.enable_accumulators() {
1719 return Err(UserInputError::Unsupported(
1720 "accumulators not enabled".to_string(),
1721 ));
1722 }
1723 }
1724 };
1725 Ok(())
1726 }
1727
1728 pub fn num_commands(&self) -> usize {
1730 match self {
1731 TransactionKind::ProgrammableTransaction(pt) => pt.commands.len(),
1732 _ => 0,
1733 }
1734 }
1735
1736 pub fn iter_commands(&self) -> impl Iterator<Item = &Command> {
1737 match self {
1738 TransactionKind::ProgrammableTransaction(pt) => pt.commands.iter(),
1739 _ => [].iter(),
1740 }
1741 }
1742
1743 pub fn tx_count(&self) -> usize {
1745 match self {
1746 TransactionKind::ProgrammableTransaction(pt) => pt.commands.len(),
1747 _ => 1,
1748 }
1749 }
1750
1751 pub fn name(&self) -> &'static str {
1752 match self {
1753 Self::ChangeEpoch(_) => "ChangeEpoch",
1754 Self::Genesis(_) => "Genesis",
1755 Self::ConsensusCommitPrologue(_) => "ConsensusCommitPrologue",
1756 Self::ConsensusCommitPrologueV2(_) => "ConsensusCommitPrologueV2",
1757 Self::ConsensusCommitPrologueV3(_) => "ConsensusCommitPrologueV3",
1758 Self::ConsensusCommitPrologueV4(_) => "ConsensusCommitPrologueV4",
1759 Self::ProgrammableTransaction(_) => "ProgrammableTransaction",
1760 Self::ProgrammableSystemTransaction(_) => "ProgrammableSystemTransaction",
1761 Self::AuthenticatorStateUpdate(_) => "AuthenticatorStateUpdate",
1762 Self::RandomnessStateUpdate(_) => "RandomnessStateUpdate",
1763 Self::EndOfEpochTransaction(_) => "EndOfEpochTransaction",
1764 }
1765 }
1766}
1767
1768impl Display for TransactionKind {
1769 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
1770 let mut writer = String::new();
1771 match &self {
1772 Self::ChangeEpoch(e) => {
1773 writeln!(writer, "Transaction Kind : Epoch Change")?;
1774 writeln!(writer, "New epoch ID : {}", e.epoch)?;
1775 writeln!(writer, "Storage gas reward : {}", e.storage_charge)?;
1776 writeln!(writer, "Computation gas reward : {}", e.computation_charge)?;
1777 writeln!(writer, "Storage rebate : {}", e.storage_rebate)?;
1778 writeln!(writer, "Timestamp : {}", e.epoch_start_timestamp_ms)?;
1779 }
1780 Self::Genesis(_) => {
1781 writeln!(writer, "Transaction Kind : Genesis")?;
1782 }
1783 Self::ConsensusCommitPrologue(p) => {
1784 writeln!(writer, "Transaction Kind : Consensus Commit Prologue")?;
1785 writeln!(writer, "Timestamp : {}", p.commit_timestamp_ms)?;
1786 }
1787 Self::ConsensusCommitPrologueV2(p) => {
1788 writeln!(writer, "Transaction Kind : Consensus Commit Prologue V2")?;
1789 writeln!(writer, "Timestamp : {}", p.commit_timestamp_ms)?;
1790 writeln!(writer, "Consensus Digest: {}", p.consensus_commit_digest)?;
1791 }
1792 Self::ConsensusCommitPrologueV3(p) => {
1793 writeln!(writer, "Transaction Kind : Consensus Commit Prologue V3")?;
1794 writeln!(writer, "Timestamp : {}", p.commit_timestamp_ms)?;
1795 writeln!(writer, "Consensus Digest: {}", p.consensus_commit_digest)?;
1796 writeln!(
1797 writer,
1798 "Consensus determined version assignment: {:?}",
1799 p.consensus_determined_version_assignments
1800 )?;
1801 }
1802 Self::ConsensusCommitPrologueV4(p) => {
1803 writeln!(writer, "Transaction Kind : Consensus Commit Prologue V4")?;
1804 writeln!(writer, "Timestamp : {}", p.commit_timestamp_ms)?;
1805 writeln!(writer, "Consensus Digest: {}", p.consensus_commit_digest)?;
1806 writeln!(
1807 writer,
1808 "Consensus determined version assignment: {:?}",
1809 p.consensus_determined_version_assignments
1810 )?;
1811 writeln!(
1812 writer,
1813 "Additional State Digest: {}",
1814 p.additional_state_digest
1815 )?;
1816 }
1817 Self::ProgrammableTransaction(p) => {
1818 writeln!(writer, "Transaction Kind : Programmable")?;
1819 write!(writer, "{p}")?;
1820 }
1821 Self::ProgrammableSystemTransaction(p) => {
1822 writeln!(writer, "Transaction Kind : Programmable System")?;
1823 write!(writer, "{p}")?;
1824 }
1825 Self::AuthenticatorStateUpdate(_) => {
1826 writeln!(writer, "Transaction Kind : Authenticator State Update")?;
1827 }
1828 Self::RandomnessStateUpdate(_) => {
1829 writeln!(writer, "Transaction Kind : Randomness State Update")?;
1830 }
1831 Self::EndOfEpochTransaction(_) => {
1832 writeln!(writer, "Transaction Kind : End of Epoch Transaction")?;
1833 }
1834 }
1835 write!(f, "{}", writer)
1836 }
1837}
1838
1839#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)]
1840pub struct GasData {
1841 pub payment: Vec<ObjectRef>,
1842 pub owner: SuiAddress,
1843 pub price: u64,
1844 pub budget: u64,
1845}
1846
1847pub fn is_gas_paid_from_address_balance(
1848 gas_data: &GasData,
1849 transaction_kind: &TransactionKind,
1850) -> bool {
1851 gas_data.payment.is_empty()
1852 && matches!(
1853 transaction_kind,
1854 TransactionKind::ProgrammableTransaction(_)
1855 )
1856}
1857
1858#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy, Serialize, Deserialize)]
1859pub enum TransactionExpiration {
1860 None,
1862 Epoch(EpochId),
1865 ValidDuring {
1875 min_epoch: Option<EpochId>,
1877 max_epoch: Option<EpochId>,
1879 min_timestamp_seconds: Option<u64>,
1881 max_timestamp_seconds: Option<u64>,
1883 chain: ChainIdentifier,
1885 nonce: u32,
1887 },
1888}
1889
1890#[enum_dispatch(TransactionDataAPI)]
1891#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)]
1892pub enum TransactionData {
1893 V1(TransactionDataV1),
1894 }
1897
1898#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)]
1899pub struct TransactionDataV1 {
1900 pub kind: TransactionKind,
1901 pub sender: SuiAddress,
1902 pub gas_data: GasData,
1903 pub expiration: TransactionExpiration,
1904}
1905
1906impl TransactionData {
1907 fn new_system_transaction(kind: TransactionKind) -> Self {
1908 assert!(kind.is_system_tx());
1910 let sender = SuiAddress::default();
1911 TransactionData::V1(TransactionDataV1 {
1912 kind,
1913 sender,
1914 gas_data: GasData {
1915 price: GAS_PRICE_FOR_SYSTEM_TX,
1916 owner: sender,
1917 payment: vec![(ObjectID::ZERO, SequenceNumber::default(), ObjectDigest::MIN)],
1918 budget: 0,
1919 },
1920 expiration: TransactionExpiration::None,
1921 })
1922 }
1923
1924 pub fn new(
1925 kind: TransactionKind,
1926 sender: SuiAddress,
1927 gas_payment: ObjectRef,
1928 gas_budget: u64,
1929 gas_price: u64,
1930 ) -> Self {
1931 TransactionData::V1(TransactionDataV1 {
1932 kind,
1933 sender,
1934 gas_data: GasData {
1935 price: gas_price,
1936 owner: sender,
1937 payment: vec![gas_payment],
1938 budget: gas_budget,
1939 },
1940 expiration: TransactionExpiration::None,
1941 })
1942 }
1943
1944 pub fn new_with_gas_coins(
1945 kind: TransactionKind,
1946 sender: SuiAddress,
1947 gas_payment: Vec<ObjectRef>,
1948 gas_budget: u64,
1949 gas_price: u64,
1950 ) -> Self {
1951 Self::new_with_gas_coins_allow_sponsor(
1952 kind,
1953 sender,
1954 gas_payment,
1955 gas_budget,
1956 gas_price,
1957 sender,
1958 )
1959 }
1960
1961 pub fn new_with_gas_coins_allow_sponsor(
1962 kind: TransactionKind,
1963 sender: SuiAddress,
1964 gas_payment: Vec<ObjectRef>,
1965 gas_budget: u64,
1966 gas_price: u64,
1967 gas_sponsor: SuiAddress,
1968 ) -> Self {
1969 TransactionData::V1(TransactionDataV1 {
1970 kind,
1971 sender,
1972 gas_data: GasData {
1973 price: gas_price,
1974 owner: gas_sponsor,
1975 payment: gas_payment,
1976 budget: gas_budget,
1977 },
1978 expiration: TransactionExpiration::None,
1979 })
1980 }
1981
1982 pub fn new_with_gas_data(kind: TransactionKind, sender: SuiAddress, gas_data: GasData) -> Self {
1983 TransactionData::V1(TransactionDataV1 {
1984 kind,
1985 sender,
1986 gas_data,
1987 expiration: TransactionExpiration::None,
1988 })
1989 }
1990
1991 pub fn new_move_call(
1992 sender: SuiAddress,
1993 package: ObjectID,
1994 module: Identifier,
1995 function: Identifier,
1996 type_arguments: Vec<TypeTag>,
1997 gas_payment: ObjectRef,
1998 arguments: Vec<CallArg>,
1999 gas_budget: u64,
2000 gas_price: u64,
2001 ) -> anyhow::Result<Self> {
2002 Self::new_move_call_with_gas_coins(
2003 sender,
2004 package,
2005 module,
2006 function,
2007 type_arguments,
2008 vec![gas_payment],
2009 arguments,
2010 gas_budget,
2011 gas_price,
2012 )
2013 }
2014
2015 pub fn new_move_call_with_gas_coins(
2016 sender: SuiAddress,
2017 package: ObjectID,
2018 module: Identifier,
2019 function: Identifier,
2020 type_arguments: Vec<TypeTag>,
2021 gas_payment: Vec<ObjectRef>,
2022 arguments: Vec<CallArg>,
2023 gas_budget: u64,
2024 gas_price: u64,
2025 ) -> anyhow::Result<Self> {
2026 let pt = {
2027 let mut builder = ProgrammableTransactionBuilder::new();
2028 builder.move_call(package, module, function, type_arguments, arguments)?;
2029 builder.finish()
2030 };
2031 Ok(Self::new_programmable(
2032 sender,
2033 gas_payment,
2034 pt,
2035 gas_budget,
2036 gas_price,
2037 ))
2038 }
2039
2040 pub fn new_transfer(
2041 recipient: SuiAddress,
2042 full_object_ref: FullObjectRef,
2043 sender: SuiAddress,
2044 gas_payment: ObjectRef,
2045 gas_budget: u64,
2046 gas_price: u64,
2047 ) -> Self {
2048 let pt = {
2049 let mut builder = ProgrammableTransactionBuilder::new();
2050 builder.transfer_object(recipient, full_object_ref).unwrap();
2051 builder.finish()
2052 };
2053 Self::new_programmable(sender, vec![gas_payment], pt, gas_budget, gas_price)
2054 }
2055
2056 pub fn new_transfer_sui(
2057 recipient: SuiAddress,
2058 sender: SuiAddress,
2059 amount: Option<u64>,
2060 gas_payment: ObjectRef,
2061 gas_budget: u64,
2062 gas_price: u64,
2063 ) -> Self {
2064 Self::new_transfer_sui_allow_sponsor(
2065 recipient,
2066 sender,
2067 amount,
2068 gas_payment,
2069 gas_budget,
2070 gas_price,
2071 sender,
2072 )
2073 }
2074
2075 pub fn new_transfer_sui_allow_sponsor(
2076 recipient: SuiAddress,
2077 sender: SuiAddress,
2078 amount: Option<u64>,
2079 gas_payment: ObjectRef,
2080 gas_budget: u64,
2081 gas_price: u64,
2082 gas_sponsor: SuiAddress,
2083 ) -> Self {
2084 let pt = {
2085 let mut builder = ProgrammableTransactionBuilder::new();
2086 builder.transfer_sui(recipient, amount);
2087 builder.finish()
2088 };
2089 Self::new_programmable_allow_sponsor(
2090 sender,
2091 vec![gas_payment],
2092 pt,
2093 gas_budget,
2094 gas_price,
2095 gas_sponsor,
2096 )
2097 }
2098
2099 pub fn new_pay(
2100 sender: SuiAddress,
2101 coins: Vec<ObjectRef>,
2102 recipients: Vec<SuiAddress>,
2103 amounts: Vec<u64>,
2104 gas_payment: ObjectRef,
2105 gas_budget: u64,
2106 gas_price: u64,
2107 ) -> anyhow::Result<Self> {
2108 let pt = {
2109 let mut builder = ProgrammableTransactionBuilder::new();
2110 builder.pay(coins, recipients, amounts)?;
2111 builder.finish()
2112 };
2113 Ok(Self::new_programmable(
2114 sender,
2115 vec![gas_payment],
2116 pt,
2117 gas_budget,
2118 gas_price,
2119 ))
2120 }
2121
2122 pub fn new_pay_sui(
2123 sender: SuiAddress,
2124 mut coins: Vec<ObjectRef>,
2125 recipients: Vec<SuiAddress>,
2126 amounts: Vec<u64>,
2127 gas_payment: ObjectRef,
2128 gas_budget: u64,
2129 gas_price: u64,
2130 ) -> anyhow::Result<Self> {
2131 coins.insert(0, gas_payment);
2132 let pt = {
2133 let mut builder = ProgrammableTransactionBuilder::new();
2134 builder.pay_sui(recipients, amounts)?;
2135 builder.finish()
2136 };
2137 Ok(Self::new_programmable(
2138 sender, coins, pt, gas_budget, gas_price,
2139 ))
2140 }
2141
2142 pub fn new_pay_all_sui(
2143 sender: SuiAddress,
2144 mut coins: Vec<ObjectRef>,
2145 recipient: SuiAddress,
2146 gas_payment: ObjectRef,
2147 gas_budget: u64,
2148 gas_price: u64,
2149 ) -> Self {
2150 coins.insert(0, gas_payment);
2151 let pt = {
2152 let mut builder = ProgrammableTransactionBuilder::new();
2153 builder.pay_all_sui(recipient);
2154 builder.finish()
2155 };
2156 Self::new_programmable(sender, coins, pt, gas_budget, gas_price)
2157 }
2158
2159 pub fn new_split_coin(
2160 sender: SuiAddress,
2161 coin: ObjectRef,
2162 amounts: Vec<u64>,
2163 gas_payment: ObjectRef,
2164 gas_budget: u64,
2165 gas_price: u64,
2166 ) -> Self {
2167 let pt = {
2168 let mut builder = ProgrammableTransactionBuilder::new();
2169 builder.split_coin(sender, coin, amounts);
2170 builder.finish()
2171 };
2172 Self::new_programmable(sender, vec![gas_payment], pt, gas_budget, gas_price)
2173 }
2174
2175 pub fn new_module(
2176 sender: SuiAddress,
2177 gas_payment: ObjectRef,
2178 modules: Vec<Vec<u8>>,
2179 dep_ids: Vec<ObjectID>,
2180 gas_budget: u64,
2181 gas_price: u64,
2182 ) -> Self {
2183 let pt = {
2184 let mut builder = ProgrammableTransactionBuilder::new();
2185 let upgrade_cap = builder.publish_upgradeable(modules, dep_ids);
2186 builder.transfer_arg(sender, upgrade_cap);
2187 builder.finish()
2188 };
2189 Self::new_programmable(sender, vec![gas_payment], pt, gas_budget, gas_price)
2190 }
2191
2192 pub fn new_upgrade(
2193 sender: SuiAddress,
2194 gas_payment: ObjectRef,
2195 package_id: ObjectID,
2196 modules: Vec<Vec<u8>>,
2197 dep_ids: Vec<ObjectID>,
2198 (upgrade_capability, capability_owner): (ObjectRef, Owner),
2199 upgrade_policy: u8,
2200 digest: Vec<u8>,
2201 gas_budget: u64,
2202 gas_price: u64,
2203 ) -> anyhow::Result<Self> {
2204 let pt = {
2205 let mut builder = ProgrammableTransactionBuilder::new();
2206 let capability_arg = match capability_owner {
2207 Owner::AddressOwner(_) => ObjectArg::ImmOrOwnedObject(upgrade_capability),
2208 Owner::Shared {
2209 initial_shared_version,
2210 }
2211 | Owner::ConsensusAddressOwner {
2212 start_version: initial_shared_version,
2213 ..
2214 } => ObjectArg::SharedObject {
2215 id: upgrade_capability.0,
2216 initial_shared_version,
2217 mutability: SharedObjectMutability::Mutable,
2218 },
2219 Owner::Immutable => {
2220 return Err(anyhow::anyhow!(
2221 "Upgrade capability is stored immutably and cannot be used for upgrades"
2222 ));
2223 }
2224 Owner::ObjectOwner(_) => {
2227 return Err(anyhow::anyhow!("Upgrade capability controlled by object"));
2228 }
2229 };
2230 builder.obj(capability_arg).unwrap();
2231 let upgrade_arg = builder.pure(upgrade_policy).unwrap();
2232 let digest_arg = builder.pure(digest).unwrap();
2233 let upgrade_ticket = builder.programmable_move_call(
2234 SUI_FRAMEWORK_PACKAGE_ID,
2235 ident_str!("package").to_owned(),
2236 ident_str!("authorize_upgrade").to_owned(),
2237 vec![],
2238 vec![Argument::Input(0), upgrade_arg, digest_arg],
2239 );
2240 let upgrade_receipt = builder.upgrade(package_id, upgrade_ticket, dep_ids, modules);
2241
2242 builder.programmable_move_call(
2243 SUI_FRAMEWORK_PACKAGE_ID,
2244 ident_str!("package").to_owned(),
2245 ident_str!("commit_upgrade").to_owned(),
2246 vec![],
2247 vec![Argument::Input(0), upgrade_receipt],
2248 );
2249
2250 builder.finish()
2251 };
2252 Ok(Self::new_programmable(
2253 sender,
2254 vec![gas_payment],
2255 pt,
2256 gas_budget,
2257 gas_price,
2258 ))
2259 }
2260
2261 pub fn new_programmable(
2262 sender: SuiAddress,
2263 gas_payment: Vec<ObjectRef>,
2264 pt: ProgrammableTransaction,
2265 gas_budget: u64,
2266 gas_price: u64,
2267 ) -> Self {
2268 Self::new_programmable_allow_sponsor(sender, gas_payment, pt, gas_budget, gas_price, sender)
2269 }
2270
2271 pub fn new_programmable_allow_sponsor(
2272 sender: SuiAddress,
2273 gas_payment: Vec<ObjectRef>,
2274 pt: ProgrammableTransaction,
2275 gas_budget: u64,
2276 gas_price: u64,
2277 sponsor: SuiAddress,
2278 ) -> Self {
2279 let kind = TransactionKind::ProgrammableTransaction(pt);
2280 Self::new_with_gas_coins_allow_sponsor(
2281 kind,
2282 sender,
2283 gas_payment,
2284 gas_budget,
2285 gas_price,
2286 sponsor,
2287 )
2288 }
2289
2290 pub fn message_version(&self) -> u64 {
2291 match self {
2292 TransactionData::V1(_) => 1,
2293 }
2294 }
2295
2296 pub fn execution_parts(&self) -> (TransactionKind, SuiAddress, GasData) {
2297 (self.kind().clone(), self.sender(), self.gas_data().clone())
2298 }
2299
2300 pub fn uses_randomness(&self) -> bool {
2301 self.kind()
2302 .shared_input_objects()
2303 .any(|obj| obj.id() == SUI_RANDOMNESS_STATE_OBJECT_ID)
2304 }
2305
2306 pub fn digest(&self) -> TransactionDigest {
2307 TransactionDigest::new(default_hash(self))
2308 }
2309}
2310
2311#[enum_dispatch]
2312pub trait TransactionDataAPI {
2313 fn sender(&self) -> SuiAddress;
2314
2315 fn kind(&self) -> &TransactionKind;
2318
2319 fn kind_mut(&mut self) -> &mut TransactionKind;
2321
2322 fn into_kind(self) -> TransactionKind;
2324
2325 fn signers(&self) -> NonEmpty<SuiAddress>;
2327
2328 fn gas_data(&self) -> &GasData;
2329
2330 fn gas_owner(&self) -> SuiAddress;
2331
2332 fn gas(&self) -> &[ObjectRef];
2333
2334 fn gas_price(&self) -> u64;
2335
2336 fn gas_budget(&self) -> u64;
2337
2338 fn expiration(&self) -> &TransactionExpiration;
2339
2340 fn move_calls(&self) -> Vec<(&ObjectID, &str, &str)>;
2341
2342 fn input_objects(&self) -> UserInputResult<Vec<InputObjectKind>>;
2343
2344 fn shared_input_objects(&self) -> Vec<SharedInputObject>;
2345
2346 fn receiving_objects(&self) -> Vec<ObjectRef>;
2347
2348 fn fastpath_dependency_objects(
2352 &self,
2353 ) -> UserInputResult<(Vec<ObjectRef>, Vec<ObjectID>, Vec<ObjectRef>)>;
2354
2355 fn process_funds_withdrawals_for_signing(
2363 &self,
2364 ) -> UserInputResult<BTreeMap<AccumulatorObjId, u64>>;
2365
2366 fn process_funds_withdrawals_for_execution(&self) -> BTreeMap<AccumulatorObjId, u64>;
2369
2370 fn has_funds_withdrawals(&self) -> bool;
2372
2373 fn get_funds_withdrawals(&self) -> Vec<FundsWithdrawalArg>;
2375
2376 fn validity_check(&self, config: &ProtocolConfig) -> UserInputResult;
2377
2378 fn validity_check_no_gas_check(&self, config: &ProtocolConfig) -> UserInputResult;
2379
2380 fn check_sponsorship(&self) -> UserInputResult;
2382
2383 fn is_system_tx(&self) -> bool;
2384 fn is_genesis_tx(&self) -> bool;
2385
2386 fn is_end_of_epoch_tx(&self) -> bool;
2389
2390 fn is_consensus_commit_prologue(&self) -> bool;
2391
2392 fn is_sponsored_tx(&self) -> bool;
2394
2395 fn is_gas_paid_from_address_balance(&self) -> bool;
2396
2397 fn sender_mut_for_testing(&mut self) -> &mut SuiAddress;
2398
2399 fn gas_data_mut(&mut self) -> &mut GasData;
2400
2401 fn expiration_mut_for_testing(&mut self) -> &mut TransactionExpiration;
2403}
2404
2405impl TransactionDataAPI for TransactionDataV1 {
2406 fn sender(&self) -> SuiAddress {
2407 self.sender
2408 }
2409
2410 fn kind(&self) -> &TransactionKind {
2411 &self.kind
2412 }
2413
2414 fn kind_mut(&mut self) -> &mut TransactionKind {
2415 &mut self.kind
2416 }
2417
2418 fn into_kind(self) -> TransactionKind {
2419 self.kind
2420 }
2421
2422 fn signers(&self) -> NonEmpty<SuiAddress> {
2424 let mut signers = nonempty![self.sender];
2425 if self.gas_owner() != self.sender {
2426 signers.push(self.gas_owner());
2427 }
2428 signers
2429 }
2430
2431 fn gas_data(&self) -> &GasData {
2432 &self.gas_data
2433 }
2434
2435 fn gas_owner(&self) -> SuiAddress {
2436 self.gas_data.owner
2437 }
2438
2439 fn gas(&self) -> &[ObjectRef] {
2440 &self.gas_data.payment
2441 }
2442
2443 fn gas_price(&self) -> u64 {
2444 self.gas_data.price
2445 }
2446
2447 fn gas_budget(&self) -> u64 {
2448 self.gas_data.budget
2449 }
2450
2451 fn expiration(&self) -> &TransactionExpiration {
2452 &self.expiration
2453 }
2454
2455 fn move_calls(&self) -> Vec<(&ObjectID, &str, &str)> {
2456 self.kind.move_calls()
2457 }
2458
2459 fn input_objects(&self) -> UserInputResult<Vec<InputObjectKind>> {
2460 let mut inputs = self.kind.input_objects()?;
2461
2462 if !self.kind.is_system_tx() {
2463 inputs.extend(
2464 self.gas()
2465 .iter()
2466 .map(|obj_ref| InputObjectKind::ImmOrOwnedMoveObject(*obj_ref)),
2467 );
2468 }
2469 Ok(inputs)
2470 }
2471
2472 fn shared_input_objects(&self) -> Vec<SharedInputObject> {
2473 self.kind.shared_input_objects().collect()
2474 }
2475
2476 fn receiving_objects(&self) -> Vec<ObjectRef> {
2477 self.kind.receiving_objects()
2478 }
2479
2480 fn fastpath_dependency_objects(
2481 &self,
2482 ) -> UserInputResult<(Vec<ObjectRef>, Vec<ObjectID>, Vec<ObjectRef>)> {
2483 let mut move_objects = vec![];
2484 let mut packages = vec![];
2485 let mut receiving_objects = vec![];
2486 self.input_objects()?.iter().for_each(|o| match o {
2487 InputObjectKind::ImmOrOwnedMoveObject(object_ref) => {
2488 move_objects.push(*object_ref);
2489 }
2490 InputObjectKind::MovePackage(package_id) => {
2491 packages.push(*package_id);
2492 }
2493 InputObjectKind::SharedMoveObject { .. } => {}
2494 });
2495 self.receiving_objects().iter().for_each(|object_ref| {
2496 receiving_objects.push(*object_ref);
2497 });
2498 Ok((move_objects, packages, receiving_objects))
2499 }
2500
2501 fn process_funds_withdrawals_for_signing(
2502 &self,
2503 ) -> UserInputResult<BTreeMap<AccumulatorObjId, u64>> {
2504 let mut withdraws = self.get_funds_withdrawals();
2505
2506 withdraws.extend(self.get_funds_withdrawal_for_gas_payment());
2507
2508 let mut withdraw_map: BTreeMap<_, u64> = BTreeMap::new();
2510 for withdraw in withdraws {
2511 let reserved_amount = match &withdraw.reservation {
2512 Reservation::MaxAmountU64(amount) => {
2513 assert!(*amount > 0, "verified in validity check");
2514 *amount
2515 }
2516 Reservation::EntireBalance => unreachable!("verified in validity check"),
2517 };
2518
2519 let account_address = withdraw.owner_for_withdrawal(self);
2520 let account_id =
2521 AccumulatorValue::get_field_id(account_address, &withdraw.type_arg.to_type_tag()?)
2522 .map_err(|e| UserInputError::InvalidWithdrawReservation {
2523 error: e.to_string(),
2524 })?;
2525
2526 let current_amount = withdraw_map.entry(account_id).or_default();
2527 *current_amount = current_amount.checked_add(reserved_amount).ok_or(
2528 UserInputError::InvalidWithdrawReservation {
2529 error: "Balance withdraw reservation overflow".to_string(),
2530 },
2531 )?;
2532 }
2533
2534 Ok(withdraw_map)
2535 }
2536
2537 fn process_funds_withdrawals_for_execution(&self) -> BTreeMap<AccumulatorObjId, u64> {
2538 let mut withdraws = self.get_funds_withdrawals();
2539 withdraws.extend(self.get_funds_withdrawal_for_gas_payment());
2540
2541 let mut withdraw_map: BTreeMap<AccumulatorObjId, u64> = BTreeMap::new();
2543 for withdraw in withdraws {
2544 let reserved_amount = match &withdraw.reservation {
2545 Reservation::MaxAmountU64(amount) => {
2546 assert!(*amount > 0, "verified in validity check");
2547 *amount
2548 }
2549 Reservation::EntireBalance => unreachable!("verified in validity check"),
2550 };
2551
2552 let withdrawal_owner = withdraw.owner_for_withdrawal(self);
2553
2554 let account_id = AccumulatorValue::get_field_id(
2556 withdrawal_owner,
2557 &withdraw.type_arg.to_type_tag().unwrap(),
2558 )
2559 .unwrap();
2560
2561 let value = withdraw_map.entry(account_id).or_default();
2562 *value = value.checked_add(reserved_amount).unwrap();
2564 }
2565
2566 withdraw_map
2567 }
2568
2569 fn has_funds_withdrawals(&self) -> bool {
2570 if self.is_gas_paid_from_address_balance() {
2571 return true;
2572 }
2573 if let TransactionKind::ProgrammableTransaction(pt) = &self.kind {
2574 for input in &pt.inputs {
2575 if matches!(input, CallArg::FundsWithdrawal(_)) {
2576 return true;
2577 }
2578 }
2579 }
2580 false
2581 }
2582
2583 fn get_funds_withdrawals(&self) -> Vec<FundsWithdrawalArg> {
2584 self.kind.get_funds_withdrawals().cloned().collect()
2585 }
2586
2587 fn validity_check(&self, config: &ProtocolConfig) -> UserInputResult {
2588 if let TransactionExpiration::ValidDuring {
2589 min_epoch,
2590 max_epoch,
2591 min_timestamp_seconds,
2592 max_timestamp_seconds,
2593 ..
2594 } = self.expiration()
2595 {
2596 if min_timestamp_seconds.is_some() || max_timestamp_seconds.is_some() {
2597 return Err(UserInputError::Unsupported(
2598 "Timestamp-based transaction expiration is not yet supported".to_string(),
2599 ));
2600 }
2601
2602 match (min_epoch, max_epoch) {
2605 (Some(min), Some(max)) => {
2606 if min != max {
2607 return Err(UserInputError::Unsupported(
2608 "Multi-epoch transaction expiration is not yet supported. min_epoch must equal max_epoch".to_string()
2609 ));
2610 }
2611 }
2612 _ => {
2613 return Err(UserInputError::Unsupported(
2614 "Both min_epoch and max_epoch must be specified and equal".to_string(),
2615 ));
2616 }
2617 }
2618 }
2619
2620 if self.has_funds_withdrawals() {
2621 fp_ensure!(
2622 !self.gas().is_empty() || config.enable_address_balance_gas_payments(),
2623 UserInputError::MissingGasPayment
2624 );
2625 fp_ensure!(
2626 config.enable_accumulators(),
2627 UserInputError::Unsupported("Address balance withdraw is not enabled".to_string())
2628 );
2629
2630 let max_withdraws = 10;
2632
2633 for (count, withdraw) in self.kind.get_funds_withdrawals().enumerate() {
2634 fp_ensure!(
2635 count < max_withdraws,
2636 UserInputError::InvalidWithdrawReservation {
2637 error: format!(
2638 "Maximum number of balance withdraw reservations is {max_withdraws}"
2639 ),
2640 }
2641 );
2642
2643 match withdraw.withdraw_from {
2644 WithdrawFrom::Sender => (),
2645 WithdrawFrom::Sponsor => {
2646 return Err(UserInputError::InvalidWithdrawReservation {
2647 error: "Explicit sponsor withdrawals are not yet supported".to_string(),
2648 });
2649 }
2650 }
2651
2652 match withdraw.reservation {
2653 Reservation::MaxAmountU64(amount) => {
2654 fp_ensure!(
2655 amount > 0,
2656 UserInputError::InvalidWithdrawReservation {
2657 error: "Balance withdraw reservation amount must be non-zero"
2658 .to_string(),
2659 }
2660 );
2661 }
2662 Reservation::EntireBalance => {
2663 return Err(UserInputError::InvalidWithdrawReservation {
2664 error: "Reserving the entire balance is not supported".to_string(),
2665 });
2666 }
2667 };
2668 }
2669 }
2670
2671 if config.enable_accumulators()
2672 && config.enable_address_balance_gas_payments()
2673 && self.is_gas_paid_from_address_balance()
2674 {
2675 match self.expiration() {
2676 TransactionExpiration::None => {
2677 return Err(UserInputError::MissingTransactionExpiration);
2678 }
2679 TransactionExpiration::Epoch(_) => {
2680 return Err(UserInputError::InvalidExpiration {
2681 error: "Address balance gas payments require ValidDuring expiration"
2682 .to_string(),
2683 });
2684 }
2685 TransactionExpiration::ValidDuring { .. } => {}
2686 }
2687 } else {
2688 fp_ensure!(!self.gas().is_empty(), UserInputError::MissingGasPayment);
2689 }
2690
2691 let gas_len = self.gas().len();
2692 let max_gas_objects = config.max_gas_payment_objects() as usize;
2693
2694 let within_limit = if config.correct_gas_payment_limit_check() {
2695 gas_len <= max_gas_objects
2696 } else {
2697 gas_len < max_gas_objects
2698 };
2699
2700 fp_ensure!(
2701 within_limit,
2702 UserInputError::SizeLimitExceeded {
2703 limit: "maximum number of gas payment objects".to_string(),
2704 value: config.max_gas_payment_objects().to_string()
2705 }
2706 );
2707
2708 if !self.is_system_tx() {
2709 let cost_table = SuiCostTable::new(config, self.gas_data.price);
2710
2711 fp_ensure!(
2712 !check_for_gas_price_too_high(config.gas_model_version())
2713 || self.gas_data.price < config.max_gas_price(),
2714 UserInputError::GasPriceTooHigh {
2715 max_gas_price: config.max_gas_price(),
2716 }
2717 );
2718
2719 fp_ensure!(
2720 self.gas_data.budget <= cost_table.max_gas_budget,
2721 UserInputError::GasBudgetTooHigh {
2722 gas_budget: self.gas_data().budget,
2723 max_budget: cost_table.max_gas_budget,
2724 }
2725 );
2726 fp_ensure!(
2727 self.gas_data.budget >= cost_table.min_transaction_cost,
2728 UserInputError::GasBudgetTooLow {
2729 gas_budget: self.gas_data.budget,
2730 min_budget: cost_table.min_transaction_cost,
2731 }
2732 );
2733 }
2734
2735 self.validity_check_no_gas_check(config)
2736 }
2737
2738 fn validity_check_no_gas_check(&self, config: &ProtocolConfig) -> UserInputResult {
2741 self.kind().validity_check(config)?;
2742 self.check_sponsorship()
2743 }
2744
2745 fn is_sponsored_tx(&self) -> bool {
2747 self.gas_owner() != self.sender
2748 }
2749
2750 fn is_gas_paid_from_address_balance(&self) -> bool {
2751 is_gas_paid_from_address_balance(&self.gas_data, &self.kind)
2752 }
2753
2754 fn check_sponsorship(&self) -> UserInputResult {
2756 if self.gas_owner() == self.sender() {
2758 return Ok(());
2759 }
2760 if matches!(&self.kind, TransactionKind::ProgrammableTransaction(_)) {
2761 return Ok(());
2762 }
2763 Err(UserInputError::UnsupportedSponsoredTransactionKind)
2764 }
2765
2766 fn is_end_of_epoch_tx(&self) -> bool {
2767 matches!(
2768 self.kind,
2769 TransactionKind::ChangeEpoch(_) | TransactionKind::EndOfEpochTransaction(_)
2770 )
2771 }
2772
2773 fn is_consensus_commit_prologue(&self) -> bool {
2774 match &self.kind {
2775 TransactionKind::ConsensusCommitPrologue(_)
2776 | TransactionKind::ConsensusCommitPrologueV2(_)
2777 | TransactionKind::ConsensusCommitPrologueV3(_)
2778 | TransactionKind::ConsensusCommitPrologueV4(_) => true,
2779
2780 TransactionKind::ProgrammableTransaction(_)
2781 | TransactionKind::ProgrammableSystemTransaction(_)
2782 | TransactionKind::ChangeEpoch(_)
2783 | TransactionKind::Genesis(_)
2784 | TransactionKind::AuthenticatorStateUpdate(_)
2785 | TransactionKind::EndOfEpochTransaction(_)
2786 | TransactionKind::RandomnessStateUpdate(_) => false,
2787 }
2788 }
2789
2790 fn is_system_tx(&self) -> bool {
2791 self.kind.is_system_tx()
2792 }
2793
2794 fn is_genesis_tx(&self) -> bool {
2795 matches!(self.kind, TransactionKind::Genesis(_))
2796 }
2797
2798 fn sender_mut_for_testing(&mut self) -> &mut SuiAddress {
2799 &mut self.sender
2800 }
2801
2802 fn gas_data_mut(&mut self) -> &mut GasData {
2803 &mut self.gas_data
2804 }
2805
2806 fn expiration_mut_for_testing(&mut self) -> &mut TransactionExpiration {
2807 &mut self.expiration
2808 }
2809}
2810
2811impl TransactionDataV1 {
2812 fn get_funds_withdrawal_for_gas_payment(&self) -> Option<FundsWithdrawalArg> {
2813 if self.is_gas_paid_from_address_balance() {
2814 Some(if self.sender() != self.gas_owner() {
2815 FundsWithdrawalArg::balance_from_sponsor(
2816 self.gas_data().budget,
2817 TypeInput::from(GAS::type_tag()),
2818 )
2819 } else {
2820 FundsWithdrawalArg::balance_from_sender(
2821 self.gas_data().budget,
2822 TypeInput::from(GAS::type_tag()),
2823 )
2824 })
2825 } else {
2826 None
2827 }
2828 }
2829}
2830
2831pub struct TxValidityCheckContext<'a> {
2832 pub config: &'a ProtocolConfig,
2833 pub epoch: EpochId,
2834 pub accumulator_object_init_shared_version: Option<SequenceNumber>,
2835 pub chain_identifier: ChainIdentifier,
2836}
2837
2838#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
2839pub struct SenderSignedData(SizeOneVec<SenderSignedTransaction>);
2840
2841#[derive(Debug, Clone, PartialEq, Eq, Hash)]
2842pub struct SenderSignedTransaction {
2843 pub intent_message: IntentMessage<TransactionData>,
2844 pub tx_signatures: Vec<GenericSignature>,
2848}
2849
2850impl Serialize for SenderSignedTransaction {
2851 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
2852 where
2853 S: serde::Serializer,
2854 {
2855 #[derive(Serialize)]
2856 #[serde(rename = "SenderSignedTransaction")]
2857 struct SignedTxn<'a> {
2858 intent_message: &'a IntentMessage<TransactionData>,
2859 tx_signatures: &'a Vec<GenericSignature>,
2860 }
2861
2862 if self.intent_message().intent != Intent::sui_transaction() {
2863 return Err(serde::ser::Error::custom("invalid Intent for Transaction"));
2864 }
2865
2866 let txn = SignedTxn {
2867 intent_message: self.intent_message(),
2868 tx_signatures: &self.tx_signatures,
2869 };
2870 txn.serialize(serializer)
2871 }
2872}
2873
2874impl<'de> Deserialize<'de> for SenderSignedTransaction {
2875 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
2876 where
2877 D: serde::Deserializer<'de>,
2878 {
2879 #[derive(Deserialize)]
2880 #[serde(rename = "SenderSignedTransaction")]
2881 struct SignedTxn {
2882 intent_message: IntentMessage<TransactionData>,
2883 tx_signatures: Vec<GenericSignature>,
2884 }
2885
2886 let SignedTxn {
2887 intent_message,
2888 tx_signatures,
2889 } = Deserialize::deserialize(deserializer)?;
2890
2891 if intent_message.intent != Intent::sui_transaction() {
2892 return Err(serde::de::Error::custom("invalid Intent for Transaction"));
2893 }
2894
2895 Ok(Self {
2896 intent_message,
2897 tx_signatures,
2898 })
2899 }
2900}
2901
2902impl SenderSignedTransaction {
2903 pub(crate) fn get_signer_sig_mapping(
2904 &self,
2905 verify_legacy_zklogin_address: bool,
2906 ) -> SuiResult<BTreeMap<SuiAddress, &GenericSignature>> {
2907 let mut mapping = BTreeMap::new();
2908 for sig in &self.tx_signatures {
2909 if verify_legacy_zklogin_address {
2910 if let GenericSignature::ZkLoginAuthenticator(z) = sig {
2912 mapping.insert(SuiAddress::try_from_padded(&z.inputs)?, sig);
2913 };
2914 }
2915 let address = sig.try_into()?;
2916 mapping.insert(address, sig);
2917 }
2918 Ok(mapping)
2919 }
2920
2921 pub fn intent_message(&self) -> &IntentMessage<TransactionData> {
2922 &self.intent_message
2923 }
2924}
2925
2926impl SenderSignedData {
2927 pub fn new(tx_data: TransactionData, tx_signatures: Vec<GenericSignature>) -> Self {
2928 Self(SizeOneVec::new(SenderSignedTransaction {
2929 intent_message: IntentMessage::new(Intent::sui_transaction(), tx_data),
2930 tx_signatures,
2931 }))
2932 }
2933
2934 pub fn new_from_sender_signature(tx_data: TransactionData, tx_signature: Signature) -> Self {
2935 Self(SizeOneVec::new(SenderSignedTransaction {
2936 intent_message: IntentMessage::new(Intent::sui_transaction(), tx_data),
2937 tx_signatures: vec![tx_signature.into()],
2938 }))
2939 }
2940
2941 pub fn inner(&self) -> &SenderSignedTransaction {
2942 self.0.element()
2943 }
2944
2945 pub fn into_inner(self) -> SenderSignedTransaction {
2946 self.0.into_inner()
2947 }
2948
2949 pub fn inner_mut(&mut self) -> &mut SenderSignedTransaction {
2950 self.0.element_mut()
2951 }
2952
2953 pub fn add_signature(&mut self, new_signature: Signature) {
2956 self.inner_mut().tx_signatures.push(new_signature.into());
2957 }
2958
2959 pub(crate) fn get_signer_sig_mapping(
2960 &self,
2961 verify_legacy_zklogin_address: bool,
2962 ) -> SuiResult<BTreeMap<SuiAddress, &GenericSignature>> {
2963 self.inner()
2964 .get_signer_sig_mapping(verify_legacy_zklogin_address)
2965 }
2966
2967 pub fn transaction_data(&self) -> &TransactionData {
2968 &self.intent_message().value
2969 }
2970
2971 pub fn intent_message(&self) -> &IntentMessage<TransactionData> {
2972 self.inner().intent_message()
2973 }
2974
2975 pub fn tx_signatures(&self) -> &[GenericSignature] {
2976 &self.inner().tx_signatures
2977 }
2978
2979 pub fn has_zklogin_sig(&self) -> bool {
2980 self.tx_signatures().iter().any(|sig| sig.is_zklogin())
2981 }
2982
2983 pub fn has_upgraded_multisig(&self) -> bool {
2984 self.tx_signatures()
2985 .iter()
2986 .any(|sig| sig.is_upgraded_multisig())
2987 }
2988
2989 #[cfg(test)]
2990 pub fn intent_message_mut_for_testing(&mut self) -> &mut IntentMessage<TransactionData> {
2991 &mut self.inner_mut().intent_message
2992 }
2993
2994 pub fn tx_signatures_mut_for_testing(&mut self) -> &mut Vec<GenericSignature> {
2996 &mut self.inner_mut().tx_signatures
2997 }
2998
2999 pub fn full_message_digest(&self) -> SenderSignedDataDigest {
3000 let mut digest = DefaultHash::default();
3001 bcs::serialize_into(&mut digest, self).expect("serialization should not fail");
3002 let hash = digest.finalize();
3003 SenderSignedDataDigest::new(hash.into())
3004 }
3005
3006 pub fn serialized_size(&self) -> SuiResult<usize> {
3007 bcs::serialized_size(self).map_err(|e| {
3008 SuiErrorKind::TransactionSerializationError {
3009 error: e.to_string(),
3010 }
3011 .into()
3012 })
3013 }
3014
3015 fn check_user_signature_protocol_compatibility(&self, config: &ProtocolConfig) -> SuiResult {
3016 for sig in &self.inner().tx_signatures {
3017 match sig {
3018 GenericSignature::MultiSig(_) => {
3019 if !config.supports_upgraded_multisig() {
3020 return Err(SuiErrorKind::UserInputError {
3021 error: UserInputError::Unsupported(
3022 "upgraded multisig format not enabled on this network".to_string(),
3023 ),
3024 }
3025 .into());
3026 }
3027 }
3028 GenericSignature::ZkLoginAuthenticator(_) => {
3029 if !config.zklogin_auth() {
3030 return Err(SuiErrorKind::UserInputError {
3031 error: UserInputError::Unsupported(
3032 "zklogin is not enabled on this network".to_string(),
3033 ),
3034 }
3035 .into());
3036 }
3037 }
3038 GenericSignature::PasskeyAuthenticator(_) => {
3039 if !config.passkey_auth() {
3040 return Err(SuiErrorKind::UserInputError {
3041 error: UserInputError::Unsupported(
3042 "passkey is not enabled on this network".to_string(),
3043 ),
3044 }
3045 .into());
3046 }
3047 }
3048 GenericSignature::Signature(_) | GenericSignature::MultiSigLegacy(_) => (),
3049 }
3050 }
3051
3052 Ok(())
3053 }
3054
3055 pub fn validity_check(&self, context: &TxValidityCheckContext<'_>) -> Result<usize, SuiError> {
3058 self.check_user_signature_protocol_compatibility(context.config)?;
3060
3061 let tx_data = &self.transaction_data();
3066 fp_ensure!(
3067 !tx_data.is_system_tx(),
3068 SuiErrorKind::UserInputError {
3069 error: UserInputError::Unsupported(
3070 "SenderSignedData must not contain system transaction".to_string()
3071 )
3072 }
3073 .into()
3074 );
3075
3076 match tx_data.expiration() {
3078 TransactionExpiration::None => {
3079 }
3081 TransactionExpiration::Epoch(exp_epoch) => {
3082 if *exp_epoch < context.epoch {
3083 return Err(SuiErrorKind::TransactionExpired.into());
3084 }
3085 }
3086 TransactionExpiration::ValidDuring {
3087 min_epoch,
3088 max_epoch,
3089 chain,
3090 ..
3091 } => {
3092 if *chain != context.chain_identifier {
3093 return Err(SuiErrorKind::UserInputError {
3094 error: UserInputError::InvalidChainId {
3095 provided: format!("{:?}", chain),
3096 expected: format!("{:?}", context.chain_identifier),
3097 },
3098 }
3099 .into());
3100 }
3101
3102 if let Some(min) = min_epoch
3103 && context.epoch < *min
3104 {
3105 return Err(SuiErrorKind::TransactionExpired.into());
3106 }
3107 if let Some(max) = max_epoch
3108 && context.epoch > *max
3109 {
3110 return Err(SuiErrorKind::TransactionExpired.into());
3111 }
3112 }
3113 }
3114
3115 let tx_size = self.serialized_size()?;
3117 let max_tx_size_bytes = context.config.max_tx_size_bytes();
3118 fp_ensure!(
3119 tx_size as u64 <= max_tx_size_bytes,
3120 SuiErrorKind::UserInputError {
3121 error: UserInputError::SizeLimitExceeded {
3122 limit: format!(
3123 "serialized transaction size exceeded maximum of {max_tx_size_bytes}"
3124 ),
3125 value: tx_size.to_string(),
3126 }
3127 }
3128 .into()
3129 );
3130
3131 tx_data
3132 .validity_check(context.config)
3133 .map_err(Into::<SuiError>::into)?;
3134
3135 Ok(tx_size)
3136 }
3137}
3138
3139impl Message for SenderSignedData {
3140 type DigestType = TransactionDigest;
3141 const SCOPE: IntentScope = IntentScope::SenderSignedTransaction;
3142
3143 fn digest(&self) -> Self::DigestType {
3145 self.intent_message().value.digest()
3146 }
3147}
3148
3149impl<S> Envelope<SenderSignedData, S> {
3150 pub fn sender_address(&self) -> SuiAddress {
3151 self.data().intent_message().value.sender()
3152 }
3153
3154 pub fn gas_owner(&self) -> SuiAddress {
3155 self.data().intent_message().value.gas_owner()
3156 }
3157
3158 pub fn gas(&self) -> &[ObjectRef] {
3159 self.data().intent_message().value.gas()
3160 }
3161
3162 pub fn is_consensus_tx(&self) -> bool {
3163 self.transaction_data().has_funds_withdrawals()
3164 || self.shared_input_objects().next().is_some()
3165 }
3166
3167 pub fn shared_input_objects(&self) -> impl Iterator<Item = SharedInputObject> + '_ {
3168 self.data()
3169 .inner()
3170 .intent_message
3171 .value
3172 .shared_input_objects()
3173 .into_iter()
3174 }
3175
3176 pub fn key(&self) -> TransactionKey {
3178 match &self.data().intent_message().value.kind() {
3179 TransactionKind::RandomnessStateUpdate(rsu) => {
3180 TransactionKey::RandomnessRound(rsu.epoch, rsu.randomness_round)
3181 }
3182 _ => TransactionKey::Digest(*self.digest()),
3183 }
3184 }
3185
3186 pub fn non_digest_key(&self) -> Option<TransactionKey> {
3191 match &self.data().intent_message().value.kind() {
3192 TransactionKind::RandomnessStateUpdate(rsu) => Some(TransactionKey::RandomnessRound(
3193 rsu.epoch,
3194 rsu.randomness_round,
3195 )),
3196 _ => None,
3197 }
3198 }
3199
3200 pub fn is_system_tx(&self) -> bool {
3201 self.data().intent_message().value.is_system_tx()
3202 }
3203
3204 pub fn is_sponsored_tx(&self) -> bool {
3205 self.data().intent_message().value.is_sponsored_tx()
3206 }
3207}
3208
3209impl Transaction {
3210 pub fn from_data_and_signer(
3211 data: TransactionData,
3212 signers: Vec<&dyn Signer<Signature>>,
3213 ) -> Self {
3214 let signatures = {
3215 let intent_msg = IntentMessage::new(Intent::sui_transaction(), &data);
3216 signers
3217 .into_iter()
3218 .map(|s| Signature::new_secure(&intent_msg, s))
3219 .collect()
3220 };
3221 Self::from_data(data, signatures)
3222 }
3223
3224 pub fn from_data(data: TransactionData, signatures: Vec<Signature>) -> Self {
3226 Self::from_generic_sig_data(data, signatures.into_iter().map(|s| s.into()).collect())
3227 }
3228
3229 pub fn signature_from_signer(
3230 data: TransactionData,
3231 intent: Intent,
3232 signer: &dyn Signer<Signature>,
3233 ) -> Signature {
3234 let intent_msg = IntentMessage::new(intent, data);
3235 Signature::new_secure(&intent_msg, signer)
3236 }
3237
3238 pub fn from_generic_sig_data(data: TransactionData, signatures: Vec<GenericSignature>) -> Self {
3239 Self::new(SenderSignedData::new(data, signatures))
3240 }
3241
3242 pub fn to_tx_bytes_and_signatures(&self) -> (Base64, Vec<Base64>) {
3245 (
3246 Base64::from_bytes(&bcs::to_bytes(&self.data().intent_message().value).unwrap()),
3247 self.data()
3248 .inner()
3249 .tx_signatures
3250 .iter()
3251 .map(|s| Base64::from_bytes(s.as_ref()))
3252 .collect(),
3253 )
3254 }
3255}
3256
3257impl VerifiedTransaction {
3258 pub fn new_change_epoch(
3259 next_epoch: EpochId,
3260 protocol_version: ProtocolVersion,
3261 storage_charge: u64,
3262 computation_charge: u64,
3263 storage_rebate: u64,
3264 non_refundable_storage_fee: u64,
3265 epoch_start_timestamp_ms: u64,
3266 system_packages: Vec<(SequenceNumber, Vec<Vec<u8>>, Vec<ObjectID>)>,
3267 ) -> Self {
3268 ChangeEpoch {
3269 epoch: next_epoch,
3270 protocol_version,
3271 storage_charge,
3272 computation_charge,
3273 storage_rebate,
3274 non_refundable_storage_fee,
3275 epoch_start_timestamp_ms,
3276 system_packages,
3277 }
3278 .pipe(TransactionKind::ChangeEpoch)
3279 .pipe(Self::new_system_transaction)
3280 }
3281
3282 pub fn new_genesis_transaction(objects: Vec<GenesisObject>) -> Self {
3283 GenesisTransaction { objects }
3284 .pipe(TransactionKind::Genesis)
3285 .pipe(Self::new_system_transaction)
3286 }
3287
3288 pub fn new_consensus_commit_prologue(
3289 epoch: u64,
3290 round: u64,
3291 commit_timestamp_ms: CheckpointTimestamp,
3292 ) -> Self {
3293 ConsensusCommitPrologue {
3294 epoch,
3295 round,
3296 commit_timestamp_ms,
3297 }
3298 .pipe(TransactionKind::ConsensusCommitPrologue)
3299 .pipe(Self::new_system_transaction)
3300 }
3301
3302 pub fn new_consensus_commit_prologue_v2(
3303 epoch: u64,
3304 round: u64,
3305 commit_timestamp_ms: CheckpointTimestamp,
3306 consensus_commit_digest: ConsensusCommitDigest,
3307 ) -> Self {
3308 ConsensusCommitPrologueV2 {
3309 epoch,
3310 round,
3311 commit_timestamp_ms,
3312 consensus_commit_digest,
3313 }
3314 .pipe(TransactionKind::ConsensusCommitPrologueV2)
3315 .pipe(Self::new_system_transaction)
3316 }
3317
3318 pub fn new_consensus_commit_prologue_v3(
3319 epoch: u64,
3320 round: u64,
3321 commit_timestamp_ms: CheckpointTimestamp,
3322 consensus_commit_digest: ConsensusCommitDigest,
3323 consensus_determined_version_assignments: ConsensusDeterminedVersionAssignments,
3324 ) -> Self {
3325 ConsensusCommitPrologueV3 {
3326 epoch,
3327 round,
3328 sub_dag_index: None,
3330 commit_timestamp_ms,
3331 consensus_commit_digest,
3332 consensus_determined_version_assignments,
3333 }
3334 .pipe(TransactionKind::ConsensusCommitPrologueV3)
3335 .pipe(Self::new_system_transaction)
3336 }
3337
3338 pub fn new_consensus_commit_prologue_v4(
3339 epoch: u64,
3340 round: u64,
3341 commit_timestamp_ms: CheckpointTimestamp,
3342 consensus_commit_digest: ConsensusCommitDigest,
3343 consensus_determined_version_assignments: ConsensusDeterminedVersionAssignments,
3344 additional_state_digest: AdditionalConsensusStateDigest,
3345 ) -> Self {
3346 ConsensusCommitPrologueV4 {
3347 epoch,
3348 round,
3349 sub_dag_index: None,
3351 commit_timestamp_ms,
3352 consensus_commit_digest,
3353 consensus_determined_version_assignments,
3354 additional_state_digest,
3355 }
3356 .pipe(TransactionKind::ConsensusCommitPrologueV4)
3357 .pipe(Self::new_system_transaction)
3358 }
3359
3360 pub fn new_authenticator_state_update(
3361 epoch: u64,
3362 round: u64,
3363 new_active_jwks: Vec<ActiveJwk>,
3364 authenticator_obj_initial_shared_version: SequenceNumber,
3365 ) -> Self {
3366 AuthenticatorStateUpdate {
3367 epoch,
3368 round,
3369 new_active_jwks,
3370 authenticator_obj_initial_shared_version,
3371 }
3372 .pipe(TransactionKind::AuthenticatorStateUpdate)
3373 .pipe(Self::new_system_transaction)
3374 }
3375
3376 pub fn new_randomness_state_update(
3377 epoch: u64,
3378 randomness_round: RandomnessRound,
3379 random_bytes: Vec<u8>,
3380 randomness_obj_initial_shared_version: SequenceNumber,
3381 ) -> Self {
3382 RandomnessStateUpdate {
3383 epoch,
3384 randomness_round,
3385 random_bytes,
3386 randomness_obj_initial_shared_version,
3387 }
3388 .pipe(TransactionKind::RandomnessStateUpdate)
3389 .pipe(Self::new_system_transaction)
3390 }
3391
3392 pub fn new_end_of_epoch_transaction(txns: Vec<EndOfEpochTransactionKind>) -> Self {
3393 TransactionKind::EndOfEpochTransaction(txns).pipe(Self::new_system_transaction)
3394 }
3395
3396 pub fn new_system_transaction(system_transaction: TransactionKind) -> Self {
3397 system_transaction
3398 .pipe(TransactionData::new_system_transaction)
3399 .pipe(|data| {
3400 SenderSignedData::new_from_sender_signature(
3401 data,
3402 Ed25519SuiSignature::from_bytes(&[0; Ed25519SuiSignature::LENGTH])
3403 .unwrap()
3404 .into(),
3405 )
3406 })
3407 .pipe(Transaction::new)
3408 .pipe(Self::new_from_verified)
3409 }
3410}
3411
3412impl VerifiedSignedTransaction {
3413 pub fn new(
3415 epoch: EpochId,
3416 transaction: VerifiedTransaction,
3417 authority: AuthorityName,
3418 secret: &dyn Signer<AuthoritySignature>,
3419 ) -> Self {
3420 Self::new_from_verified(SignedTransaction::new(
3421 epoch,
3422 transaction.into_inner().into_data(),
3423 secret,
3424 authority,
3425 ))
3426 }
3427}
3428
3429pub type Transaction = Envelope<SenderSignedData, EmptySignInfo>;
3431pub type VerifiedTransaction = VerifiedEnvelope<SenderSignedData, EmptySignInfo>;
3432pub type TrustedTransaction = TrustedEnvelope<SenderSignedData, EmptySignInfo>;
3433
3434pub type SignedTransaction = Envelope<SenderSignedData, AuthoritySignInfo>;
3436pub type VerifiedSignedTransaction = VerifiedEnvelope<SenderSignedData, AuthoritySignInfo>;
3437
3438impl Transaction {
3439 pub fn verify_signature_for_testing(
3440 &self,
3441 current_epoch: EpochId,
3442 verify_params: &VerifyParams,
3443 ) -> SuiResult {
3444 verify_sender_signed_data_message_signatures(
3445 self.data(),
3446 current_epoch,
3447 verify_params,
3448 Arc::new(VerifiedDigestCache::new_empty()),
3449 )
3450 }
3451
3452 pub fn try_into_verified_for_testing(
3453 self,
3454 current_epoch: EpochId,
3455 verify_params: &VerifyParams,
3456 ) -> SuiResult<VerifiedTransaction> {
3457 self.verify_signature_for_testing(current_epoch, verify_params)?;
3458 Ok(VerifiedTransaction::new_from_verified(self))
3459 }
3460}
3461
3462impl SignedTransaction {
3463 pub fn verify_signatures_authenticated_for_testing(
3464 &self,
3465 committee: &Committee,
3466 verify_params: &VerifyParams,
3467 ) -> SuiResult {
3468 verify_sender_signed_data_message_signatures(
3469 self.data(),
3470 committee.epoch(),
3471 verify_params,
3472 Arc::new(VerifiedDigestCache::new_empty()),
3473 )?;
3474
3475 self.auth_sig().verify_secure(
3476 self.data(),
3477 Intent::sui_app(IntentScope::SenderSignedTransaction),
3478 committee,
3479 )
3480 }
3481
3482 pub fn try_into_verified_for_testing(
3483 self,
3484 committee: &Committee,
3485 verify_params: &VerifyParams,
3486 ) -> SuiResult<VerifiedSignedTransaction> {
3487 self.verify_signatures_authenticated_for_testing(committee, verify_params)?;
3488 Ok(VerifiedSignedTransaction::new_from_verified(self))
3489 }
3490}
3491
3492pub type CertifiedTransaction = Envelope<SenderSignedData, AuthorityStrongQuorumSignInfo>;
3493
3494impl CertifiedTransaction {
3495 pub fn certificate_digest(&self) -> CertificateDigest {
3496 let mut digest = DefaultHash::default();
3497 bcs::serialize_into(&mut digest, self).expect("serialization should not fail");
3498 let hash = digest.finalize();
3499 CertificateDigest::new(hash.into())
3500 }
3501
3502 pub fn gas_price(&self) -> u64 {
3503 self.data().transaction_data().gas_price()
3504 }
3505
3506 pub fn verify_signatures_authenticated(
3509 &self,
3510 committee: &Committee,
3511 verify_params: &VerifyParams,
3512 zklogin_inputs_cache: Arc<VerifiedDigestCache<ZKLoginInputsDigest>>,
3513 ) -> SuiResult {
3514 verify_sender_signed_data_message_signatures(
3515 self.data(),
3516 committee.epoch(),
3517 verify_params,
3518 zklogin_inputs_cache,
3519 )?;
3520 self.auth_sig().verify_secure(
3521 self.data(),
3522 Intent::sui_app(IntentScope::SenderSignedTransaction),
3523 committee,
3524 )
3525 }
3526
3527 pub fn try_into_verified_for_testing(
3528 self,
3529 committee: &Committee,
3530 verify_params: &VerifyParams,
3531 ) -> SuiResult<VerifiedCertificate> {
3532 self.verify_signatures_authenticated(
3533 committee,
3534 verify_params,
3535 Arc::new(VerifiedDigestCache::new_empty()),
3536 )?;
3537 Ok(VerifiedCertificate::new_from_verified(self))
3538 }
3539
3540 pub fn verify_committee_sigs_only(&self, committee: &Committee) -> SuiResult {
3541 self.auth_sig().verify_secure(
3542 self.data(),
3543 Intent::sui_app(IntentScope::SenderSignedTransaction),
3544 committee,
3545 )
3546 }
3547}
3548
3549pub type VerifiedCertificate = VerifiedEnvelope<SenderSignedData, AuthorityStrongQuorumSignInfo>;
3550pub type TrustedCertificate = TrustedEnvelope<SenderSignedData, AuthorityStrongQuorumSignInfo>;
3551
3552#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize, PartialOrd, Ord, Hash)]
3553pub enum InputObjectKind {
3554 MovePackage(ObjectID),
3556 ImmOrOwnedMoveObject(ObjectRef),
3558 SharedMoveObject {
3560 id: ObjectID,
3561 initial_shared_version: SequenceNumber,
3562 mutability: SharedObjectMutability,
3563 },
3564}
3565
3566#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize, PartialOrd, Ord, Hash)]
3567pub enum SharedObjectMutability {
3568 Immutable,
3570 Mutable,
3571 NonExclusiveWrite,
3575}
3576
3577impl SharedObjectMutability {
3578 pub fn is_exclusive(&self) -> bool {
3579 match self {
3580 SharedObjectMutability::Mutable => true,
3581 SharedObjectMutability::Immutable => false,
3582 SharedObjectMutability::NonExclusiveWrite => false,
3583 }
3584 }
3585}
3586
3587impl InputObjectKind {
3588 pub fn object_id(&self) -> ObjectID {
3589 self.full_object_id().id()
3590 }
3591
3592 pub fn full_object_id(&self) -> FullObjectID {
3593 match self {
3594 Self::MovePackage(id) => FullObjectID::Fastpath(*id),
3595 Self::ImmOrOwnedMoveObject((id, _, _)) => FullObjectID::Fastpath(*id),
3596 Self::SharedMoveObject {
3597 id,
3598 initial_shared_version,
3599 ..
3600 } => FullObjectID::Consensus((*id, *initial_shared_version)),
3601 }
3602 }
3603
3604 pub fn version(&self) -> Option<SequenceNumber> {
3605 match self {
3606 Self::MovePackage(..) => None,
3607 Self::ImmOrOwnedMoveObject((_, version, _)) => Some(*version),
3608 Self::SharedMoveObject { .. } => None,
3609 }
3610 }
3611
3612 pub fn object_not_found_error(&self) -> UserInputError {
3613 match *self {
3614 Self::MovePackage(package_id) => {
3615 UserInputError::DependentPackageNotFound { package_id }
3616 }
3617 Self::ImmOrOwnedMoveObject((object_id, version, _)) => UserInputError::ObjectNotFound {
3618 object_id,
3619 version: Some(version),
3620 },
3621 Self::SharedMoveObject { id, .. } => UserInputError::ObjectNotFound {
3622 object_id: id,
3623 version: None,
3624 },
3625 }
3626 }
3627
3628 pub fn is_shared_object(&self) -> bool {
3629 matches!(self, Self::SharedMoveObject { .. })
3630 }
3631}
3632
3633#[derive(Clone, Debug)]
3636pub struct ObjectReadResult {
3637 pub input_object_kind: InputObjectKind,
3638 pub object: ObjectReadResultKind,
3639}
3640
3641#[derive(Clone)]
3642pub enum ObjectReadResultKind {
3643 Object(Object),
3644 ObjectConsensusStreamEnded(SequenceNumber, TransactionDigest),
3647 CancelledTransactionSharedObject(SequenceNumber),
3649}
3650
3651impl ObjectReadResultKind {
3652 pub fn is_cancelled(&self) -> bool {
3653 matches!(
3654 self,
3655 ObjectReadResultKind::CancelledTransactionSharedObject(_)
3656 )
3657 }
3658
3659 pub fn version(&self) -> SequenceNumber {
3660 match self {
3661 ObjectReadResultKind::Object(object) => object.version(),
3662 ObjectReadResultKind::ObjectConsensusStreamEnded(seq, _) => *seq,
3663 ObjectReadResultKind::CancelledTransactionSharedObject(seq) => *seq,
3664 }
3665 }
3666}
3667
3668impl std::fmt::Debug for ObjectReadResultKind {
3669 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3670 match self {
3671 ObjectReadResultKind::Object(obj) => {
3672 write!(f, "Object({:?})", obj.compute_object_reference())
3673 }
3674 ObjectReadResultKind::ObjectConsensusStreamEnded(seq, digest) => {
3675 write!(f, "ObjectConsensusStreamEnded({}, {:?})", seq, digest)
3676 }
3677 ObjectReadResultKind::CancelledTransactionSharedObject(seq) => {
3678 write!(f, "CancelledTransactionSharedObject({})", seq)
3679 }
3680 }
3681 }
3682}
3683
3684impl From<Object> for ObjectReadResultKind {
3685 fn from(object: Object) -> Self {
3686 Self::Object(object)
3687 }
3688}
3689
3690impl ObjectReadResult {
3691 pub fn new(input_object_kind: InputObjectKind, object: ObjectReadResultKind) -> Self {
3692 if let (
3693 InputObjectKind::ImmOrOwnedMoveObject(_),
3694 ObjectReadResultKind::ObjectConsensusStreamEnded(_, _),
3695 ) = (&input_object_kind, &object)
3696 {
3697 panic!("only consensus objects can be ObjectConsensusStreamEnded");
3698 }
3699
3700 if let (
3701 InputObjectKind::ImmOrOwnedMoveObject(_),
3702 ObjectReadResultKind::CancelledTransactionSharedObject(_),
3703 ) = (&input_object_kind, &object)
3704 {
3705 panic!("only consensus objects can be CancelledTransactionSharedObject");
3706 }
3707
3708 Self {
3709 input_object_kind,
3710 object,
3711 }
3712 }
3713
3714 pub fn id(&self) -> ObjectID {
3715 self.input_object_kind.object_id()
3716 }
3717
3718 pub fn as_object(&self) -> Option<&Object> {
3719 match &self.object {
3720 ObjectReadResultKind::Object(object) => Some(object),
3721 ObjectReadResultKind::ObjectConsensusStreamEnded(_, _) => None,
3722 ObjectReadResultKind::CancelledTransactionSharedObject(_) => None,
3723 }
3724 }
3725
3726 pub fn new_from_gas_object(gas: &Object) -> Self {
3727 let objref = gas.compute_object_reference();
3728 Self {
3729 input_object_kind: InputObjectKind::ImmOrOwnedMoveObject(objref),
3730 object: ObjectReadResultKind::Object(gas.clone()),
3731 }
3732 }
3733
3734 pub fn is_mutable(&self) -> bool {
3735 match (&self.input_object_kind, &self.object) {
3736 (InputObjectKind::MovePackage(_), _) => false,
3737 (InputObjectKind::ImmOrOwnedMoveObject(_), ObjectReadResultKind::Object(object)) => {
3738 !object.is_immutable()
3739 }
3740 (
3741 InputObjectKind::ImmOrOwnedMoveObject(_),
3742 ObjectReadResultKind::ObjectConsensusStreamEnded(_, _),
3743 ) => unreachable!(),
3744 (
3745 InputObjectKind::ImmOrOwnedMoveObject(_),
3746 ObjectReadResultKind::CancelledTransactionSharedObject(_),
3747 ) => unreachable!(),
3748 (InputObjectKind::SharedMoveObject { mutability, .. }, _) => match mutability {
3749 SharedObjectMutability::Mutable => true,
3750 SharedObjectMutability::Immutable => false,
3751 SharedObjectMutability::NonExclusiveWrite => false,
3752 },
3753 }
3754 }
3755
3756 pub fn is_shared_object(&self) -> bool {
3757 self.input_object_kind.is_shared_object()
3758 }
3759
3760 pub fn is_consensus_stream_ended(&self) -> bool {
3761 self.consensus_stream_end_info().is_some()
3762 }
3763
3764 pub fn consensus_stream_end_info(&self) -> Option<(SequenceNumber, TransactionDigest)> {
3765 match &self.object {
3766 ObjectReadResultKind::ObjectConsensusStreamEnded(v, tx) => Some((*v, *tx)),
3767 _ => None,
3768 }
3769 }
3770
3771 pub fn get_owned_objref(&self) -> Option<ObjectRef> {
3773 match (&self.input_object_kind, &self.object) {
3774 (InputObjectKind::MovePackage(_), _) => None,
3775 (
3776 InputObjectKind::ImmOrOwnedMoveObject(objref),
3777 ObjectReadResultKind::Object(object),
3778 ) => {
3779 if object.is_immutable() {
3780 None
3781 } else {
3782 Some(*objref)
3783 }
3784 }
3785 (
3786 InputObjectKind::ImmOrOwnedMoveObject(_),
3787 ObjectReadResultKind::ObjectConsensusStreamEnded(_, _),
3788 ) => unreachable!(),
3789 (
3790 InputObjectKind::ImmOrOwnedMoveObject(_),
3791 ObjectReadResultKind::CancelledTransactionSharedObject(_),
3792 ) => unreachable!(),
3793 (InputObjectKind::SharedMoveObject { .. }, _) => None,
3794 }
3795 }
3796
3797 pub fn is_owned(&self) -> bool {
3798 self.get_owned_objref().is_some()
3799 }
3800
3801 pub fn to_shared_input(&self) -> Option<SharedInput> {
3802 match self.input_object_kind {
3803 InputObjectKind::MovePackage(_) => None,
3804 InputObjectKind::ImmOrOwnedMoveObject(_) => None,
3805 InputObjectKind::SharedMoveObject { id, mutability, .. } => Some(match &self.object {
3806 ObjectReadResultKind::Object(obj) => {
3807 SharedInput::Existing(obj.compute_object_reference())
3808 }
3809 ObjectReadResultKind::ObjectConsensusStreamEnded(seq, digest) => {
3810 SharedInput::ConsensusStreamEnded((id, *seq, mutability, *digest))
3811 }
3812 ObjectReadResultKind::CancelledTransactionSharedObject(seq) => {
3813 SharedInput::Cancelled((id, *seq))
3814 }
3815 }),
3816 }
3817 }
3818
3819 pub fn get_previous_transaction(&self) -> Option<TransactionDigest> {
3820 match &self.object {
3821 ObjectReadResultKind::Object(obj) => Some(obj.previous_transaction),
3822 ObjectReadResultKind::ObjectConsensusStreamEnded(_, digest) => Some(*digest),
3823 ObjectReadResultKind::CancelledTransactionSharedObject(_) => None,
3824 }
3825 }
3826}
3827
3828#[derive(Clone)]
3829pub struct InputObjects {
3830 objects: Vec<ObjectReadResult>,
3831}
3832
3833impl std::fmt::Debug for InputObjects {
3834 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3835 f.debug_list().entries(self.objects.iter()).finish()
3836 }
3837}
3838
3839pub struct CheckedInputObjects(InputObjects);
3842
3843impl CheckedInputObjects {
3849 pub fn new_with_checked_transaction_inputs(inputs: InputObjects) -> Self {
3851 Self(inputs)
3852 }
3853
3854 pub fn new_for_genesis(input_objects: Vec<ObjectReadResult>) -> Self {
3856 Self(InputObjects::new(input_objects))
3857 }
3858
3859 pub fn new_for_replay(input_objects: InputObjects) -> Self {
3861 Self(input_objects)
3862 }
3863
3864 pub fn inner(&self) -> &InputObjects {
3865 &self.0
3866 }
3867
3868 pub fn into_inner(self) -> InputObjects {
3869 self.0
3870 }
3871}
3872
3873impl From<Vec<ObjectReadResult>> for InputObjects {
3874 fn from(objects: Vec<ObjectReadResult>) -> Self {
3875 Self::new(objects)
3876 }
3877}
3878
3879impl InputObjects {
3880 pub fn new(objects: Vec<ObjectReadResult>) -> Self {
3881 Self { objects }
3882 }
3883
3884 pub fn len(&self) -> usize {
3885 self.objects.len()
3886 }
3887
3888 pub fn is_empty(&self) -> bool {
3889 self.objects.is_empty()
3890 }
3891
3892 pub fn contains_consensus_stream_ended_objects(&self) -> bool {
3893 self.objects
3894 .iter()
3895 .any(|obj| obj.is_consensus_stream_ended())
3896 }
3897
3898 pub fn get_cancelled_objects(&self) -> Option<(Vec<ObjectID>, SequenceNumber)> {
3901 let mut contains_cancelled = false;
3902 let mut cancel_reason = None;
3903 let mut cancelled_objects = Vec::new();
3904 for obj in &self.objects {
3905 if let ObjectReadResultKind::CancelledTransactionSharedObject(version) = obj.object {
3906 contains_cancelled = true;
3907 if version == SequenceNumber::CONGESTED
3908 || version == SequenceNumber::RANDOMNESS_UNAVAILABLE
3909 {
3910 assert!(cancel_reason.is_none() || cancel_reason == Some(version));
3912 cancel_reason = Some(version);
3913 cancelled_objects.push(obj.id());
3914 }
3915 }
3916 }
3917
3918 if !cancelled_objects.is_empty() {
3919 Some((
3920 cancelled_objects,
3921 cancel_reason
3922 .expect("there should be a cancel reason if there are cancelled objects"),
3923 ))
3924 } else {
3925 assert!(!contains_cancelled);
3926 None
3927 }
3928 }
3929
3930 pub fn filter_owned_objects(&self) -> Vec<ObjectRef> {
3931 let owned_objects: Vec<_> = self
3932 .objects
3933 .iter()
3934 .filter_map(|obj| obj.get_owned_objref())
3935 .collect();
3936
3937 trace!(
3938 num_mutable_objects = owned_objects.len(),
3939 "Checked locks and found mutable objects"
3940 );
3941
3942 owned_objects
3943 }
3944
3945 pub fn filter_shared_objects(&self) -> Vec<SharedInput> {
3946 self.objects
3947 .iter()
3948 .filter(|obj| obj.is_shared_object())
3949 .map(|obj| {
3950 obj.to_shared_input()
3951 .expect("already filtered for shared objects")
3952 })
3953 .collect()
3954 }
3955
3956 pub fn transaction_dependencies(&self) -> BTreeSet<TransactionDigest> {
3957 self.objects
3958 .iter()
3959 .filter_map(|obj| obj.get_previous_transaction())
3960 .collect()
3961 }
3962
3963 pub fn exclusive_mutable_inputs(&self) -> BTreeMap<ObjectID, (VersionDigest, Owner)> {
3966 self.mutables_with_input_kinds()
3967 .filter_map(|(id, (version, owner, kind))| match kind {
3968 InputObjectKind::SharedMoveObject { mutability, .. } => match mutability {
3969 SharedObjectMutability::Mutable => Some((id, (version, owner))),
3970 SharedObjectMutability::Immutable => None,
3971 SharedObjectMutability::NonExclusiveWrite => None,
3972 },
3973 _ => Some((id, (version, owner))),
3974 })
3975 .collect()
3976 }
3977
3978 pub fn non_exclusive_input_objects(&self) -> BTreeMap<ObjectID, Object> {
3979 self.objects
3980 .iter()
3981 .filter_map(|read_result| {
3982 match (read_result.as_object(), read_result.input_object_kind) {
3983 (
3984 Some(object),
3985 InputObjectKind::SharedMoveObject {
3986 mutability: SharedObjectMutability::NonExclusiveWrite,
3987 ..
3988 },
3989 ) => Some((read_result.id(), object.clone())),
3990 _ => None,
3991 }
3992 })
3993 .collect()
3994 }
3995
3996 pub fn all_mutable_inputs(&self) -> BTreeMap<ObjectID, (VersionDigest, Owner)> {
3999 self.mutables_with_input_kinds()
4000 .filter_map(|(id, (version, owner, kind))| match kind {
4001 InputObjectKind::SharedMoveObject { mutability, .. } => match mutability {
4002 SharedObjectMutability::Mutable => Some((id, (version, owner))),
4003 SharedObjectMutability::Immutable => None,
4004 SharedObjectMutability::NonExclusiveWrite => Some((id, (version, owner))),
4005 },
4006 _ => Some((id, (version, owner))),
4007 })
4008 .collect()
4009 }
4010
4011 fn mutables_with_input_kinds(
4012 &self,
4013 ) -> impl Iterator<Item = (ObjectID, (VersionDigest, Owner, InputObjectKind))> + '_ {
4014 self.objects.iter().filter_map(
4015 |ObjectReadResult {
4016 input_object_kind,
4017 object,
4018 }| match (input_object_kind, object) {
4019 (InputObjectKind::MovePackage(_), _) => None,
4020 (
4021 InputObjectKind::ImmOrOwnedMoveObject(object_ref),
4022 ObjectReadResultKind::Object(object),
4023 ) => {
4024 if object.is_immutable() {
4025 None
4026 } else {
4027 Some((
4028 object_ref.0,
4029 (
4030 (object_ref.1, object_ref.2),
4031 object.owner.clone(),
4032 *input_object_kind,
4033 ),
4034 ))
4035 }
4036 }
4037 (
4038 InputObjectKind::ImmOrOwnedMoveObject(_),
4039 ObjectReadResultKind::ObjectConsensusStreamEnded(_, _),
4040 ) => {
4041 unreachable!()
4042 }
4043 (
4044 InputObjectKind::SharedMoveObject { .. },
4045 ObjectReadResultKind::ObjectConsensusStreamEnded(_, _),
4046 ) => None,
4047 (
4048 InputObjectKind::SharedMoveObject { mutability, .. },
4049 ObjectReadResultKind::Object(object),
4050 ) => match *mutability {
4051 SharedObjectMutability::Mutable => {
4052 let oref = object.compute_object_reference();
4053 Some((
4054 oref.0,
4055 ((oref.1, oref.2), object.owner.clone(), *input_object_kind),
4056 ))
4057 }
4058 SharedObjectMutability::Immutable => None,
4059 SharedObjectMutability::NonExclusiveWrite => {
4060 let oref = object.compute_object_reference();
4061 Some((
4062 oref.0,
4063 ((oref.1, oref.2), object.owner.clone(), *input_object_kind),
4064 ))
4065 }
4066 },
4067 (
4068 InputObjectKind::ImmOrOwnedMoveObject(_),
4069 ObjectReadResultKind::CancelledTransactionSharedObject(_),
4070 ) => {
4071 unreachable!()
4072 }
4073 (
4074 InputObjectKind::SharedMoveObject { .. },
4075 ObjectReadResultKind::CancelledTransactionSharedObject(_),
4076 ) => None,
4077 },
4078 )
4079 }
4080
4081 pub fn lamport_timestamp(&self, receiving_objects: &[ObjectRef]) -> SequenceNumber {
4085 let input_versions = self
4086 .objects
4087 .iter()
4088 .filter_map(|object| match &object.object {
4089 ObjectReadResultKind::Object(object) => {
4090 object.data.try_as_move().map(MoveObject::version)
4091 }
4092 ObjectReadResultKind::ObjectConsensusStreamEnded(v, _) => Some(*v),
4093 ObjectReadResultKind::CancelledTransactionSharedObject(_) => None,
4094 })
4095 .chain(receiving_objects.iter().map(|object_ref| object_ref.1));
4096
4097 SequenceNumber::lamport_increment(input_versions)
4098 }
4099
4100 pub fn object_kinds(&self) -> impl Iterator<Item = &InputObjectKind> {
4101 self.objects.iter().map(
4102 |ObjectReadResult {
4103 input_object_kind, ..
4104 }| input_object_kind,
4105 )
4106 }
4107
4108 pub fn consensus_stream_ended_objects(&self) -> BTreeMap<ObjectID, SequenceNumber> {
4109 self.objects
4110 .iter()
4111 .filter_map(|obj| {
4112 if let InputObjectKind::SharedMoveObject {
4113 id,
4114 initial_shared_version,
4115 ..
4116 } = obj.input_object_kind
4117 {
4118 obj.is_consensus_stream_ended()
4119 .then_some((id, initial_shared_version))
4120 } else {
4121 None
4122 }
4123 })
4124 .collect()
4125 }
4126
4127 pub fn into_object_map(self) -> BTreeMap<ObjectID, Object> {
4128 self.objects
4129 .into_iter()
4130 .filter_map(|o| o.as_object().map(|object| (o.id(), object.clone())))
4131 .collect()
4132 }
4133
4134 pub fn push(&mut self, object: ObjectReadResult) {
4135 self.objects.push(object);
4136 }
4137
4138 pub fn iter(&self) -> impl Iterator<Item = &ObjectReadResult> {
4139 self.objects.iter()
4140 }
4141
4142 pub fn iter_objects(&self) -> impl Iterator<Item = &Object> {
4143 self.objects.iter().filter_map(|o| o.as_object())
4144 }
4145
4146 pub fn non_exclusive_mutable_inputs(
4147 &self,
4148 ) -> impl Iterator<Item = (ObjectID, SequenceNumber)> + '_ {
4149 self.objects.iter().filter_map(
4150 |ObjectReadResult {
4151 input_object_kind,
4152 object,
4153 }| match input_object_kind {
4154 InputObjectKind::SharedMoveObject {
4158 id,
4159 mutability: SharedObjectMutability::NonExclusiveWrite,
4160 ..
4161 } if !object.is_cancelled() => Some((*id, object.version())),
4162 _ => None,
4163 },
4164 )
4165 }
4166}
4167
4168#[derive(Clone, Debug)]
4172pub enum ReceivingObjectReadResultKind {
4173 Object(Object),
4174 PreviouslyReceivedObject,
4176}
4177
4178impl ReceivingObjectReadResultKind {
4179 pub fn as_object(&self) -> Option<&Object> {
4180 match &self {
4181 Self::Object(object) => Some(object),
4182 Self::PreviouslyReceivedObject => None,
4183 }
4184 }
4185}
4186
4187pub struct ReceivingObjectReadResult {
4188 pub object_ref: ObjectRef,
4189 pub object: ReceivingObjectReadResultKind,
4190}
4191
4192impl ReceivingObjectReadResult {
4193 pub fn new(object_ref: ObjectRef, object: ReceivingObjectReadResultKind) -> Self {
4194 Self { object_ref, object }
4195 }
4196
4197 pub fn is_previously_received(&self) -> bool {
4198 matches!(
4199 self.object,
4200 ReceivingObjectReadResultKind::PreviouslyReceivedObject
4201 )
4202 }
4203}
4204
4205impl From<Object> for ReceivingObjectReadResultKind {
4206 fn from(object: Object) -> Self {
4207 Self::Object(object)
4208 }
4209}
4210
4211pub struct ReceivingObjects {
4212 pub objects: Vec<ReceivingObjectReadResult>,
4213}
4214
4215impl ReceivingObjects {
4216 pub fn iter(&self) -> impl Iterator<Item = &ReceivingObjectReadResult> {
4217 self.objects.iter()
4218 }
4219
4220 pub fn iter_objects(&self) -> impl Iterator<Item = &Object> {
4221 self.objects.iter().filter_map(|o| o.object.as_object())
4222 }
4223}
4224
4225impl From<Vec<ReceivingObjectReadResult>> for ReceivingObjects {
4226 fn from(objects: Vec<ReceivingObjectReadResult>) -> Self {
4227 Self { objects }
4228 }
4229}
4230
4231impl Display for CertifiedTransaction {
4232 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
4233 let mut writer = String::new();
4234 writeln!(writer, "Transaction Hash: {:?}", self.digest())?;
4235 writeln!(
4236 writer,
4237 "Signed Authorities Bitmap : {:?}",
4238 self.auth_sig().signers_map
4239 )?;
4240 write!(writer, "{}", &self.data().intent_message().value.kind())?;
4241 write!(f, "{}", writer)
4242 }
4243}
4244
4245#[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
4249pub enum TransactionKey {
4250 Digest(TransactionDigest),
4251 RandomnessRound(EpochId, RandomnessRound),
4252 AccumulatorSettlement(EpochId, u64 ),
4253}
4254
4255impl TransactionKey {
4256 pub fn unwrap_digest(&self) -> &TransactionDigest {
4257 match self {
4258 TransactionKey::Digest(d) => d,
4259 _ => panic!("called expect_digest on a non-Digest TransactionKey: {self:?}"),
4260 }
4261 }
4262
4263 pub fn as_digest(&self) -> Option<&TransactionDigest> {
4264 match self {
4265 TransactionKey::Digest(d) => Some(d),
4266 _ => None,
4267 }
4268 }
4269}