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