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