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