1use super::{SUI_BRIDGE_OBJECT_ID, base_types::*, error::*};
6use crate::accumulator_root::{AccumulatorObjId, AccumulatorValue, check_accumulator_type_bounds};
7use crate::allowance::{ResolvedAllowance, parse_allowance_object};
8use crate::authenticator_state::ActiveJwk;
9use crate::balance::{
10 BALANCE_MODULE_NAME, BALANCE_REDEEM_FUNDS_FUNCTION_NAME, BALANCE_SEND_FUNDS_FUNCTION_NAME,
11 BALANCE_SPLIT_FUNCTION_NAME, BALANCE_ZERO_FUNCTION_NAME, Balance,
12};
13use crate::coin::{
14 COIN_MODULE_NAME, INTO_BALANCE_FUNC_NAME, PUT_FUNC_NAME, REDEEM_FUNDS_FUNC_NAME,
15 SEND_FUNDS_FUNC_NAME,
16};
17use crate::coin_reservation::{
18 CoinReservationResolverTrait, ParsedDigest, ParsedObjectRefWithdrawal,
19};
20use crate::committee::{Committee, EpochId, ProtocolVersion};
21use crate::crypto::{
22 AuthoritySignInfo, AuthoritySignInfoTrait, AuthoritySignature, AuthorityStrongQuorumSignInfo,
23 DefaultHash, Ed25519SuiSignature, EmptySignInfo, RandomnessRound, Signature, Signer,
24 SuiSignatureInner, ToFromBytes, default_hash,
25};
26use crate::digests::{AdditionalConsensusStateDigest, SenderSignedDataDigest};
27use crate::digests::{ChainIdentifier, ConsensusCommitDigest};
28use crate::execution::{ExecutionTimeObservationKey, SharedInput};
29use crate::funds_accumulator::{FUNDS_ACCUMULATOR_MODULE_NAME, WITHDRAWAL_SPLIT_FUNC_NAME};
30use crate::gas_coin::GAS;
31use crate::gas_model::gas_predicates::check_for_gas_price_too_high;
32use crate::gas_model::gas_v2::SuiCostTable;
33use crate::message_envelope::{Envelope, Message, TrustedEnvelope, VerifiedEnvelope};
34use crate::messages_checkpoint::CheckpointTimestamp;
35use crate::messages_consensus::{
36 ConsensusCommitPrologue, ConsensusCommitPrologueV2, ConsensusCommitPrologueV3,
37 ConsensusCommitPrologueV4, ConsensusDeterminedVersionAssignments,
38};
39use crate::object::{MoveObject, Object, Owner};
40use crate::programmable_transaction_builder::ProgrammableTransactionBuilder;
41use crate::signature::{GenericSignature, VerifyParams};
42use crate::signature_verification::{
43 VerifiedDigestCache, verify_sender_signed_data_message_signatures,
44};
45use crate::type_input::TypeInput;
46use crate::{
47 SUI_ACCUMULATOR_ROOT_OBJECT_ID, SUI_AUTHENTICATOR_STATE_OBJECT_ID, SUI_CLOCK_OBJECT_ID,
48 SUI_CLOCK_OBJECT_SHARED_VERSION, SUI_FRAMEWORK_ADDRESS, SUI_FRAMEWORK_PACKAGE_ID,
49 SUI_RANDOMNESS_STATE_OBJECT_ID, SUI_SYSTEM_STATE_OBJECT_ID,
50 SUI_SYSTEM_STATE_OBJECT_SHARED_VERSION,
51};
52use enum_dispatch::enum_dispatch;
53use fastcrypto::{encoding::Base64, hash::HashFunction};
54use itertools::{Either, Itertools};
55use move_core_types::account_address::AccountAddress;
56use move_core_types::identifier::IdentStr;
57use move_core_types::{ident_str, identifier};
58use move_core_types::{identifier::Identifier, language_storage::TypeTag};
59use mysten_common::{ZipDebugEqIteratorExt, assert_reachable, debug_fatal};
60use nonempty::{NonEmpty, nonempty};
61use serde::{Deserialize, Serialize};
62use shared_crypto::intent::{Intent, IntentMessage, IntentScope};
63use std::collections::btree_map::Entry;
64use std::fmt::Write;
65use std::fmt::{Debug, Display, Formatter};
66use std::sync::Arc;
67use std::sync::RwLock;
68use std::time::Duration;
69use std::{
70 collections::{BTreeMap, BTreeSet, HashSet},
71 hash::Hash,
72 iter,
73};
74use strum::IntoStaticStr;
75use sui_protocol_config::{PerObjectCongestionControlMode, ProtocolConfig};
76use tap::Pipe;
77use tracing::trace;
78
79#[cfg(test)]
80#[path = "unit_tests/transaction_serialization_tests.rs"]
81mod transaction_serialization_tests;
82
83pub const TEST_ONLY_GAS_UNIT_FOR_TRANSFER: u64 = 10_000;
84pub const TEST_ONLY_GAS_UNIT_FOR_OBJECT_BASICS: u64 = 50_000;
85pub const TEST_ONLY_GAS_UNIT_FOR_PUBLISH: u64 = 70_000;
86pub const TEST_ONLY_GAS_UNIT_FOR_STAKING: u64 = 50_000;
87pub const TEST_ONLY_GAS_UNIT_FOR_GENERIC: u64 = 50_000;
88pub const TEST_ONLY_GAS_UNIT_FOR_SPLIT_COIN: u64 = 10_000;
89pub const TEST_ONLY_GAS_UNIT_FOR_HEAVY_COMPUTATION_STORAGE: u64 = 5_000_000;
94
95pub const GAS_PRICE_FOR_SYSTEM_TX: u64 = 1;
96
97pub const DEFAULT_VALIDATOR_GAS_PRICE: u64 = 1000;
98
99const BLOCKED_MOVE_FUNCTIONS: [(ObjectID, &str, &str); 0] = [];
100
101#[cfg(test)]
102#[path = "unit_tests/messages_tests.rs"]
103mod messages_tests;
104
105#[cfg(test)]
106#[path = "unit_tests/balance_withdraw_tests.rs"]
107mod balance_withdraw_tests;
108
109#[cfg(test)]
110#[path = "unit_tests/address_balance_gas_tests.rs"]
111mod address_balance_gas_tests;
112
113#[cfg(test)]
114#[path = "unit_tests/transaction_claims_tests.rs"]
115mod transaction_claims_tests;
116
117#[cfg(test)]
118#[path = "unit_tests/allowed_proposers_tests.rs"]
119mod allowed_proposers_tests;
120
121#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)]
122pub enum CallArg {
123 Pure(Vec<u8>),
125 Object(ObjectArg),
127 FundsWithdrawal(FundsWithdrawalArg),
131}
132
133impl CallArg {
134 pub const SUI_SYSTEM_MUT: Self = Self::Object(ObjectArg::SUI_SYSTEM_MUT);
135 pub const CLOCK_IMM: Self = Self::Object(ObjectArg::SharedObject {
136 id: SUI_CLOCK_OBJECT_ID,
137 initial_shared_version: SUI_CLOCK_OBJECT_SHARED_VERSION,
138 mutability: SharedObjectMutability::Immutable,
139 });
140 pub const CLOCK_MUT: Self = Self::Object(ObjectArg::SharedObject {
141 id: SUI_CLOCK_OBJECT_ID,
142 initial_shared_version: SUI_CLOCK_OBJECT_SHARED_VERSION,
143 mutability: SharedObjectMutability::Mutable,
144 });
145}
146
147#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy, Serialize, Deserialize)]
148pub enum ObjectArg {
149 ImmOrOwnedObject(ObjectRef),
151 SharedObject {
154 id: ObjectID,
155 initial_shared_version: SequenceNumber,
156 mutability: SharedObjectMutability,
159 },
160 Receiving(ObjectRef),
162}
163
164#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)]
165pub enum Reservation {
166 MaxAmountU64(u64),
168}
169
170#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)]
171pub enum WithdrawalTypeArg {
172 Balance(TypeTag),
173}
174
175impl WithdrawalTypeArg {
176 pub fn to_type_tag(&self) -> TypeTag {
179 let WithdrawalTypeArg::Balance(type_param) = self;
180 Balance::type_tag(type_param.clone())
181 }
182
183 pub fn get_balance_type_param(&self) -> Option<TypeTag> {
187 let WithdrawalTypeArg::Balance(type_param) = self;
188 Some(type_param.clone())
189 }
190}
191
192#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)]
194pub struct FundsWithdrawalArg {
195 pub reservation: Reservation,
197 pub type_arg: WithdrawalTypeArg,
199 pub withdraw_from: WithdrawFrom,
201}
202
203#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)]
204pub enum WithdrawFrom {
205 Sender,
207 Sponsor,
209 SenderAllowance {
211 funder: SuiAddress,
212 allowance: ObjectID,
213 },
214 }
216
217impl FundsWithdrawalArg {
218 pub fn balance_from_sender(amount: u64, balance_type: TypeTag) -> Self {
220 Self {
221 reservation: Reservation::MaxAmountU64(amount),
222 type_arg: WithdrawalTypeArg::Balance(balance_type),
223 withdraw_from: WithdrawFrom::Sender,
224 }
225 }
226
227 pub fn balance_from_sponsor(amount: u64, balance_type: TypeTag) -> Self {
229 Self {
230 reservation: Reservation::MaxAmountU64(amount),
231 type_arg: WithdrawalTypeArg::Balance(balance_type),
232 withdraw_from: WithdrawFrom::Sponsor,
233 }
234 }
235
236 pub fn balance_from_allowance(
239 amount: u64,
240 balance_type: TypeTag,
241 funder: SuiAddress,
242 allowance: ObjectID,
243 ) -> Self {
244 Self {
245 reservation: Reservation::MaxAmountU64(amount),
246 type_arg: WithdrawalTypeArg::Balance(balance_type),
247 withdraw_from: WithdrawFrom::SenderAllowance { funder, allowance },
248 }
249 }
250
251 pub fn owner_for_withdrawal(&self, tx: &impl TransactionDataAPI) -> SuiAddress {
253 match &self.withdraw_from {
254 WithdrawFrom::Sender => tx.sender(),
255 WithdrawFrom::Sponsor => tx.gas_owner(),
256 WithdrawFrom::SenderAllowance { funder, .. } => *funder,
257 }
258 }
259}
260
261fn type_input_validity_check(
262 tag: &TypeInput,
263 config: &ProtocolConfig,
264 starting_count: &mut usize,
265) -> UserInputResult<()> {
266 let mut stack = vec![(tag, 1)];
267 while let Some((tag, depth)) = stack.pop() {
268 *starting_count += 1;
269 fp_ensure!(
270 *starting_count < config.max_type_arguments() as usize,
271 UserInputError::SizeLimitExceeded {
272 limit: "maximum type arguments in a call transaction".to_string(),
273 value: config.max_type_arguments().to_string()
274 }
275 );
276 fp_ensure!(
277 depth < config.max_type_argument_depth(),
278 UserInputError::SizeLimitExceeded {
279 limit: "maximum type argument depth in a call transaction".to_string(),
280 value: config.max_type_argument_depth().to_string()
281 }
282 );
283 match tag {
284 TypeInput::Bool
285 | TypeInput::U8
286 | TypeInput::U64
287 | TypeInput::U128
288 | TypeInput::Address
289 | TypeInput::Signer
290 | TypeInput::U16
291 | TypeInput::U32
292 | TypeInput::U256 => (),
293 TypeInput::Vector(t) => {
294 stack.push((t, depth + 1));
295 }
296 TypeInput::Struct(s) => {
297 let next_depth = depth + 1;
298 if config.validate_identifier_inputs() {
299 fp_ensure!(
300 identifier::is_valid(&s.module),
301 UserInputError::InvalidIdentifier {
302 error: s.module.clone()
303 }
304 );
305 fp_ensure!(
306 identifier::is_valid(&s.name),
307 UserInputError::InvalidIdentifier {
308 error: s.name.clone()
309 }
310 );
311 }
312 stack.extend(s.type_params.iter().map(|t| (t, next_depth)));
313 }
314 }
315 }
316 Ok(())
317}
318
319#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)]
320pub struct ChangeEpoch {
321 pub epoch: EpochId,
323 pub protocol_version: ProtocolVersion,
325 pub storage_charge: u64,
327 pub computation_charge: u64,
329 pub storage_rebate: u64,
331 pub non_refundable_storage_fee: u64,
333 pub epoch_start_timestamp_ms: u64,
335 pub system_packages: Vec<(SequenceNumber, Vec<Vec<u8>>, Vec<ObjectID>)>,
341}
342
343#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)]
344pub struct GenesisTransaction {
345 pub objects: Vec<GenesisObject>,
346}
347
348#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)]
349pub enum GenesisObject {
350 RawObject {
351 data: crate::object::Data,
352 owner: crate::object::Owner,
353 },
354}
355
356impl GenesisObject {
357 pub fn id(&self) -> ObjectID {
358 match self {
359 GenesisObject::RawObject { data, .. } => data.id(),
360 }
361 }
362}
363
364#[derive(Debug, Hash, PartialEq, Eq, Clone, Serialize, Deserialize)]
365pub struct AuthenticatorStateExpire {
366 pub min_epoch: u64,
368 pub authenticator_obj_initial_shared_version: SequenceNumber,
370}
371
372impl AuthenticatorStateExpire {
373 pub fn authenticator_obj_initial_shared_version(&self) -> SequenceNumber {
374 self.authenticator_obj_initial_shared_version
375 }
376}
377
378#[derive(Debug, Hash, PartialEq, Eq, Clone, Serialize, Deserialize)]
379pub enum StoredExecutionTimeObservations {
380 V1(Vec<(ExecutionTimeObservationKey, Vec<(AuthorityName, Duration)>)>),
381}
382
383#[derive(Debug, Hash, PartialEq, Eq, Clone, Serialize, Deserialize)]
384pub struct WriteAccumulatorStorageCost {
385 pub storage_cost: u64,
387}
388
389impl StoredExecutionTimeObservations {
390 pub fn unwrap_v1(self) -> Vec<(ExecutionTimeObservationKey, Vec<(AuthorityName, Duration)>)> {
391 match self {
392 Self::V1(observations) => observations,
393 }
394 }
395
396 pub fn filter_and_sort_v1<P>(&self, predicate: P, limit: usize) -> Self
397 where
398 P: FnMut(&&(ExecutionTimeObservationKey, Vec<(AuthorityName, Duration)>)) -> bool,
399 {
400 match self {
401 Self::V1(observations) => Self::V1(
402 observations
403 .iter()
404 .filter(predicate)
405 .sorted_by_key(|(key, _)| key)
406 .take(limit)
407 .cloned()
408 .collect(),
409 ),
410 }
411 }
412
413 pub fn chunk_observations(&self, chunk_size: usize) -> Vec<Self> {
416 match self {
417 Self::V1(observations) => {
418 if chunk_size == 0 {
419 return vec![];
420 }
421 observations
422 .chunks(chunk_size)
423 .map(|chunk| Self::V1(chunk.to_vec()))
424 .collect()
425 }
426 }
427 }
428
429 pub fn merge_sorted_chunks(chunks: Vec<Self>) -> Self {
432 let mut all_observations = Vec::new();
433
434 for chunk in chunks {
435 match chunk {
436 Self::V1(observations) => {
437 all_observations.extend(observations);
438 }
439 }
440 }
441
442 Self::V1(all_observations)
443 }
444}
445
446#[derive(Debug, Hash, PartialEq, Eq, Clone, Serialize, Deserialize)]
447pub struct AuthenticatorStateUpdate {
448 pub epoch: u64,
450 pub round: u64,
452 pub new_active_jwks: Vec<ActiveJwk>,
454 pub authenticator_obj_initial_shared_version: SequenceNumber,
456 }
459
460impl AuthenticatorStateUpdate {
461 pub fn authenticator_obj_initial_shared_version(&self) -> SequenceNumber {
462 self.authenticator_obj_initial_shared_version
463 }
464}
465
466#[derive(Debug, Hash, PartialEq, Eq, Clone, Serialize, Deserialize)]
467pub struct RandomnessStateUpdate {
468 pub epoch: u64,
470 pub randomness_round: RandomnessRound,
472 pub random_bytes: Vec<u8>,
474 pub randomness_obj_initial_shared_version: SequenceNumber,
476 }
479
480impl RandomnessStateUpdate {
481 pub fn randomness_obj_initial_shared_version(&self) -> SequenceNumber {
482 self.randomness_obj_initial_shared_version
483 }
484}
485
486#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize, IntoStaticStr)]
487pub enum TransactionKind {
488 ProgrammableTransaction(ProgrammableTransaction),
490 ChangeEpoch(ChangeEpoch),
502 Genesis(GenesisTransaction),
503 ConsensusCommitPrologue(ConsensusCommitPrologue),
504 AuthenticatorStateUpdate(AuthenticatorStateUpdate),
505
506 EndOfEpochTransaction(Vec<EndOfEpochTransactionKind>),
509
510 RandomnessStateUpdate(RandomnessStateUpdate),
511 ConsensusCommitPrologueV2(ConsensusCommitPrologueV2),
513
514 ConsensusCommitPrologueV3(ConsensusCommitPrologueV3),
515 ConsensusCommitPrologueV4(ConsensusCommitPrologueV4),
516
517 ProgrammableSystemTransaction(ProgrammableTransaction),
519 }
521
522#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize, IntoStaticStr)]
524pub enum EndOfEpochTransactionKind {
525 ChangeEpoch(ChangeEpoch),
526 AuthenticatorStateCreate,
527 AuthenticatorStateExpire(AuthenticatorStateExpire),
528 RandomnessStateCreate,
529 DenyListStateCreate,
530 BridgeStateCreate(ChainIdentifier),
531 BridgeCommitteeInit(SequenceNumber),
532 StoreExecutionTimeObservations(StoredExecutionTimeObservations),
533 AccumulatorRootCreate,
534 CoinRegistryCreate,
535 DisplayRegistryCreate,
536 AddressAliasStateCreate,
537 WriteAccumulatorStorageCost(WriteAccumulatorStorageCost),
538 ForwardingAddressRegistryCreate,
539}
540
541impl EndOfEpochTransactionKind {
542 pub fn new_change_epoch(
543 next_epoch: EpochId,
544 protocol_version: ProtocolVersion,
545 storage_charge: u64,
546 computation_charge: u64,
547 storage_rebate: u64,
548 non_refundable_storage_fee: u64,
549 epoch_start_timestamp_ms: u64,
550 system_packages: Vec<(SequenceNumber, Vec<Vec<u8>>, Vec<ObjectID>)>,
551 ) -> Self {
552 Self::ChangeEpoch(ChangeEpoch {
553 epoch: next_epoch,
554 protocol_version,
555 storage_charge,
556 computation_charge,
557 storage_rebate,
558 non_refundable_storage_fee,
559 epoch_start_timestamp_ms,
560 system_packages,
561 })
562 }
563
564 pub fn new_authenticator_state_expire(
565 min_epoch: u64,
566 authenticator_obj_initial_shared_version: SequenceNumber,
567 ) -> Self {
568 Self::AuthenticatorStateExpire(AuthenticatorStateExpire {
569 min_epoch,
570 authenticator_obj_initial_shared_version,
571 })
572 }
573
574 pub fn new_authenticator_state_create() -> Self {
575 Self::AuthenticatorStateCreate
576 }
577
578 pub fn new_randomness_state_create() -> Self {
579 Self::RandomnessStateCreate
580 }
581
582 pub fn new_accumulator_root_create() -> Self {
583 Self::AccumulatorRootCreate
584 }
585
586 pub fn new_coin_registry_create() -> Self {
587 Self::CoinRegistryCreate
588 }
589
590 pub fn new_display_registry_create() -> Self {
591 Self::DisplayRegistryCreate
592 }
593
594 pub fn new_deny_list_state_create() -> Self {
595 Self::DenyListStateCreate
596 }
597
598 pub fn new_address_alias_state_create() -> Self {
599 Self::AddressAliasStateCreate
600 }
601
602 pub fn new_forwarding_address_registry_create() -> Self {
603 Self::ForwardingAddressRegistryCreate
604 }
605
606 pub fn new_bridge_create(chain_identifier: ChainIdentifier) -> Self {
607 Self::BridgeStateCreate(chain_identifier)
608 }
609
610 pub fn init_bridge_committee(bridge_shared_version: SequenceNumber) -> Self {
611 Self::BridgeCommitteeInit(bridge_shared_version)
612 }
613
614 pub fn new_store_execution_time_observations(
615 estimates: StoredExecutionTimeObservations,
616 ) -> Self {
617 Self::StoreExecutionTimeObservations(estimates)
618 }
619
620 pub fn new_write_accumulator_storage_cost(storage_cost: u64) -> Self {
621 Self::WriteAccumulatorStorageCost(WriteAccumulatorStorageCost { storage_cost })
622 }
623
624 fn input_objects(&self) -> Vec<InputObjectKind> {
625 match self {
626 Self::ChangeEpoch(_) => {
627 vec![InputObjectKind::SharedMoveObject {
628 id: SUI_SYSTEM_STATE_OBJECT_ID,
629 initial_shared_version: SUI_SYSTEM_STATE_OBJECT_SHARED_VERSION,
630 mutability: SharedObjectMutability::Mutable,
631 }]
632 }
633 Self::AuthenticatorStateCreate => vec![],
634 Self::AuthenticatorStateExpire(expire) => {
635 vec![InputObjectKind::SharedMoveObject {
636 id: SUI_AUTHENTICATOR_STATE_OBJECT_ID,
637 initial_shared_version: expire.authenticator_obj_initial_shared_version(),
638 mutability: SharedObjectMutability::Mutable,
639 }]
640 }
641 Self::RandomnessStateCreate => vec![],
642 Self::DenyListStateCreate => vec![],
643 Self::BridgeStateCreate(_) => vec![],
644 Self::BridgeCommitteeInit(bridge_version) => vec![
645 InputObjectKind::SharedMoveObject {
646 id: SUI_BRIDGE_OBJECT_ID,
647 initial_shared_version: *bridge_version,
648 mutability: SharedObjectMutability::Mutable,
649 },
650 InputObjectKind::SharedMoveObject {
651 id: SUI_SYSTEM_STATE_OBJECT_ID,
652 initial_shared_version: SUI_SYSTEM_STATE_OBJECT_SHARED_VERSION,
653 mutability: SharedObjectMutability::Mutable,
654 },
655 ],
656 Self::StoreExecutionTimeObservations(_) => {
657 vec![InputObjectKind::SharedMoveObject {
658 id: SUI_SYSTEM_STATE_OBJECT_ID,
659 initial_shared_version: SUI_SYSTEM_STATE_OBJECT_SHARED_VERSION,
660 mutability: SharedObjectMutability::Mutable,
661 }]
662 }
663 Self::AccumulatorRootCreate => vec![],
664 Self::CoinRegistryCreate => vec![],
665 Self::DisplayRegistryCreate => vec![],
666 Self::AddressAliasStateCreate => vec![],
667 Self::WriteAccumulatorStorageCost(_) => {
668 vec![InputObjectKind::SharedMoveObject {
669 id: SUI_SYSTEM_STATE_OBJECT_ID,
670 initial_shared_version: SUI_SYSTEM_STATE_OBJECT_SHARED_VERSION,
671 mutability: SharedObjectMutability::Mutable,
672 }]
673 }
674 Self::ForwardingAddressRegistryCreate => vec![],
675 }
676 }
677
678 fn shared_input_objects(&self) -> impl Iterator<Item = SharedInputObject> + '_ {
679 match self {
680 Self::ChangeEpoch(_) => {
681 Either::Left(vec![SharedInputObject::SUI_SYSTEM_OBJ].into_iter())
682 }
683 Self::AuthenticatorStateExpire(expire) => Either::Left(
684 vec![SharedInputObject {
685 id: SUI_AUTHENTICATOR_STATE_OBJECT_ID,
686 initial_shared_version: expire.authenticator_obj_initial_shared_version(),
687 mutability: SharedObjectMutability::Mutable,
688 }]
689 .into_iter(),
690 ),
691 Self::AuthenticatorStateCreate => Either::Right(iter::empty()),
692 Self::RandomnessStateCreate => Either::Right(iter::empty()),
693 Self::DenyListStateCreate => Either::Right(iter::empty()),
694 Self::BridgeStateCreate(_) => Either::Right(iter::empty()),
695 Self::BridgeCommitteeInit(bridge_version) => Either::Left(
696 vec![
697 SharedInputObject {
698 id: SUI_BRIDGE_OBJECT_ID,
699 initial_shared_version: *bridge_version,
700 mutability: SharedObjectMutability::Mutable,
701 },
702 SharedInputObject::SUI_SYSTEM_OBJ,
703 ]
704 .into_iter(),
705 ),
706 Self::StoreExecutionTimeObservations(_) => {
707 Either::Left(vec![SharedInputObject::SUI_SYSTEM_OBJ].into_iter())
708 }
709 Self::AccumulatorRootCreate => Either::Right(iter::empty()),
710 Self::CoinRegistryCreate => Either::Right(iter::empty()),
711 Self::DisplayRegistryCreate => Either::Right(iter::empty()),
712 Self::AddressAliasStateCreate => Either::Right(iter::empty()),
713 Self::WriteAccumulatorStorageCost(_) => {
714 Either::Left(vec![SharedInputObject::SUI_SYSTEM_OBJ].into_iter())
715 }
716 Self::ForwardingAddressRegistryCreate => Either::Right(iter::empty()),
717 }
718 }
719
720 fn validity_check(&self, config: &ProtocolConfig) -> UserInputResult {
721 match self {
722 Self::ChangeEpoch(_) => (),
723 Self::AuthenticatorStateCreate | Self::AuthenticatorStateExpire(_) => {
724 if !config.enable_jwk_consensus_updates() {
725 return Err(UserInputError::Unsupported(
726 "authenticator state updates not enabled".to_string(),
727 ));
728 }
729 }
730 Self::RandomnessStateCreate => {
731 if !config.random_beacon() {
732 return Err(UserInputError::Unsupported(
733 "random beacon not enabled".to_string(),
734 ));
735 }
736 }
737 Self::DenyListStateCreate => {
738 if !config.enable_coin_deny_list() {
739 return Err(UserInputError::Unsupported(
740 "coin deny list not enabled".to_string(),
741 ));
742 }
743 }
744 Self::BridgeStateCreate(_) => {
745 if !config.bridge() {
746 return Err(UserInputError::Unsupported(
747 "bridge not enabled".to_string(),
748 ));
749 }
750 }
751 Self::BridgeCommitteeInit(_) => {
752 if !config.bridge() {
753 return Err(UserInputError::Unsupported(
754 "bridge not enabled".to_string(),
755 ));
756 }
757 if !config.should_try_to_finalize_bridge_committee() {
758 return Err(UserInputError::Unsupported(
759 "should not try to finalize committee yet".to_string(),
760 ));
761 }
762 }
763 Self::StoreExecutionTimeObservations(_) => {
764 if !matches!(
765 config.per_object_congestion_control_mode(),
766 PerObjectCongestionControlMode::ExecutionTimeEstimate(_)
767 ) {
768 return Err(UserInputError::Unsupported(
769 "execution time estimation not enabled".to_string(),
770 ));
771 }
772 }
773 Self::AccumulatorRootCreate => {
774 if !config.create_root_accumulator_object() {
775 return Err(UserInputError::Unsupported(
776 "accumulators not enabled".to_string(),
777 ));
778 }
779 }
780 Self::CoinRegistryCreate => {
781 if !config.enable_coin_registry() {
782 return Err(UserInputError::Unsupported(
783 "coin registry not enabled".to_string(),
784 ));
785 }
786 }
787 Self::DisplayRegistryCreate => {
788 if !config.enable_display_registry() {
789 return Err(UserInputError::Unsupported(
790 "display registry not enabled".to_string(),
791 ));
792 }
793 }
794 Self::AddressAliasStateCreate => {
795 if !config.address_aliases() {
796 return Err(UserInputError::Unsupported(
797 "address aliases not enabled".to_string(),
798 ));
799 }
800 }
801 Self::WriteAccumulatorStorageCost(_) => {
802 if !config.enable_accumulators() {
803 return Err(UserInputError::Unsupported(
804 "accumulators not enabled".to_string(),
805 ));
806 }
807 }
808 Self::ForwardingAddressRegistryCreate => {
809 if !config.create_forwarding_address_registry() {
810 return Err(UserInputError::Unsupported(
811 "forwarding address registry not enabled".to_string(),
812 ));
813 }
814 }
815 }
816 Ok(())
817 }
818}
819
820impl CallArg {
821 fn input_objects(&self) -> Vec<InputObjectKind> {
822 match self {
823 CallArg::Pure(_) => vec![],
824 CallArg::Object(ObjectArg::ImmOrOwnedObject(object_ref)) => {
825 if ParsedDigest::is_coin_reservation_digest(&object_ref.2) {
826 vec![]
827 } else {
828 vec![InputObjectKind::ImmOrOwnedMoveObject(*object_ref)]
829 }
830 }
831 CallArg::Object(ObjectArg::SharedObject {
832 id,
833 initial_shared_version,
834 mutability,
835 }) => vec![InputObjectKind::SharedMoveObject {
836 id: *id,
837 initial_shared_version: *initial_shared_version,
838 mutability: *mutability,
839 }],
840 CallArg::Object(ObjectArg::Receiving(_)) => vec![],
842 CallArg::FundsWithdrawal(_) => vec![],
846 }
847 }
848
849 fn receiving_objects(&self) -> Vec<ObjectRef> {
850 match self {
851 CallArg::Pure(_) => vec![],
852 CallArg::Object(o) => match o {
853 ObjectArg::ImmOrOwnedObject(_) => vec![],
854 ObjectArg::SharedObject { .. } => vec![],
855 ObjectArg::Receiving(obj_ref) => vec![*obj_ref],
856 },
857 CallArg::FundsWithdrawal(_) => vec![],
858 }
859 }
860
861 pub fn validity_check(&self, config: &ProtocolConfig) -> UserInputResult {
862 match self {
863 CallArg::Pure(p) => {
864 fp_ensure!(
865 p.len() < config.max_pure_argument_size() as usize,
866 UserInputError::SizeLimitExceeded {
867 limit: "maximum pure argument size".to_string(),
868 value: config.max_pure_argument_size().to_string()
869 }
870 );
871 }
872 CallArg::Object(o) => match o {
873 ObjectArg::ImmOrOwnedObject(obj_ref)
874 if ParsedDigest::is_coin_reservation_digest(&obj_ref.2) =>
875 {
876 if !config.enable_coin_reservation_obj_refs() {
877 return Err(UserInputError::Unsupported(
878 "coin reservation backward compatibility layer is not enabled"
879 .to_string(),
880 ));
881 }
882 }
883 ObjectArg::ImmOrOwnedObject(_) => (),
884 ObjectArg::SharedObject { mutability, .. } => match mutability {
885 SharedObjectMutability::Mutable | SharedObjectMutability::Immutable => (),
886 SharedObjectMutability::NonExclusiveWrite => {
887 if !config.enable_non_exclusive_writes() {
888 return Err(UserInputError::Unsupported(
889 "User transactions cannot use SharedObjectMutability::NonExclusiveWrite".to_string(),
890 ));
891 }
892 }
893 },
894
895 ObjectArg::Receiving(_) => {
896 if !config.receive_objects() {
897 return Err(UserInputError::Unsupported(format!(
898 "receiving objects is not supported at {:?}",
899 config.version
900 )));
901 }
902 }
903 },
904 CallArg::FundsWithdrawal(w) => {
905 fp_ensure!(
906 check_accumulator_type_bounds(config, &w.type_arg.to_type_tag()),
907 UserInputError::SizeLimitExceeded {
908 limit: "maximum type nodes in a funds accumulator type".to_string(),
909 value: config.max_accumulator_type_nodes().to_string()
910 }
911 );
912 }
913 }
914 Ok(())
915 }
916}
917
918impl From<bool> for CallArg {
919 fn from(b: bool) -> Self {
920 CallArg::Pure(bcs::to_bytes(&b).unwrap())
922 }
923}
924
925impl From<u8> for CallArg {
926 fn from(n: u8) -> Self {
927 CallArg::Pure(bcs::to_bytes(&n).unwrap())
929 }
930}
931
932impl From<u16> for CallArg {
933 fn from(n: u16) -> Self {
934 CallArg::Pure(bcs::to_bytes(&n).unwrap())
936 }
937}
938
939impl From<u32> for CallArg {
940 fn from(n: u32) -> Self {
941 CallArg::Pure(bcs::to_bytes(&n).unwrap())
943 }
944}
945
946impl From<u64> for CallArg {
947 fn from(n: u64) -> Self {
948 CallArg::Pure(bcs::to_bytes(&n).unwrap())
950 }
951}
952
953impl From<u128> for CallArg {
954 fn from(n: u128) -> Self {
955 CallArg::Pure(bcs::to_bytes(&n).unwrap())
957 }
958}
959
960impl From<&Vec<u8>> for CallArg {
961 fn from(v: &Vec<u8>) -> Self {
962 CallArg::Pure(bcs::to_bytes(v).unwrap())
964 }
965}
966
967impl From<ObjectRef> for CallArg {
968 fn from(obj: ObjectRef) -> Self {
969 CallArg::Object(ObjectArg::ImmOrOwnedObject(obj))
970 }
971}
972
973impl ObjectArg {
974 pub const SUI_SYSTEM_MUT: Self = Self::SharedObject {
975 id: SUI_SYSTEM_STATE_OBJECT_ID,
976 initial_shared_version: SUI_SYSTEM_STATE_OBJECT_SHARED_VERSION,
977 mutability: SharedObjectMutability::Mutable,
978 };
979
980 pub fn id(&self) -> ObjectID {
981 match self {
982 ObjectArg::Receiving((id, _, _))
983 | ObjectArg::ImmOrOwnedObject((id, _, _))
984 | ObjectArg::SharedObject { id, .. } => *id,
985 }
986 }
987}
988
989fn add_type_input_packages(packages: &mut BTreeSet<ObjectID>, type_argument: &TypeInput) {
991 let mut stack = vec![type_argument];
992 while let Some(cur) = stack.pop() {
993 match cur {
994 TypeInput::Bool
995 | TypeInput::U8
996 | TypeInput::U64
997 | TypeInput::U128
998 | TypeInput::Address
999 | TypeInput::Signer
1000 | TypeInput::U16
1001 | TypeInput::U32
1002 | TypeInput::U256 => (),
1003 TypeInput::Vector(inner) => stack.push(inner),
1004 TypeInput::Struct(struct_tag) => {
1005 packages.insert(struct_tag.address.into());
1006 stack.extend(struct_tag.type_params.iter())
1007 }
1008 }
1009 }
1010}
1011
1012#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)]
1015pub struct ProgrammableTransaction {
1016 pub inputs: Vec<CallArg>,
1018 pub commands: Vec<Command>,
1021}
1022
1023#[cfg(feature = "testing")]
1024static GASLESS_TOKENS_FOR_TESTING: RwLock<Vec<(String, u64)>> = RwLock::new(Vec::new());
1025
1026#[cfg(feature = "testing")]
1027pub fn add_gasless_token_for_testing(type_string: String, min_transfer: u64) {
1028 GASLESS_TOKENS_FOR_TESTING
1029 .write()
1030 .unwrap()
1031 .push((type_string, min_transfer));
1032}
1033
1034#[cfg(feature = "testing")]
1035pub fn clear_gasless_tokens_for_testing() {
1036 GASLESS_TOKENS_FOR_TESTING.write().unwrap().clear();
1037}
1038
1039impl ProgrammableTransaction {
1040 pub fn validate_argument_indices(&self) -> UserInputResult {
1041 for (command_idx, command) in self.commands.iter().enumerate() {
1042 for (argument_idx, argument) in command.arguments().enumerate() {
1043 let index = match argument {
1044 Argument::Input(index) if *index as usize >= self.inputs.len() => *index,
1045 Argument::Result(index) | Argument::NestedResult(index, _)
1046 if *index as usize >= command_idx =>
1047 {
1048 *index
1049 }
1050 Argument::GasCoin
1051 | Argument::Input(_)
1052 | Argument::Result(_)
1053 | Argument::NestedResult(_, _) => continue,
1054 };
1055 return Err(UserInputError::InvalidArgumentIndex {
1056 command_idx,
1057 argument_idx,
1058 index,
1059 });
1060 }
1061 }
1062 Ok(())
1063 }
1064
1065 pub fn has_shared_inputs(&self) -> bool {
1066 self.inputs
1067 .iter()
1068 .any(|input| matches!(input, CallArg::Object(ObjectArg::SharedObject { .. })))
1069 }
1070
1071 pub fn validate_gasless_transaction(&self, config: &ProtocolConfig) -> UserInputResult {
1072 fp_ensure!(
1073 !self.commands.is_empty(),
1074 UserInputError::Unsupported(
1075 "Gasless transactions must have at least one command".to_string()
1076 )
1077 );
1078
1079 for input in &self.inputs {
1080 match input {
1081 CallArg::Pure(_) | CallArg::FundsWithdrawal(_) => {}
1082 CallArg::Object(
1083 ObjectArg::ImmOrOwnedObject(_) | ObjectArg::SharedObject { .. },
1084 ) => {}
1085 CallArg::Object(ObjectArg::Receiving(_)) => {
1086 return Err(UserInputError::Unsupported(
1087 "Gasless transactions do not support Receiving object inputs".to_string(),
1088 ));
1089 }
1090 }
1091 }
1092
1093 let allowed_token_types = get_gasless_allowed_token_types(config);
1094
1095 for command in &self.commands {
1096 command.validate_gasless_transaction(&allowed_token_types)?;
1097 }
1098
1099 self.validate_gasless_inputs(config)?;
1100
1101 Ok(())
1102 }
1103
1104 fn validate_gasless_inputs(&self, config: &ProtocolConfig) -> UserInputResult {
1105 let mut used_inputs = vec![false; self.inputs.len()];
1106 for idx in self.commands.iter().flat_map(|cmd| cmd.input_arguments()) {
1107 if let Some(slot) = used_inputs.get_mut(idx as usize) {
1108 *slot = true;
1109 }
1110 }
1111
1112 let max_unused_pure = config.get_gasless_max_unused_inputs();
1113 let max_pure_bytes = config.get_gasless_max_pure_input_bytes();
1114 let mut unused_pure_count = 0u64;
1115
1116 for (i, input) in self.inputs.iter().enumerate() {
1117 let is_used = used_inputs[i];
1118 match input {
1119 CallArg::Pure(bytes) => {
1120 fp_ensure!(
1121 bytes.len() as u64 <= max_pure_bytes,
1122 UserInputError::Unsupported(format!(
1123 "Input {} has size {} bytes, but gasless transactions \
1124 allow at most {} bytes per Pure input",
1125 i,
1126 bytes.len(),
1127 max_pure_bytes
1128 ))
1129 );
1130 if !is_used {
1131 unused_pure_count += 1;
1132 }
1133 }
1134 CallArg::Object(_) if !is_used => {
1135 return Err(UserInputError::Unsupported(format!(
1136 "Gasless transactions do not allow unused Object inputs (input {})",
1137 i
1138 )));
1139 }
1140 CallArg::FundsWithdrawal(_) if !is_used => {
1141 return Err(UserInputError::Unsupported(format!(
1142 "Gasless transactions do not allow unused FundsWithdrawal inputs (input {})",
1143 i
1144 )));
1145 }
1146 CallArg::Object(_) | CallArg::FundsWithdrawal(_) => {}
1147 }
1148 }
1149
1150 fp_ensure!(
1151 unused_pure_count <= max_unused_pure,
1152 UserInputError::Unsupported(format!(
1153 "Gasless transactions allow at most {} unused Pure inputs, but found {}",
1154 max_unused_pure, unused_pure_count
1155 ))
1156 );
1157
1158 Ok(())
1159 }
1160}
1161
1162pub fn get_gasless_allowed_token_types(config: &ProtocolConfig) -> Arc<BTreeMap<TypeTag, u64>> {
1164 #[allow(clippy::type_complexity)]
1165 static CACHE: RwLock<Option<(u64, Arc<BTreeMap<TypeTag, u64>>)>> = RwLock::new(None);
1166
1167 let version = config.version.as_u64();
1168
1169 if let Some((v, map)) = CACHE.read().unwrap().as_ref()
1171 && *v == version
1172 {
1173 return apply_test_token_overrides(Arc::clone(map));
1174 }
1175
1176 let mut cache = CACHE.write().unwrap();
1178 if let Some((v, map)) = cache.as_ref()
1179 && *v == version
1180 {
1181 return apply_test_token_overrides(Arc::clone(map));
1182 }
1183 let map: BTreeMap<TypeTag, u64> = config
1184 .gasless_allowed_token_types()
1185 .iter()
1186 .map(|(s, min_amount)| {
1187 let tag: TypeTag = s
1188 .parse()
1189 .unwrap_or_else(|e| panic!("invalid gasless token type {s:?}: {e}"));
1190 (tag, *min_amount)
1191 })
1192 .collect();
1193 let arc = Arc::new(map);
1194 *cache = Some((version, Arc::clone(&arc)));
1195 apply_test_token_overrides(arc)
1196}
1197
1198fn apply_test_token_overrides(base: Arc<BTreeMap<TypeTag, u64>>) -> Arc<BTreeMap<TypeTag, u64>> {
1199 #[cfg(feature = "testing")]
1200 {
1201 let overrides = GASLESS_TOKENS_FOR_TESTING.read().unwrap();
1202 if !overrides.is_empty() {
1203 let mut types = (*base).clone();
1204 for (s, min_transfer) in overrides.iter() {
1205 match s.parse() {
1206 Ok(tag) => {
1207 types.insert(tag, *min_transfer);
1208 }
1209 Err(e) => {
1210 debug_fatal!("invalid gasless token override {s:?}: {e}");
1211 }
1212 }
1213 }
1214 return Arc::new(types);
1215 }
1216 }
1217 base
1218}
1219
1220#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)]
1222pub enum Command {
1223 MoveCall(Box<ProgrammableMoveCall>),
1225 TransferObjects(Vec<Argument>, Argument),
1230 SplitCoins(Argument, Vec<Argument>),
1233 MergeCoins(Argument, Vec<Argument>),
1236 Publish(Vec<Vec<u8>>, Vec<ObjectID>),
1239 MakeMoveVec(Option<TypeInput>, Vec<Argument>),
1243 Upgrade(Vec<Vec<u8>>, Vec<ObjectID>, ObjectID, Argument),
1251}
1252
1253#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy, Serialize, Deserialize)]
1255pub enum Argument {
1256 GasCoin,
1259 Input(u16),
1262 Result(u16),
1264 NestedResult(u16, u16),
1267}
1268
1269#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)]
1272pub struct ProgrammableMoveCall {
1273 pub package: ObjectID,
1275 pub module: String,
1277 pub function: String,
1279 pub type_arguments: Vec<TypeInput>,
1281 pub arguments: Vec<Argument>,
1283}
1284
1285impl ProgrammableMoveCall {
1286 fn input_objects(&self) -> Vec<InputObjectKind> {
1287 let ProgrammableMoveCall {
1288 package,
1289 type_arguments,
1290 ..
1291 } = self;
1292 let mut packages = BTreeSet::from([*package]);
1293 for type_argument in type_arguments {
1294 add_type_input_packages(&mut packages, type_argument)
1295 }
1296 packages
1297 .into_iter()
1298 .map(InputObjectKind::MovePackage)
1299 .collect()
1300 }
1301
1302 pub fn validity_check(&self, config: &ProtocolConfig) -> UserInputResult {
1303 let is_blocked = BLOCKED_MOVE_FUNCTIONS.contains(&(
1304 self.package,
1305 self.module.as_str(),
1306 self.function.as_str(),
1307 ));
1308 fp_ensure!(!is_blocked, UserInputError::BlockedMoveFunction);
1309 let mut type_arguments_count = 0;
1310 for tag in &self.type_arguments {
1311 type_input_validity_check(tag, config, &mut type_arguments_count)?;
1312 }
1313 fp_ensure!(
1314 self.arguments.len() < config.max_arguments() as usize,
1315 UserInputError::SizeLimitExceeded {
1316 limit: "maximum arguments in a move call".to_string(),
1317 value: config.max_arguments().to_string()
1318 }
1319 );
1320 if config.validate_identifier_inputs() {
1321 fp_ensure!(
1322 identifier::is_valid(&self.module),
1323 UserInputError::InvalidIdentifier {
1324 error: self.module.clone()
1325 }
1326 );
1327 fp_ensure!(
1328 identifier::is_valid(&self.function),
1329 UserInputError::InvalidIdentifier {
1330 error: self.module.clone()
1331 }
1332 );
1333 }
1334 Ok(())
1335 }
1336
1337 fn validate_gasless_transaction(
1338 &self,
1339 allowed_token_types: &BTreeMap<TypeTag, u64>,
1340 ) -> UserInputResult {
1341 type FunctionIdent = (AccountAddress, &'static IdentStr, &'static IdentStr);
1342
1343 enum TypeArgConstraint {
1344 FundType,
1346 BalanceType,
1348 }
1349 use TypeArgConstraint::*;
1350
1351 const SUI_BALANCE_SEND_FUNDS: FunctionIdent = (
1352 SUI_FRAMEWORK_ADDRESS,
1353 BALANCE_MODULE_NAME,
1354 BALANCE_SEND_FUNDS_FUNCTION_NAME,
1355 );
1356 const SUI_BALANCE_REDEEM_FUNDS: FunctionIdent = (
1357 SUI_FRAMEWORK_ADDRESS,
1358 BALANCE_MODULE_NAME,
1359 BALANCE_REDEEM_FUNDS_FUNCTION_NAME,
1360 );
1361 const SUI_BALANCE_SPLIT: FunctionIdent = (
1362 SUI_FRAMEWORK_ADDRESS,
1363 BALANCE_MODULE_NAME,
1364 BALANCE_SPLIT_FUNCTION_NAME,
1365 );
1366 const SUI_BALANCE_ZERO: FunctionIdent = (
1367 SUI_FRAMEWORK_ADDRESS,
1368 BALANCE_MODULE_NAME,
1369 BALANCE_ZERO_FUNCTION_NAME,
1370 );
1371 const SUI_FUNDS_ACCUMULATOR_WITHDRAWAL_SPLIT: FunctionIdent = (
1372 SUI_FRAMEWORK_ADDRESS,
1373 FUNDS_ACCUMULATOR_MODULE_NAME,
1374 WITHDRAWAL_SPLIT_FUNC_NAME,
1375 );
1376 const SUI_COIN_INTO_BALANCE: FunctionIdent = (
1377 SUI_FRAMEWORK_ADDRESS,
1378 COIN_MODULE_NAME,
1379 INTO_BALANCE_FUNC_NAME,
1380 );
1381 const SUI_COIN_REDEEM_FUNDS: FunctionIdent = (
1382 SUI_FRAMEWORK_ADDRESS,
1383 COIN_MODULE_NAME,
1384 REDEEM_FUNDS_FUNC_NAME,
1385 );
1386 const SUI_COIN_SEND_FUNDS: FunctionIdent = (
1387 SUI_FRAMEWORK_ADDRESS,
1388 COIN_MODULE_NAME,
1389 SEND_FUNDS_FUNC_NAME,
1390 );
1391 const SUI_COIN_PUT: FunctionIdent =
1392 (SUI_FRAMEWORK_ADDRESS, COIN_MODULE_NAME, PUT_FUNC_NAME);
1393
1394 const GASLESS_FUNCTIONS: &[(FunctionIdent, &[Option<TypeArgConstraint>])] = &[
1395 (SUI_BALANCE_SEND_FUNDS, &[Some(FundType)]),
1396 (SUI_BALANCE_REDEEM_FUNDS, &[Some(FundType)]),
1397 (SUI_BALANCE_SPLIT, &[Some(FundType)]),
1398 (SUI_BALANCE_ZERO, &[Some(FundType)]),
1399 (SUI_FUNDS_ACCUMULATOR_WITHDRAWAL_SPLIT, &[Some(BalanceType)]),
1400 (SUI_COIN_INTO_BALANCE, &[Some(FundType)]),
1401 (SUI_COIN_REDEEM_FUNDS, &[Some(FundType)]),
1402 (SUI_COIN_SEND_FUNDS, &[Some(FundType)]),
1403 (SUI_COIN_PUT, &[Some(FundType)]),
1404 ];
1405
1406 let Some((_, type_arg_constraints)) =
1407 GASLESS_FUNCTIONS
1408 .iter()
1409 .find(|((addr, module, function), _)| {
1410 *addr == AccountAddress::from(self.package)
1411 && module.as_str() == self.module
1412 && function.as_str() == self.function
1413 })
1414 else {
1415 return Err(UserInputError::Unsupported(format!(
1416 "Function {}::{}::{} is not supported in gasless transactions",
1417 self.package, self.module, self.function
1418 )));
1419 };
1420
1421 fp_ensure!(
1422 type_arg_constraints.len() == self.type_arguments.len(),
1423 UserInputError::Unsupported(format!(
1424 "Function {}::{}::{} requires {} type arguments, but {} were provided",
1425 self.package,
1426 self.module,
1427 self.function,
1428 type_arg_constraints.len(),
1429 self.type_arguments.len()
1430 ))
1431 );
1432
1433 for (type_arg_constraint, type_input) in type_arg_constraints
1434 .iter()
1435 .zip_debug_eq(&self.type_arguments)
1436 {
1437 let Some(type_arg_constraint) = type_arg_constraint else {
1438 continue;
1439 };
1440 let type_arg = type_input.to_type_tag().map_err(|e| {
1441 UserInputError::Unsupported(format!(
1442 "Failed to parse type argument {type_input} as a type tag: {e}"
1443 ))
1444 })?;
1445 let fund_type = match type_arg_constraint {
1446 TypeArgConstraint::FundType => type_arg,
1447 TypeArgConstraint::BalanceType => Balance::maybe_get_balance_type_param(&type_arg)
1448 .ok_or_else(|| {
1449 UserInputError::Unsupported(format!(
1450 "Expected a type Balance<_> but got {type_input}",
1451 ))
1452 })?,
1453 };
1454 fp_ensure!(
1455 allowed_token_types.contains_key(&fund_type),
1456 UserInputError::Unsupported(format!(
1457 "Fund type {fund_type} is not currently allowed in gasless transactions"
1458 ))
1459 );
1460 }
1461 Ok(())
1462 }
1463}
1464
1465impl Command {
1466 pub fn move_call(
1467 package: ObjectID,
1468 module: Identifier,
1469 function: Identifier,
1470 type_arguments: Vec<TypeTag>,
1471 arguments: Vec<Argument>,
1472 ) -> Self {
1473 let module = module.to_string();
1474 let function = function.to_string();
1475 let type_arguments = type_arguments.into_iter().map(TypeInput::from).collect();
1476 Command::MoveCall(Box::new(ProgrammableMoveCall {
1477 package,
1478 module,
1479 function,
1480 type_arguments,
1481 arguments,
1482 }))
1483 }
1484
1485 pub fn make_move_vec(ty: Option<TypeTag>, args: Vec<Argument>) -> Self {
1486 Command::MakeMoveVec(ty.map(TypeInput::from), args)
1487 }
1488
1489 fn input_objects(&self) -> Vec<InputObjectKind> {
1490 match self {
1491 Command::Upgrade(_, deps, package_id, _) => deps
1492 .iter()
1493 .map(|id| InputObjectKind::MovePackage(*id))
1494 .chain(Some(InputObjectKind::MovePackage(*package_id)))
1495 .collect(),
1496 Command::Publish(_, deps) => deps
1497 .iter()
1498 .map(|id| InputObjectKind::MovePackage(*id))
1499 .collect(),
1500 Command::MoveCall(c) => c.input_objects(),
1501 Command::MakeMoveVec(Some(t), _) => {
1502 let mut packages = BTreeSet::new();
1503 add_type_input_packages(&mut packages, t);
1504 packages
1505 .into_iter()
1506 .map(InputObjectKind::MovePackage)
1507 .collect()
1508 }
1509 Command::MakeMoveVec(None, _)
1510 | Command::TransferObjects(_, _)
1511 | Command::SplitCoins(_, _)
1512 | Command::MergeCoins(_, _) => vec![],
1513 }
1514 }
1515
1516 fn non_system_packages_to_be_published(&self) -> Option<&Vec<Vec<u8>>> {
1517 match self {
1518 Command::Upgrade(v, _, _, _) => Some(v),
1519 Command::Publish(v, _) => Some(v),
1520 Command::MoveCall(_)
1521 | Command::TransferObjects(_, _)
1522 | Command::SplitCoins(_, _)
1523 | Command::MergeCoins(_, _)
1524 | Command::MakeMoveVec(_, _) => None,
1525 }
1526 }
1527
1528 fn validity_check(&self, config: &ProtocolConfig) -> UserInputResult {
1529 match self {
1530 Command::MoveCall(call) => call.validity_check(config)?,
1531 Command::TransferObjects(args, _)
1532 | Command::MergeCoins(_, args)
1533 | Command::SplitCoins(_, args) => {
1534 fp_ensure!(!args.is_empty(), UserInputError::EmptyCommandInput);
1535 fp_ensure!(
1536 args.len() < config.max_arguments() as usize,
1537 UserInputError::SizeLimitExceeded {
1538 limit: "maximum arguments in a programmable transaction command"
1539 .to_string(),
1540 value: config.max_arguments().to_string()
1541 }
1542 );
1543 }
1544 Command::MakeMoveVec(ty_opt, args) => {
1545 fp_ensure!(
1547 ty_opt.is_some() || !args.is_empty(),
1548 UserInputError::EmptyCommandInput
1549 );
1550 if let Some(ty) = ty_opt {
1551 let mut type_arguments_count = 0;
1552 type_input_validity_check(ty, config, &mut type_arguments_count)?;
1553 }
1554 fp_ensure!(
1555 args.len() < config.max_arguments() as usize,
1556 UserInputError::SizeLimitExceeded {
1557 limit: "maximum arguments in a programmable transaction command"
1558 .to_string(),
1559 value: config.max_arguments().to_string()
1560 }
1561 );
1562 }
1563 Command::Publish(modules, deps) | Command::Upgrade(modules, deps, _, _) => {
1564 fp_ensure!(!modules.is_empty(), UserInputError::EmptyCommandInput);
1565 fp_ensure!(
1566 modules.len() < config.max_modules_in_publish() as usize,
1567 UserInputError::SizeLimitExceeded {
1568 limit: "maximum modules in a programmable transaction upgrade command"
1569 .to_string(),
1570 value: config.max_modules_in_publish().to_string()
1571 }
1572 );
1573 if let Some(max_package_dependencies) = config.max_package_dependencies_as_option()
1574 {
1575 fp_ensure!(
1576 deps.len() < max_package_dependencies as usize,
1577 UserInputError::SizeLimitExceeded {
1578 limit: "maximum package dependencies".to_string(),
1579 value: max_package_dependencies.to_string()
1580 }
1581 );
1582 };
1583 }
1584 };
1585 Ok(())
1586 }
1587
1588 fn validate_gasless_transaction(
1589 &self,
1590 allowed_token_types: &BTreeMap<TypeTag, u64>,
1591 ) -> UserInputResult {
1592 match self {
1593 Command::MoveCall(call) => call.validate_gasless_transaction(allowed_token_types),
1594 Command::MergeCoins(_, _) | Command::SplitCoins(_, _) => Ok(()),
1595 _ => Err(UserInputError::Unsupported(
1596 "Gasless transactions only support MoveCall, MergeCoins, and SplitCoins commands"
1597 .to_string(),
1598 )),
1599 }
1600 }
1601
1602 fn is_input_arg_used(&self, input_arg: u16) -> bool {
1603 self.is_argument_used(Argument::Input(input_arg))
1604 }
1605
1606 pub fn is_gas_coin_used(&self) -> bool {
1607 self.is_argument_used(Argument::GasCoin)
1608 }
1609
1610 pub fn is_argument_used(&self, argument: Argument) -> bool {
1611 self.arguments().any(|a| a == &argument)
1612 }
1613
1614 fn input_arguments(&self) -> impl Iterator<Item = u16> + '_ {
1615 self.arguments().filter_map(|arg| match arg {
1616 Argument::Input(i) => Some(*i),
1617 _ => None,
1618 })
1619 }
1620
1621 pub(crate) fn arguments(&self) -> Box<dyn Iterator<Item = &Argument> + '_> {
1623 match self {
1624 Command::MoveCall(c) => Box::new(c.arguments.iter()),
1625 Command::TransferObjects(args, recipient) => {
1626 Box::new(args.iter().chain(std::iter::once(recipient)))
1627 }
1628 Command::SplitCoins(coin, amounts) | Command::MergeCoins(coin, amounts) => {
1629 Box::new(std::iter::once(coin).chain(amounts))
1630 }
1631 Command::MakeMoveVec(_, args) => Box::new(args.iter()),
1632 Command::Upgrade(_, _, _, arg) => Box::new(std::iter::once(arg)),
1633 Command::Publish(_, _) => Box::new(std::iter::empty()),
1634 }
1635 }
1636}
1637
1638pub fn write_sep<T: Display>(
1639 f: &mut Formatter<'_>,
1640 items: impl IntoIterator<Item = T>,
1641 sep: &str,
1642) -> std::fmt::Result {
1643 let mut xs = items.into_iter();
1644 let Some(x) = xs.next() else {
1645 return Ok(());
1646 };
1647 write!(f, "{x}")?;
1648 for x in xs {
1649 write!(f, "{sep}{x}")?;
1650 }
1651 Ok(())
1652}
1653
1654impl ProgrammableTransaction {
1655 pub fn input_objects(&self) -> UserInputResult<Vec<InputObjectKind>> {
1656 let ProgrammableTransaction { inputs, commands } = self;
1657 let input_arg_objects = inputs
1658 .iter()
1659 .flat_map(|arg| arg.input_objects())
1660 .collect::<Vec<_>>();
1661 let mut used = HashSet::new();
1663 if !input_arg_objects.iter().all(|o| used.insert(o.object_id())) {
1664 return Err(UserInputError::DuplicateObjectRefInput);
1665 }
1666 let command_input_objects: BTreeSet<InputObjectKind> = commands
1668 .iter()
1669 .flat_map(|command| command.input_objects())
1670 .collect();
1671 Ok(input_arg_objects
1672 .into_iter()
1673 .chain(command_input_objects)
1674 .collect())
1675 }
1676
1677 fn receiving_objects(&self) -> Vec<ObjectRef> {
1678 let ProgrammableTransaction { inputs, .. } = self;
1679 inputs
1680 .iter()
1681 .flat_map(|arg| arg.receiving_objects())
1682 .collect()
1683 }
1684
1685 fn validity_check(&self, config: &ProtocolConfig) -> UserInputResult {
1686 let ProgrammableTransaction { inputs, commands } = self;
1687 fp_ensure!(
1688 commands.len() < config.max_programmable_tx_commands() as usize,
1689 UserInputError::SizeLimitExceeded {
1690 limit: "maximum commands in a programmable transaction".to_string(),
1691 value: config.max_programmable_tx_commands().to_string()
1692 }
1693 );
1694 let total_inputs = self.input_objects()?.len() + self.receiving_objects().len();
1695 fp_ensure!(
1696 total_inputs <= config.max_input_objects() as usize,
1697 UserInputError::SizeLimitExceeded {
1698 limit: "maximum input + receiving objects in a transaction".to_string(),
1699 value: config.max_input_objects().to_string()
1700 }
1701 );
1702 fp_ensure!(
1703 inputs.len() <= u16::MAX as usize,
1704 UserInputError::SizeLimitExceeded {
1705 limit: "maximum inputs in a programmable transaction".to_string(),
1706 value: u16::MAX.to_string()
1707 }
1708 );
1709 for input in inputs {
1710 input.validity_check(config)?
1711 }
1712 if let Some(max_publish_commands) = config.max_publish_or_upgrade_per_ptb_as_option() {
1713 let publish_count = commands
1714 .iter()
1715 .filter(|c| matches!(c, Command::Publish(_, _) | Command::Upgrade(_, _, _, _)))
1716 .count() as u64;
1717 fp_ensure!(
1718 publish_count <= max_publish_commands,
1719 UserInputError::MaxPublishCountExceeded {
1720 max_publish_commands,
1721 publish_count,
1722 }
1723 );
1724 }
1725 for command in commands {
1726 command.validity_check(config)?;
1727 }
1728 if config.validate_ptb_argument_indices() {
1729 self.validate_argument_indices()?;
1730 }
1731
1732 if let Some(random_index) = inputs.iter().position(|obj| {
1735 matches!(
1736 obj,
1737 CallArg::Object(ObjectArg::SharedObject { id, .. }) if *id == SUI_RANDOMNESS_STATE_OBJECT_ID
1738 )
1739 }) {
1740 fp_ensure!(
1741 config.random_beacon(),
1742 UserInputError::Unsupported(
1743 "randomness is not enabled on this network".to_string(),
1744 )
1745 );
1746 let mut used_random_object = false;
1747 let random_index = random_index.try_into().unwrap();
1748 for command in commands {
1749 if !used_random_object {
1750 used_random_object = command.is_input_arg_used(random_index);
1751 } else {
1752 fp_ensure!(
1753 matches!(
1754 command,
1755 Command::TransferObjects(_, _) | Command::MergeCoins(_, _)
1756 ),
1757 UserInputError::PostRandomCommandRestrictions
1758 );
1759 }
1760 }
1761 }
1762
1763 Ok(())
1764 }
1765
1766 pub fn coin_reservation_obj_refs(&self) -> impl Iterator<Item = ObjectRef> + '_ {
1768 self.inputs.iter().filter_map(|arg| match arg {
1769 CallArg::Object(ObjectArg::ImmOrOwnedObject(obj_ref))
1770 if ParsedDigest::is_coin_reservation_digest(&obj_ref.2) =>
1771 {
1772 Some(*obj_ref)
1773 }
1774 _ => None,
1775 })
1776 }
1777
1778 pub fn shared_input_objects(&self) -> impl Iterator<Item = SharedInputObject> + '_ {
1779 self.inputs.iter().filter_map(|arg| match arg {
1780 CallArg::Pure(_)
1781 | CallArg::Object(ObjectArg::Receiving(_))
1782 | CallArg::Object(ObjectArg::ImmOrOwnedObject(_))
1783 | CallArg::FundsWithdrawal(_) => None,
1784 CallArg::Object(ObjectArg::SharedObject {
1785 id,
1786 initial_shared_version,
1787 mutability,
1788 }) => Some(SharedInputObject {
1789 id: *id,
1790 initial_shared_version: *initial_shared_version,
1791 mutability: *mutability,
1792 }),
1793 })
1794 }
1795
1796 fn move_calls(&self) -> Vec<(usize, &ObjectID, &str, &str)> {
1797 self.commands
1798 .iter()
1799 .enumerate()
1800 .filter_map(|(idx, command)| match command {
1801 Command::MoveCall(m) => {
1802 Some((idx, &m.package, m.module.as_str(), m.function.as_str()))
1803 }
1804 _ => None,
1805 })
1806 .collect()
1807 }
1808
1809 pub fn non_system_packages_to_be_published(&self) -> impl Iterator<Item = &Vec<Vec<u8>>> + '_ {
1810 self.commands
1811 .iter()
1812 .filter_map(|q| q.non_system_packages_to_be_published())
1813 }
1814}
1815
1816impl Display for Argument {
1817 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
1818 match self {
1819 Argument::GasCoin => write!(f, "GasCoin"),
1820 Argument::Input(i) => write!(f, "Input({i})"),
1821 Argument::Result(i) => write!(f, "Result({i})"),
1822 Argument::NestedResult(i, j) => write!(f, "NestedResult({i},{j})"),
1823 }
1824 }
1825}
1826
1827impl Display for ProgrammableMoveCall {
1828 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
1829 let ProgrammableMoveCall {
1830 package,
1831 module,
1832 function,
1833 type_arguments,
1834 arguments,
1835 } = self;
1836 write!(f, "{package}::{module}::{function}")?;
1837 if !type_arguments.is_empty() {
1838 write!(f, "<")?;
1839 write_sep(f, type_arguments, ",")?;
1840 write!(f, ">")?;
1841 }
1842 write!(f, "(")?;
1843 write_sep(f, arguments, ",")?;
1844 write!(f, ")")
1845 }
1846}
1847
1848impl Display for Command {
1849 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
1850 match self {
1851 Command::MoveCall(p) => {
1852 write!(f, "MoveCall({p})")
1853 }
1854 Command::MakeMoveVec(ty_opt, elems) => {
1855 write!(f, "MakeMoveVec(")?;
1856 if let Some(ty) = ty_opt {
1857 write!(f, "Some{ty}")?;
1858 } else {
1859 write!(f, "None")?;
1860 }
1861 write!(f, ",[")?;
1862 write_sep(f, elems, ",")?;
1863 write!(f, "])")
1864 }
1865 Command::TransferObjects(objs, addr) => {
1866 write!(f, "TransferObjects([")?;
1867 write_sep(f, objs, ",")?;
1868 write!(f, "],{addr})")
1869 }
1870 Command::SplitCoins(coin, amounts) => {
1871 write!(f, "SplitCoins({coin}")?;
1872 write_sep(f, amounts, ",")?;
1873 write!(f, ")")
1874 }
1875 Command::MergeCoins(target, coins) => {
1876 write!(f, "MergeCoins({target},")?;
1877 write_sep(f, coins, ",")?;
1878 write!(f, ")")
1879 }
1880 Command::Publish(_bytes, deps) => {
1881 write!(f, "Publish(_,")?;
1882 write_sep(f, deps, ",")?;
1883 write!(f, ")")
1884 }
1885 Command::Upgrade(_bytes, deps, current_package_id, ticket) => {
1886 write!(f, "Upgrade(_,")?;
1887 write_sep(f, deps, ",")?;
1888 write!(f, ", {current_package_id}")?;
1889 write!(f, ", {ticket}")?;
1890 write!(f, ")")
1891 }
1892 }
1893 }
1894}
1895
1896impl Display for ProgrammableTransaction {
1897 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
1898 let ProgrammableTransaction { inputs, commands } = self;
1899 writeln!(f, "Inputs: {inputs:?}")?;
1900 writeln!(f, "Commands: [")?;
1901 for c in commands {
1902 writeln!(f, " {c},")?;
1903 }
1904 writeln!(f, "]")
1905 }
1906}
1907
1908#[derive(Debug, PartialEq, Eq)]
1909pub struct SharedInputObject {
1910 pub id: ObjectID,
1911 pub initial_shared_version: SequenceNumber,
1912 pub mutability: SharedObjectMutability,
1913}
1914
1915impl SharedInputObject {
1916 pub const SUI_SYSTEM_OBJ: Self = Self {
1917 id: SUI_SYSTEM_STATE_OBJECT_ID,
1918 initial_shared_version: SUI_SYSTEM_STATE_OBJECT_SHARED_VERSION,
1919 mutability: SharedObjectMutability::Mutable,
1920 };
1921
1922 pub fn id(&self) -> ObjectID {
1923 self.id
1924 }
1925
1926 pub fn id_and_version(&self) -> (ObjectID, SequenceNumber) {
1927 (self.id, self.initial_shared_version)
1928 }
1929
1930 pub fn into_id_and_version(self) -> (ObjectID, SequenceNumber) {
1931 (self.id, self.initial_shared_version)
1932 }
1933
1934 pub fn is_accessed_exclusively(&self) -> bool {
1935 self.mutability.is_exclusive()
1936 }
1937
1938 pub fn may_mutate(&self) -> bool {
1940 match self.mutability {
1941 SharedObjectMutability::Immutable => false,
1942 SharedObjectMutability::Mutable | SharedObjectMutability::NonExclusiveWrite => true,
1943 }
1944 }
1945}
1946
1947impl TransactionKind {
1948 pub fn programmable(pt: ProgrammableTransaction) -> Self {
1951 TransactionKind::ProgrammableTransaction(pt)
1952 }
1953
1954 pub fn is_system_tx(&self) -> bool {
1955 match self {
1957 TransactionKind::ChangeEpoch(_)
1958 | TransactionKind::Genesis(_)
1959 | TransactionKind::ConsensusCommitPrologue(_)
1960 | TransactionKind::ConsensusCommitPrologueV2(_)
1961 | TransactionKind::ConsensusCommitPrologueV3(_)
1962 | TransactionKind::ConsensusCommitPrologueV4(_)
1963 | TransactionKind::AuthenticatorStateUpdate(_)
1964 | TransactionKind::RandomnessStateUpdate(_)
1965 | TransactionKind::EndOfEpochTransaction(_)
1966 | TransactionKind::ProgrammableSystemTransaction(_) => true,
1967 TransactionKind::ProgrammableTransaction(_) => false,
1968 }
1969 }
1970
1971 pub fn is_end_of_epoch_tx(&self) -> bool {
1972 matches!(
1973 self,
1974 TransactionKind::EndOfEpochTransaction(_) | TransactionKind::ChangeEpoch(_)
1975 )
1976 }
1977
1978 pub fn is_accumulator_barrier_settle_tx(&self) -> bool {
1979 matches!(self, TransactionKind::ProgrammableSystemTransaction(_))
1980 && self.shared_input_objects().any(|obj| {
1981 obj.id == SUI_ACCUMULATOR_ROOT_OBJECT_ID
1982 && obj.mutability == SharedObjectMutability::Mutable
1983 })
1984 }
1985
1986 pub fn is_accumulator_settle_tx(&self) -> bool {
1989 if let TransactionKind::ProgrammableSystemTransaction(pt) = self {
1990 pt.shared_input_objects()
1991 .any(|obj| obj.id == SUI_ACCUMULATOR_ROOT_OBJECT_ID)
1992 } else {
1993 false
1994 }
1995 }
1996
1997 pub fn accumulator_barrier_settlement_key(&self) -> Option<TransactionKey> {
2001 let TransactionKind::ProgrammableSystemTransaction(pt) = self else {
2002 return None;
2003 };
2004 let has_mutable_acc_root = pt.inputs.iter().any(|input| {
2005 matches!(
2006 input,
2007 CallArg::Object(ObjectArg::SharedObject {
2008 id,
2009 mutability: SharedObjectMutability::Mutable,
2010 ..
2011 }) if *id == SUI_ACCUMULATOR_ROOT_OBJECT_ID
2012 )
2013 });
2014 if !has_mutable_acc_root {
2015 return None;
2016 }
2017 let epoch = pt.inputs.get(1).and_then(|arg| match arg {
2020 CallArg::Pure(bytes) => bcs::from_bytes::<u64>(bytes).ok(),
2021 _ => None,
2022 })?;
2023 let checkpoint_height = pt.inputs.get(2).and_then(|arg| match arg {
2024 CallArg::Pure(bytes) => bcs::from_bytes::<u64>(bytes).ok(),
2025 _ => None,
2026 })?;
2027 Some(TransactionKey::AccumulatorSettlement(
2028 epoch,
2029 checkpoint_height,
2030 ))
2031 }
2032
2033 pub fn get_advance_epoch_tx_gas_summary(&self) -> Option<(u64, u64)> {
2037 let e = match self {
2038 Self::ChangeEpoch(e) => e,
2039 Self::EndOfEpochTransaction(txns) => {
2040 if let EndOfEpochTransactionKind::ChangeEpoch(e) =
2041 txns.last().expect("at least one end-of-epoch txn required")
2042 {
2043 e
2044 } else {
2045 panic!("final end-of-epoch txn must be ChangeEpoch")
2046 }
2047 }
2048 _ => return None,
2049 };
2050
2051 Some((e.computation_charge + e.storage_charge, e.storage_rebate))
2052 }
2053
2054 pub fn shared_input_objects(&self) -> impl Iterator<Item = SharedInputObject> + '_ {
2057 match &self {
2058 Self::ChangeEpoch(_) => {
2059 Either::Left(Either::Left(iter::once(SharedInputObject::SUI_SYSTEM_OBJ)))
2060 }
2061
2062 Self::ConsensusCommitPrologue(_)
2063 | Self::ConsensusCommitPrologueV2(_)
2064 | Self::ConsensusCommitPrologueV3(_)
2065 | Self::ConsensusCommitPrologueV4(_) => {
2066 Either::Left(Either::Left(iter::once(SharedInputObject {
2067 id: SUI_CLOCK_OBJECT_ID,
2068 initial_shared_version: SUI_CLOCK_OBJECT_SHARED_VERSION,
2069 mutability: SharedObjectMutability::Mutable,
2070 })))
2071 }
2072 Self::AuthenticatorStateUpdate(update) => {
2073 Either::Left(Either::Left(iter::once(SharedInputObject {
2074 id: SUI_AUTHENTICATOR_STATE_OBJECT_ID,
2075 initial_shared_version: update.authenticator_obj_initial_shared_version,
2076 mutability: SharedObjectMutability::Mutable,
2077 })))
2078 }
2079 Self::RandomnessStateUpdate(update) => {
2080 Either::Left(Either::Left(iter::once(SharedInputObject {
2081 id: SUI_RANDOMNESS_STATE_OBJECT_ID,
2082 initial_shared_version: update.randomness_obj_initial_shared_version,
2083 mutability: SharedObjectMutability::Mutable,
2084 })))
2085 }
2086 Self::EndOfEpochTransaction(txns) => Either::Left(Either::Right(
2087 txns.iter().flat_map(|txn| txn.shared_input_objects()),
2088 )),
2089 Self::ProgrammableTransaction(pt) | Self::ProgrammableSystemTransaction(pt) => {
2090 Either::Right(Either::Left(pt.shared_input_objects()))
2091 }
2092 Self::Genesis(_) => Either::Right(Either::Right(iter::empty())),
2093 }
2094 }
2095
2096 fn move_calls(&self) -> Vec<(usize, &ObjectID, &str, &str)> {
2097 match &self {
2098 Self::ProgrammableTransaction(pt) => pt.move_calls(),
2099 _ => vec![],
2100 }
2101 }
2102
2103 pub fn receiving_objects(&self) -> Vec<ObjectRef> {
2104 match &self {
2105 TransactionKind::ChangeEpoch(_)
2106 | TransactionKind::Genesis(_)
2107 | TransactionKind::ConsensusCommitPrologue(_)
2108 | TransactionKind::ConsensusCommitPrologueV2(_)
2109 | TransactionKind::ConsensusCommitPrologueV3(_)
2110 | TransactionKind::ConsensusCommitPrologueV4(_)
2111 | TransactionKind::AuthenticatorStateUpdate(_)
2112 | TransactionKind::RandomnessStateUpdate(_)
2113 | TransactionKind::EndOfEpochTransaction(_)
2114 | TransactionKind::ProgrammableSystemTransaction(_) => vec![],
2115 TransactionKind::ProgrammableTransaction(pt) => pt.receiving_objects(),
2116 }
2117 }
2118
2119 pub fn input_objects(&self) -> UserInputResult<Vec<InputObjectKind>> {
2124 let input_objects = match &self {
2125 Self::ChangeEpoch(_) => {
2126 vec![InputObjectKind::SharedMoveObject {
2127 id: SUI_SYSTEM_STATE_OBJECT_ID,
2128 initial_shared_version: SUI_SYSTEM_STATE_OBJECT_SHARED_VERSION,
2129 mutability: SharedObjectMutability::Mutable,
2130 }]
2131 }
2132 Self::Genesis(_) => {
2133 vec![]
2134 }
2135 Self::ConsensusCommitPrologue(_)
2136 | Self::ConsensusCommitPrologueV2(_)
2137 | Self::ConsensusCommitPrologueV3(_)
2138 | Self::ConsensusCommitPrologueV4(_) => {
2139 vec![InputObjectKind::SharedMoveObject {
2140 id: SUI_CLOCK_OBJECT_ID,
2141 initial_shared_version: SUI_CLOCK_OBJECT_SHARED_VERSION,
2142 mutability: SharedObjectMutability::Mutable,
2143 }]
2144 }
2145 Self::AuthenticatorStateUpdate(update) => {
2146 vec![InputObjectKind::SharedMoveObject {
2147 id: SUI_AUTHENTICATOR_STATE_OBJECT_ID,
2148 initial_shared_version: update.authenticator_obj_initial_shared_version(),
2149 mutability: SharedObjectMutability::Mutable,
2150 }]
2151 }
2152 Self::RandomnessStateUpdate(update) => {
2153 vec![InputObjectKind::SharedMoveObject {
2154 id: SUI_RANDOMNESS_STATE_OBJECT_ID,
2155 initial_shared_version: update.randomness_obj_initial_shared_version(),
2156 mutability: SharedObjectMutability::Mutable,
2157 }]
2158 }
2159 Self::EndOfEpochTransaction(txns) => {
2160 let before_dedup: Vec<_> =
2163 txns.iter().flat_map(|txn| txn.input_objects()).collect();
2164 let mut has_seen = HashSet::new();
2165 let mut after_dedup = vec![];
2166 for obj in before_dedup {
2167 if has_seen.insert(obj) {
2168 after_dedup.push(obj);
2169 }
2170 }
2171 after_dedup
2172 }
2173 Self::ProgrammableTransaction(p) | Self::ProgrammableSystemTransaction(p) => {
2174 return p.input_objects();
2175 }
2176 };
2177 let mut used = HashSet::new();
2185 if !input_objects.iter().all(|o| used.insert(o.object_id())) {
2186 return Err(UserInputError::DuplicateObjectRefInput);
2187 }
2188 Ok(input_objects)
2189 }
2190
2191 pub fn get_funds_withdrawals<'a>(
2192 &'a self,
2193 ) -> impl Iterator<Item = &'a FundsWithdrawalArg> + 'a {
2194 let TransactionKind::ProgrammableTransaction(pt) = &self else {
2195 return Either::Left(iter::empty());
2196 };
2197 Either::Right(pt.inputs.iter().filter_map(|input| {
2198 if let CallArg::FundsWithdrawal(withdraw) = input {
2199 Some(withdraw)
2200 } else {
2201 None
2202 }
2203 }))
2204 }
2205
2206 pub fn get_coin_reservation_obj_refs(&self) -> impl Iterator<Item = ObjectRef> + '_ {
2207 let TransactionKind::ProgrammableTransaction(pt) = &self else {
2208 return Either::Left(iter::empty());
2209 };
2210 Either::Right(pt.coin_reservation_obj_refs())
2211 }
2212
2213 pub fn has_coin_reservations(&self) -> bool {
2214 self.get_coin_reservation_obj_refs().next().is_some()
2215 }
2216
2217 pub fn validity_check(&self, config: &ProtocolConfig) -> UserInputResult {
2218 match self {
2219 TransactionKind::ProgrammableTransaction(p) => p.validity_check(config)?,
2220 TransactionKind::ChangeEpoch(_)
2223 | TransactionKind::Genesis(_)
2224 | TransactionKind::ConsensusCommitPrologue(_) => (),
2225 TransactionKind::ConsensusCommitPrologueV2(_) => {
2226 if !config.include_consensus_digest_in_prologue() {
2227 return Err(UserInputError::Unsupported(
2228 "ConsensusCommitPrologueV2 is not supported".to_string(),
2229 ));
2230 }
2231 }
2232 TransactionKind::ConsensusCommitPrologueV3(_) => {
2233 if !config.record_consensus_determined_version_assignments_in_prologue() {
2234 return Err(UserInputError::Unsupported(
2235 "ConsensusCommitPrologueV3 is not supported".to_string(),
2236 ));
2237 }
2238 }
2239 TransactionKind::ConsensusCommitPrologueV4(_) => {
2240 if !config.record_additional_state_digest_in_prologue() {
2241 return Err(UserInputError::Unsupported(
2242 "ConsensusCommitPrologueV4 is not supported".to_string(),
2243 ));
2244 }
2245 }
2246 TransactionKind::EndOfEpochTransaction(txns) => {
2247 if !config.end_of_epoch_transaction_supported() {
2248 return Err(UserInputError::Unsupported(
2249 "EndOfEpochTransaction is not supported".to_string(),
2250 ));
2251 }
2252
2253 for tx in txns {
2254 tx.validity_check(config)?;
2255 }
2256 }
2257
2258 TransactionKind::AuthenticatorStateUpdate(_) => {
2259 if !config.enable_jwk_consensus_updates() {
2260 return Err(UserInputError::Unsupported(
2261 "authenticator state updates not enabled".to_string(),
2262 ));
2263 }
2264 }
2265 TransactionKind::RandomnessStateUpdate(_) => {
2266 if !config.random_beacon() {
2267 return Err(UserInputError::Unsupported(
2268 "randomness state updates not enabled".to_string(),
2269 ));
2270 }
2271 }
2272 TransactionKind::ProgrammableSystemTransaction(_) => {
2273 if !config.enable_accumulators() {
2274 return Err(UserInputError::Unsupported(
2275 "accumulators not enabled".to_string(),
2276 ));
2277 }
2278 }
2279 };
2280 Ok(())
2281 }
2282
2283 pub fn num_commands(&self) -> usize {
2285 match self {
2286 TransactionKind::ProgrammableTransaction(pt) => pt.commands.len(),
2287 _ => 0,
2288 }
2289 }
2290
2291 pub fn iter_commands(&self) -> impl Iterator<Item = &Command> {
2292 match self {
2293 TransactionKind::ProgrammableTransaction(pt) => pt.commands.iter(),
2294 _ => [].iter(),
2295 }
2296 }
2297
2298 pub fn tx_count(&self) -> usize {
2300 match self {
2301 TransactionKind::ProgrammableTransaction(pt) => pt.commands.len(),
2302 _ => 1,
2303 }
2304 }
2305
2306 pub fn name(&self) -> &'static str {
2307 match self {
2308 Self::ChangeEpoch(_) => "ChangeEpoch",
2309 Self::Genesis(_) => "Genesis",
2310 Self::ConsensusCommitPrologue(_) => "ConsensusCommitPrologue",
2311 Self::ConsensusCommitPrologueV2(_) => "ConsensusCommitPrologueV2",
2312 Self::ConsensusCommitPrologueV3(_) => "ConsensusCommitPrologueV3",
2313 Self::ConsensusCommitPrologueV4(_) => "ConsensusCommitPrologueV4",
2314 Self::ProgrammableTransaction(_) => "ProgrammableTransaction",
2315 Self::ProgrammableSystemTransaction(_) => "ProgrammableSystemTransaction",
2316 Self::AuthenticatorStateUpdate(_) => "AuthenticatorStateUpdate",
2317 Self::RandomnessStateUpdate(_) => "RandomnessStateUpdate",
2318 Self::EndOfEpochTransaction(_) => "EndOfEpochTransaction",
2319 }
2320 }
2321}
2322
2323impl Display for TransactionKind {
2324 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
2325 let mut writer = String::new();
2326 match &self {
2327 Self::ChangeEpoch(e) => {
2328 writeln!(writer, "Transaction Kind : Epoch Change")?;
2329 writeln!(writer, "New epoch ID : {}", e.epoch)?;
2330 writeln!(writer, "Storage gas reward : {}", e.storage_charge)?;
2331 writeln!(writer, "Computation gas reward : {}", e.computation_charge)?;
2332 writeln!(writer, "Storage rebate : {}", e.storage_rebate)?;
2333 writeln!(writer, "Timestamp : {}", e.epoch_start_timestamp_ms)?;
2334 }
2335 Self::Genesis(_) => {
2336 writeln!(writer, "Transaction Kind : Genesis")?;
2337 }
2338 Self::ConsensusCommitPrologue(p) => {
2339 writeln!(writer, "Transaction Kind : Consensus Commit Prologue")?;
2340 writeln!(writer, "Timestamp : {}", p.commit_timestamp_ms)?;
2341 }
2342 Self::ConsensusCommitPrologueV2(p) => {
2343 writeln!(writer, "Transaction Kind : Consensus Commit Prologue V2")?;
2344 writeln!(writer, "Timestamp : {}", p.commit_timestamp_ms)?;
2345 writeln!(writer, "Consensus Digest: {}", p.consensus_commit_digest)?;
2346 }
2347 Self::ConsensusCommitPrologueV3(p) => {
2348 writeln!(writer, "Transaction Kind : Consensus Commit Prologue V3")?;
2349 writeln!(writer, "Timestamp : {}", p.commit_timestamp_ms)?;
2350 writeln!(writer, "Consensus Digest: {}", p.consensus_commit_digest)?;
2351 writeln!(
2352 writer,
2353 "Consensus determined version assignment: {:?}",
2354 p.consensus_determined_version_assignments
2355 )?;
2356 }
2357 Self::ConsensusCommitPrologueV4(p) => {
2358 writeln!(writer, "Transaction Kind : Consensus Commit Prologue V4")?;
2359 writeln!(writer, "Timestamp : {}", p.commit_timestamp_ms)?;
2360 writeln!(writer, "Consensus Digest: {}", p.consensus_commit_digest)?;
2361 writeln!(
2362 writer,
2363 "Consensus determined version assignment: {:?}",
2364 p.consensus_determined_version_assignments
2365 )?;
2366 writeln!(
2367 writer,
2368 "Additional State Digest: {}",
2369 p.additional_state_digest
2370 )?;
2371 }
2372 Self::ProgrammableTransaction(p) => {
2373 writeln!(writer, "Transaction Kind : Programmable")?;
2374 write!(writer, "{p}")?;
2375 }
2376 Self::ProgrammableSystemTransaction(p) => {
2377 writeln!(writer, "Transaction Kind : Programmable System")?;
2378 write!(writer, "{p}")?;
2379 }
2380 Self::AuthenticatorStateUpdate(_) => {
2381 writeln!(writer, "Transaction Kind : Authenticator State Update")?;
2382 }
2383 Self::RandomnessStateUpdate(_) => {
2384 writeln!(writer, "Transaction Kind : Randomness State Update")?;
2385 }
2386 Self::EndOfEpochTransaction(_) => {
2387 writeln!(writer, "Transaction Kind : End of Epoch Transaction")?;
2388 }
2389 }
2390 write!(f, "{}", writer)
2391 }
2392}
2393
2394#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)]
2395pub struct GasData {
2396 pub payment: Vec<ObjectRef>,
2397 pub owner: SuiAddress,
2398 pub price: u64,
2399 pub budget: u64,
2400}
2401
2402impl GasData {
2403 pub fn is_unmetered(&self) -> bool {
2404 self.payment.len() == 1
2405 && self.payment[0].0 == ObjectID::ZERO
2406 && self.payment[0].1 == SequenceNumber::default()
2407 && self.payment[0].2 == ObjectDigest::MIN
2408 }
2409}
2410
2411pub fn is_gas_paid_from_address_balance(
2412 gas_data: &GasData,
2413 transaction_kind: &TransactionKind,
2414) -> bool {
2415 gas_data.payment.is_empty()
2416 && matches!(
2417 transaction_kind,
2418 TransactionKind::ProgrammableTransaction(_)
2419 )
2420}
2421
2422pub fn is_gasless_transaction(gas_data: &GasData, transaction_kind: &TransactionKind) -> bool {
2423 is_gas_paid_from_address_balance(gas_data, transaction_kind) && gas_data.price == 0
2424}
2425
2426#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)]
2427pub enum TransactionExpiration {
2428 None,
2430 Epoch(EpochId),
2433 ValidDuring {
2443 min_epoch: Option<EpochId>,
2445 max_epoch: Option<EpochId>,
2447 min_timestamp: Option<u64>,
2449 max_timestamp: Option<u64>,
2451 chain: ChainIdentifier,
2453 nonce: u32,
2455 },
2456 Validity {
2459 min_epoch: Option<EpochId>,
2461 max_epoch: Option<EpochId>,
2463 min_timestamp: Option<u64>,
2465 max_timestamp: Option<u64>,
2467 chain: ChainIdentifier,
2469 nonce: u32,
2471 allowed_proposers: Option<AllowedProposers>,
2473 },
2474}
2475
2476#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)]
2479pub struct AllowedProposers {
2480 pub epoch: EpochId,
2484 #[serde(with = "nonempty_as_vec")]
2489 pub proposers: NonEmpty<u32>,
2490}
2491
2492pub const MAX_UNPAID_ALLOWED_PROPOSERS: u64 = 3;
2498
2499impl TransactionExpiration {
2500 pub fn is_replay_protected(&self) -> bool {
2505 matches!(
2506 self,
2507 TransactionExpiration::ValidDuring {
2508 min_epoch: Some(min_epoch),
2509 max_epoch: Some(max_epoch),
2510 ..
2511 }
2512 | TransactionExpiration::Validity {
2513 min_epoch: Some(min_epoch),
2514 max_epoch: Some(max_epoch),
2515 ..
2516 } if *max_epoch == *min_epoch || *max_epoch == min_epoch.saturating_add(1)
2517 )
2518 }
2519
2520 pub fn allowed_proposers(&self, epoch: EpochId) -> Option<&AllowedProposers> {
2528 match self {
2529 TransactionExpiration::Validity {
2530 allowed_proposers: Some(allowed),
2531 ..
2532 } if allowed.epoch == epoch => Some(allowed),
2533 _ => None,
2534 }
2535 }
2536
2537 pub fn restricts_proposers(&self, epoch: EpochId) -> bool {
2540 self.allowed_proposers(epoch).is_some()
2541 }
2542
2543 pub fn is_allowed_proposer(&self, proposer: u32, epoch: EpochId) -> bool {
2546 self.allowed_proposers(epoch).is_none_or(|allowed| {
2547 allowed
2551 .proposers
2552 .iter()
2553 .find(|p| **p >= proposer)
2554 .is_some_and(|p| *p == proposer)
2555 })
2556 }
2557}
2558
2559#[enum_dispatch(TransactionDataAPI)]
2560#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)]
2561pub enum TransactionData {
2562 V1(TransactionDataV1),
2563 }
2566
2567#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)]
2568pub struct TransactionDataV1 {
2569 pub kind: TransactionKind,
2570 pub sender: SuiAddress,
2571 pub gas_data: GasData,
2572 pub expiration: TransactionExpiration,
2573}
2574
2575impl TransactionData {
2576 pub fn as_v1(&self) -> &TransactionDataV1 {
2577 match self {
2578 TransactionData::V1(v1) => v1,
2579 }
2580 }
2581
2582 pub fn has_replay_protection(
2587 &self,
2588 input_objects: impl IntoIterator<Item = impl std::borrow::Borrow<ObjectReadResult>>,
2589 ) -> bool {
2590 self.expiration().is_replay_protected()
2591 || !self.gas_data().payment.is_empty()
2592 || input_objects
2593 .into_iter()
2594 .any(|object| object.borrow().is_replay_protected_input())
2595 }
2596 fn new_system_transaction(kind: TransactionKind) -> Self {
2597 assert!(kind.is_system_tx());
2599 let sender = SuiAddress::default();
2600 TransactionData::V1(TransactionDataV1 {
2601 kind,
2602 sender,
2603 gas_data: GasData {
2604 price: GAS_PRICE_FOR_SYSTEM_TX,
2605 owner: sender,
2606 payment: vec![(ObjectID::ZERO, SequenceNumber::default(), ObjectDigest::MIN)],
2607 budget: 0,
2608 },
2609 expiration: TransactionExpiration::None,
2610 })
2611 }
2612
2613 pub fn new(
2614 kind: TransactionKind,
2615 sender: SuiAddress,
2616 gas_payment: ObjectRef,
2617 gas_budget: u64,
2618 gas_price: u64,
2619 ) -> Self {
2620 TransactionData::V1(TransactionDataV1 {
2621 kind,
2622 sender,
2623 gas_data: GasData {
2624 price: gas_price,
2625 owner: sender,
2626 payment: vec![gas_payment],
2627 budget: gas_budget,
2628 },
2629 expiration: TransactionExpiration::None,
2630 })
2631 }
2632
2633 pub fn new_with_gas_coins(
2634 kind: TransactionKind,
2635 sender: SuiAddress,
2636 gas_payment: Vec<ObjectRef>,
2637 gas_budget: u64,
2638 gas_price: u64,
2639 ) -> Self {
2640 Self::new_with_gas_coins_allow_sponsor(
2641 kind,
2642 sender,
2643 gas_payment,
2644 gas_budget,
2645 gas_price,
2646 sender,
2647 )
2648 }
2649
2650 pub fn new_with_gas_coins_allow_sponsor(
2651 kind: TransactionKind,
2652 sender: SuiAddress,
2653 gas_payment: Vec<ObjectRef>,
2654 gas_budget: u64,
2655 gas_price: u64,
2656 gas_sponsor: SuiAddress,
2657 ) -> Self {
2658 TransactionData::V1(TransactionDataV1 {
2659 kind,
2660 sender,
2661 gas_data: GasData {
2662 price: gas_price,
2663 owner: gas_sponsor,
2664 payment: gas_payment,
2665 budget: gas_budget,
2666 },
2667 expiration: TransactionExpiration::None,
2668 })
2669 }
2670
2671 pub fn new_with_gas_data(kind: TransactionKind, sender: SuiAddress, gas_data: GasData) -> Self {
2672 TransactionData::V1(TransactionDataV1 {
2673 kind,
2674 sender,
2675 gas_data,
2676 expiration: TransactionExpiration::None,
2677 })
2678 }
2679
2680 pub fn new_with_gas_data_and_expiration(
2681 kind: TransactionKind,
2682 sender: SuiAddress,
2683 gas_data: GasData,
2684 expiration: TransactionExpiration,
2685 ) -> Self {
2686 TransactionData::V1(TransactionDataV1 {
2687 kind,
2688 sender,
2689 gas_data,
2690 expiration,
2691 })
2692 }
2693
2694 pub fn new_move_call(
2695 sender: SuiAddress,
2696 package: ObjectID,
2697 module: Identifier,
2698 function: Identifier,
2699 type_arguments: Vec<TypeTag>,
2700 gas_payment: ObjectRef,
2701 arguments: Vec<CallArg>,
2702 gas_budget: u64,
2703 gas_price: u64,
2704 ) -> anyhow::Result<Self> {
2705 Self::new_move_call_with_gas_coins(
2706 sender,
2707 package,
2708 module,
2709 function,
2710 type_arguments,
2711 vec![gas_payment],
2712 arguments,
2713 gas_budget,
2714 gas_price,
2715 )
2716 }
2717
2718 pub fn new_move_call_with_gas_coins(
2719 sender: SuiAddress,
2720 package: ObjectID,
2721 module: Identifier,
2722 function: Identifier,
2723 type_arguments: Vec<TypeTag>,
2724 gas_payment: Vec<ObjectRef>,
2725 arguments: Vec<CallArg>,
2726 gas_budget: u64,
2727 gas_price: u64,
2728 ) -> anyhow::Result<Self> {
2729 let pt = {
2730 let mut builder = ProgrammableTransactionBuilder::new();
2731 builder.move_call(package, module, function, type_arguments, arguments)?;
2732 builder.finish()
2733 };
2734 Ok(Self::new_programmable(
2735 sender,
2736 gas_payment,
2737 pt,
2738 gas_budget,
2739 gas_price,
2740 ))
2741 }
2742
2743 pub fn new_transfer(
2744 recipient: SuiAddress,
2745 full_object_ref: FullObjectRef,
2746 sender: SuiAddress,
2747 gas_payment: ObjectRef,
2748 gas_budget: u64,
2749 gas_price: u64,
2750 ) -> Self {
2751 let pt = {
2752 let mut builder = ProgrammableTransactionBuilder::new();
2753 builder.transfer_object(recipient, full_object_ref).unwrap();
2754 builder.finish()
2755 };
2756 Self::new_programmable(sender, vec![gas_payment], pt, gas_budget, gas_price)
2757 }
2758
2759 pub fn new_transfer_sui(
2760 recipient: SuiAddress,
2761 sender: SuiAddress,
2762 amount: Option<u64>,
2763 gas_payment: ObjectRef,
2764 gas_budget: u64,
2765 gas_price: u64,
2766 ) -> Self {
2767 Self::new_transfer_sui_allow_sponsor(
2768 recipient,
2769 sender,
2770 amount,
2771 gas_payment,
2772 gas_budget,
2773 gas_price,
2774 sender,
2775 )
2776 }
2777
2778 pub fn new_transfer_sui_allow_sponsor(
2779 recipient: SuiAddress,
2780 sender: SuiAddress,
2781 amount: Option<u64>,
2782 gas_payment: ObjectRef,
2783 gas_budget: u64,
2784 gas_price: u64,
2785 gas_sponsor: SuiAddress,
2786 ) -> Self {
2787 let pt = {
2788 let mut builder = ProgrammableTransactionBuilder::new();
2789 builder.transfer_sui(recipient, amount);
2790 builder.finish()
2791 };
2792 Self::new_programmable_allow_sponsor(
2793 sender,
2794 vec![gas_payment],
2795 pt,
2796 gas_budget,
2797 gas_price,
2798 gas_sponsor,
2799 )
2800 }
2801
2802 pub fn new_pay(
2803 sender: SuiAddress,
2804 coins: Vec<ObjectRef>,
2805 recipients: Vec<SuiAddress>,
2806 amounts: Vec<u64>,
2807 gas_payment: ObjectRef,
2808 gas_budget: u64,
2809 gas_price: u64,
2810 ) -> anyhow::Result<Self> {
2811 let pt = {
2812 let mut builder = ProgrammableTransactionBuilder::new();
2813 builder.pay(coins, recipients, amounts)?;
2814 builder.finish()
2815 };
2816 Ok(Self::new_programmable(
2817 sender,
2818 vec![gas_payment],
2819 pt,
2820 gas_budget,
2821 gas_price,
2822 ))
2823 }
2824
2825 pub fn new_pay_sui(
2826 sender: SuiAddress,
2827 mut coins: Vec<ObjectRef>,
2828 recipients: Vec<SuiAddress>,
2829 amounts: Vec<u64>,
2830 gas_payment: ObjectRef,
2831 gas_budget: u64,
2832 gas_price: u64,
2833 ) -> anyhow::Result<Self> {
2834 coins.insert(0, gas_payment);
2835 let pt = {
2836 let mut builder = ProgrammableTransactionBuilder::new();
2837 builder.pay_sui(recipients, amounts)?;
2838 builder.finish()
2839 };
2840 Ok(Self::new_programmable(
2841 sender, coins, pt, gas_budget, gas_price,
2842 ))
2843 }
2844
2845 pub fn new_pay_all_sui(
2846 sender: SuiAddress,
2847 mut coins: Vec<ObjectRef>,
2848 recipient: SuiAddress,
2849 gas_payment: ObjectRef,
2850 gas_budget: u64,
2851 gas_price: u64,
2852 ) -> Self {
2853 coins.insert(0, gas_payment);
2854 let pt = {
2855 let mut builder = ProgrammableTransactionBuilder::new();
2856 builder.pay_all_sui(recipient);
2857 builder.finish()
2858 };
2859 Self::new_programmable(sender, coins, pt, gas_budget, gas_price)
2860 }
2861
2862 pub fn new_split_coin(
2863 sender: SuiAddress,
2864 coin: ObjectRef,
2865 amounts: Vec<u64>,
2866 gas_payment: ObjectRef,
2867 gas_budget: u64,
2868 gas_price: u64,
2869 ) -> Self {
2870 let pt = {
2871 let mut builder = ProgrammableTransactionBuilder::new();
2872 builder.split_coin(sender, coin, amounts);
2873 builder.finish()
2874 };
2875 Self::new_programmable(sender, vec![gas_payment], pt, gas_budget, gas_price)
2876 }
2877
2878 pub fn new_module(
2879 sender: SuiAddress,
2880 gas_payment: ObjectRef,
2881 modules: Vec<Vec<u8>>,
2882 dep_ids: Vec<ObjectID>,
2883 gas_budget: u64,
2884 gas_price: u64,
2885 ) -> Self {
2886 let pt = {
2887 let mut builder = ProgrammableTransactionBuilder::new();
2888 let upgrade_cap = builder.publish_upgradeable(modules, dep_ids);
2889 builder.transfer_arg(sender, upgrade_cap);
2890 builder.finish()
2891 };
2892 Self::new_programmable(sender, vec![gas_payment], pt, gas_budget, gas_price)
2893 }
2894
2895 pub fn new_upgrade(
2896 sender: SuiAddress,
2897 gas_payment: ObjectRef,
2898 package_id: ObjectID,
2899 modules: Vec<Vec<u8>>,
2900 dep_ids: Vec<ObjectID>,
2901 (upgrade_capability, capability_owner): (ObjectRef, Owner),
2902 upgrade_policy: u8,
2903 digest: Vec<u8>,
2904 gas_budget: u64,
2905 gas_price: u64,
2906 ) -> anyhow::Result<Self> {
2907 let pt = {
2908 let mut builder = ProgrammableTransactionBuilder::new();
2909 let capability_arg = match capability_owner {
2910 Owner::AddressOwner(_) => ObjectArg::ImmOrOwnedObject(upgrade_capability),
2911 Owner::Shared {
2912 initial_shared_version,
2913 }
2914 | Owner::ConsensusAddressOwner {
2915 start_version: initial_shared_version,
2916 ..
2917 }
2918 | Owner::Party {
2919 start_version: initial_shared_version,
2920 ..
2921 } => ObjectArg::SharedObject {
2922 id: upgrade_capability.0,
2923 initial_shared_version,
2924 mutability: SharedObjectMutability::Mutable,
2925 },
2926 Owner::Immutable => {
2927 return Err(anyhow::anyhow!(
2928 "Upgrade capability is stored immutably and cannot be used for upgrades"
2929 ));
2930 }
2931 Owner::ObjectOwner(_) => {
2934 return Err(anyhow::anyhow!("Upgrade capability controlled by object"));
2935 }
2936 };
2937 builder.obj(capability_arg).unwrap();
2938 let upgrade_arg = builder.pure(upgrade_policy).unwrap();
2939 let digest_arg = builder.pure(digest).unwrap();
2940 let upgrade_ticket = builder.programmable_move_call(
2941 SUI_FRAMEWORK_PACKAGE_ID,
2942 ident_str!("package").to_owned(),
2943 ident_str!("authorize_upgrade").to_owned(),
2944 vec![],
2945 vec![Argument::Input(0), upgrade_arg, digest_arg],
2946 );
2947 let upgrade_receipt = builder.upgrade(package_id, upgrade_ticket, dep_ids, modules);
2948
2949 builder.programmable_move_call(
2950 SUI_FRAMEWORK_PACKAGE_ID,
2951 ident_str!("package").to_owned(),
2952 ident_str!("commit_upgrade").to_owned(),
2953 vec![],
2954 vec![Argument::Input(0), upgrade_receipt],
2955 );
2956
2957 builder.finish()
2958 };
2959 Ok(Self::new_programmable(
2960 sender,
2961 vec![gas_payment],
2962 pt,
2963 gas_budget,
2964 gas_price,
2965 ))
2966 }
2967
2968 pub fn new_programmable(
2969 sender: SuiAddress,
2970 gas_payment: Vec<ObjectRef>,
2971 pt: ProgrammableTransaction,
2972 gas_budget: u64,
2973 gas_price: u64,
2974 ) -> Self {
2975 Self::new_programmable_allow_sponsor(sender, gas_payment, pt, gas_budget, gas_price, sender)
2976 }
2977
2978 pub fn new_programmable_allow_sponsor(
2979 sender: SuiAddress,
2980 gas_payment: Vec<ObjectRef>,
2981 pt: ProgrammableTransaction,
2982 gas_budget: u64,
2983 gas_price: u64,
2984 sponsor: SuiAddress,
2985 ) -> Self {
2986 let kind = TransactionKind::ProgrammableTransaction(pt);
2987 Self::new_with_gas_coins_allow_sponsor(
2988 kind,
2989 sender,
2990 gas_payment,
2991 gas_budget,
2992 gas_price,
2993 sponsor,
2994 )
2995 }
2996
2997 pub fn new_programmable_with_address_balance_gas(
2998 sender: SuiAddress,
2999 pt: ProgrammableTransaction,
3000 gas_budget: u64,
3001 gas_price: u64,
3002 chain_identifier: ChainIdentifier,
3003 current_epoch: EpochId,
3004 nonce: u32,
3005 ) -> Self {
3006 TransactionData::V1(TransactionDataV1 {
3007 kind: TransactionKind::ProgrammableTransaction(pt),
3008 sender,
3009 gas_data: GasData {
3010 payment: vec![],
3011 owner: sender,
3012 price: gas_price,
3013 budget: gas_budget,
3014 },
3015 expiration: TransactionExpiration::ValidDuring {
3016 min_epoch: Some(current_epoch),
3017 max_epoch: Some(current_epoch + 1),
3018 min_timestamp: None,
3019 max_timestamp: None,
3020 chain: chain_identifier,
3021 nonce,
3022 },
3023 })
3024 }
3025
3026 pub fn message_version(&self) -> u64 {
3027 match self {
3028 TransactionData::V1(_) => 1,
3029 }
3030 }
3031
3032 pub fn execution_parts(&self) -> (TransactionKind, SuiAddress, GasData) {
3033 (self.kind().clone(), self.sender(), self.gas_data().clone())
3034 }
3035
3036 pub fn uses_randomness(&self) -> bool {
3037 self.kind()
3038 .shared_input_objects()
3039 .any(|obj| obj.id() == SUI_RANDOMNESS_STATE_OBJECT_ID)
3040 }
3041
3042 pub fn digest(&self) -> TransactionDigest {
3043 TransactionDigest::new(default_hash(self))
3044 }
3045}
3046
3047#[enum_dispatch]
3048pub trait TransactionDataAPI {
3049 fn sender(&self) -> SuiAddress;
3050
3051 fn kind(&self) -> &TransactionKind;
3054
3055 fn kind_mut(&mut self) -> &mut TransactionKind;
3057
3058 fn into_kind(self) -> TransactionKind;
3060
3061 fn required_signers(&self) -> NonEmpty<SuiAddress>;
3063
3064 fn gas_data(&self) -> &GasData;
3065
3066 fn gas_owner(&self) -> SuiAddress;
3067
3068 fn gas(&self) -> &[ObjectRef];
3069
3070 fn gas_price(&self) -> u64;
3071
3072 fn gas_budget(&self) -> u64;
3073
3074 fn expiration(&self) -> &TransactionExpiration;
3075
3076 fn expiration_mut(&mut self) -> &mut TransactionExpiration;
3077
3078 fn move_calls(&self) -> Vec<(usize, &ObjectID, &str, &str)>;
3079
3080 fn input_objects(&self) -> UserInputResult<Vec<InputObjectKind>>;
3081
3082 fn shared_input_objects(&self) -> Vec<SharedInputObject>;
3083
3084 fn receiving_objects(&self) -> Vec<ObjectRef>;
3085
3086 fn fastpath_dependency_objects(
3090 &self,
3091 ) -> UserInputResult<(Vec<ObjectRef>, Vec<ObjectID>, Vec<ObjectRef>)>;
3092
3093 fn process_funds_withdrawals_for_signing(
3101 &self,
3102 chain_identifier: ChainIdentifier,
3103 coin_resolver: &dyn CoinReservationResolverTrait,
3104 ) -> UserInputResult<BTreeMap<AccumulatorObjId, (u64, TypeTag, SuiAddress)>>;
3105
3106 fn process_funds_withdrawals_for_estimation(
3110 &self,
3111 chain_identifier: ChainIdentifier,
3112 coin_resolver: &dyn CoinReservationResolverTrait,
3113 ) -> UserInputResult<BTreeMap<AccumulatorObjId, (u64, TypeTag, SuiAddress)>>;
3114
3115 fn process_funds_withdrawals_for_execution(
3118 &self,
3119 chain_identifier: ChainIdentifier,
3120 ) -> BTreeMap<AccumulatorObjId, u64>;
3121
3122 fn check_allowance_inputs(&self, input_objects: &InputObjects) -> UserInputResult<()>;
3126
3127 fn has_funds_withdrawals(&self) -> bool;
3129
3130 fn coin_reservation_obj_refs(
3131 &self,
3132 chain_identifier: ChainIdentifier,
3133 ) -> Vec<ParsedObjectRefWithdrawal>;
3134
3135 fn validity_check(&self, context: &TxValidityCheckContext<'_>) -> SuiResult;
3136
3137 fn check_sponsorship(&self) -> UserInputResult;
3139
3140 fn is_system_tx(&self) -> bool;
3141 fn is_genesis_tx(&self) -> bool;
3142
3143 fn is_end_of_epoch_tx(&self) -> bool;
3146
3147 fn is_consensus_commit_prologue(&self) -> bool;
3148
3149 fn is_sponsored_tx(&self) -> bool;
3151
3152 fn is_gas_paid_from_address_balance(&self) -> bool;
3153
3154 fn is_gasless_transaction(&self) -> bool;
3155
3156 fn sender_mut_for_testing(&mut self) -> &mut SuiAddress;
3157
3158 fn gas_data_mut(&mut self) -> &mut GasData;
3159
3160 fn expiration_mut_for_testing(&mut self) -> &mut TransactionExpiration;
3162}
3163
3164impl TransactionDataAPI for TransactionDataV1 {
3165 fn sender(&self) -> SuiAddress {
3166 self.sender
3167 }
3168
3169 fn kind(&self) -> &TransactionKind {
3170 &self.kind
3171 }
3172
3173 fn kind_mut(&mut self) -> &mut TransactionKind {
3174 &mut self.kind
3175 }
3176
3177 fn into_kind(self) -> TransactionKind {
3178 self.kind
3179 }
3180
3181 fn required_signers(&self) -> NonEmpty<SuiAddress> {
3183 let mut signers = nonempty![self.sender];
3184 if self.gas_owner() != self.sender {
3185 signers.push(self.gas_owner());
3186 }
3187 signers
3188 }
3189
3190 fn gas_data(&self) -> &GasData {
3191 &self.gas_data
3192 }
3193
3194 fn gas_owner(&self) -> SuiAddress {
3195 self.gas_data.owner
3196 }
3197
3198 fn gas(&self) -> &[ObjectRef] {
3199 &self.gas_data.payment
3200 }
3201
3202 fn gas_price(&self) -> u64 {
3203 self.gas_data.price
3204 }
3205
3206 fn gas_budget(&self) -> u64 {
3207 self.gas_data.budget
3208 }
3209
3210 fn expiration(&self) -> &TransactionExpiration {
3211 &self.expiration
3212 }
3213
3214 fn expiration_mut(&mut self) -> &mut TransactionExpiration {
3215 &mut self.expiration
3216 }
3217
3218 fn move_calls(&self) -> Vec<(usize, &ObjectID, &str, &str)> {
3219 self.kind.move_calls()
3220 }
3221
3222 fn input_objects(&self) -> UserInputResult<Vec<InputObjectKind>> {
3223 let mut inputs = self.kind.input_objects()?;
3224
3225 if !self.kind.is_system_tx() {
3226 inputs.extend(
3227 self.gas()
3228 .iter()
3229 .filter(|obj_ref| !ParsedDigest::is_coin_reservation_digest(&obj_ref.2))
3230 .map(|obj_ref| InputObjectKind::ImmOrOwnedMoveObject(*obj_ref)),
3231 );
3232 }
3233 Ok(inputs)
3234 }
3235
3236 fn shared_input_objects(&self) -> Vec<SharedInputObject> {
3237 self.kind.shared_input_objects().collect()
3238 }
3239
3240 fn receiving_objects(&self) -> Vec<ObjectRef> {
3241 self.kind.receiving_objects()
3242 }
3243
3244 fn fastpath_dependency_objects(
3245 &self,
3246 ) -> UserInputResult<(Vec<ObjectRef>, Vec<ObjectID>, Vec<ObjectRef>)> {
3247 let mut move_objects = vec![];
3248 let mut packages = vec![];
3249 let mut receiving_objects = vec![];
3250 self.input_objects()?.iter().for_each(|o| match o {
3251 InputObjectKind::ImmOrOwnedMoveObject(object_ref) => {
3252 move_objects.push(*object_ref);
3253 }
3254 InputObjectKind::MovePackage(package_id) => {
3255 packages.push(*package_id);
3256 }
3257 InputObjectKind::SharedMoveObject { .. } => {}
3258 });
3259 self.receiving_objects().iter().for_each(|object_ref| {
3260 receiving_objects.push(*object_ref);
3261 });
3262 Ok((move_objects, packages, receiving_objects))
3263 }
3264
3265 fn process_funds_withdrawals_for_signing(
3266 &self,
3267 chain_identifier: ChainIdentifier,
3268 coin_resolver: &dyn CoinReservationResolverTrait,
3269 ) -> UserInputResult<BTreeMap<AccumulatorObjId, (u64, TypeTag, SuiAddress)>> {
3270 self.accumulate_funds_withdrawals(chain_identifier, coin_resolver, true)
3271 }
3272
3273 fn process_funds_withdrawals_for_estimation(
3274 &self,
3275 chain_identifier: ChainIdentifier,
3276 coin_resolver: &dyn CoinReservationResolverTrait,
3277 ) -> UserInputResult<BTreeMap<AccumulatorObjId, (u64, TypeTag, SuiAddress)>> {
3278 self.accumulate_funds_withdrawals(chain_identifier, coin_resolver, false)
3279 }
3280
3281 fn process_funds_withdrawals_for_execution(
3282 &self,
3283 chain_identifier: ChainIdentifier,
3284 ) -> BTreeMap<AccumulatorObjId, u64> {
3285 let mut withdraws: Vec<_> = self.get_funds_withdrawals().collect();
3286 withdraws.extend(self.get_funds_withdrawal_for_gas_payment());
3287
3288 let mut withdraw_map: BTreeMap<AccumulatorObjId, u64> = BTreeMap::new();
3290 for withdraw in withdraws {
3291 let reserved_amount = match &withdraw.reservation {
3292 Reservation::MaxAmountU64(amount) => {
3293 assert!(*amount > 0, "verified in validity check");
3294 *amount
3295 }
3296 };
3297
3298 let withdrawal_owner = withdraw.owner_for_withdrawal(self);
3299
3300 let account_id =
3302 AccumulatorValue::get_field_id(withdrawal_owner, &withdraw.type_arg.to_type_tag())
3303 .unwrap();
3304
3305 let value = withdraw_map.entry(account_id).or_default();
3306 *value = value.checked_add(reserved_amount).unwrap();
3308 }
3309
3310 for obj in self.coin_reservation_obj_refs() {
3314 assert_reachable!("processing coin reservation withdrawal");
3315 let parsed = ParsedObjectRefWithdrawal::parse(&obj, chain_identifier).unwrap();
3317 let value = withdraw_map
3318 .entry(AccumulatorObjId::new_unchecked(parsed.unmasked_object_id))
3323 .or_default();
3324 *value = value.checked_add(parsed.reservation_amount()).unwrap();
3326 }
3327
3328 withdraw_map
3329 }
3330
3331 fn check_allowance_inputs(&self, input_objects: &InputObjects) -> UserInputResult<()> {
3332 let allowance_withdrawals: Vec<_> = self
3333 .get_funds_withdrawals()
3334 .filter_map(|w| match w.withdraw_from {
3335 WithdrawFrom::SenderAllowance { funder, allowance } => {
3336 Some((funder, allowance, w.type_arg.to_type_tag()))
3337 }
3338 _ => None,
3339 })
3340 .collect();
3341 if allowance_withdrawals.is_empty() {
3342 return Ok(());
3343 }
3344 let objects_by_id: BTreeMap<ObjectID, &Object> = input_objects
3346 .iter()
3347 .filter_map(|input| Some((input.id(), input.as_object()?)))
3348 .collect();
3349 let mut resolved_allowances: BTreeMap<ObjectID, ResolvedAllowance> = BTreeMap::new();
3352 for (specified_funder, allowance, requested_funds_type) in allowance_withdrawals {
3353 let resolved: &ResolvedAllowance = match resolved_allowances.entry(allowance) {
3354 Entry::Occupied(entry) => entry.into_mut(),
3355 Entry::Vacant(entry) => {
3356 let object = objects_by_id.get(&allowance).ok_or_else(|| {
3357 UserInputError::InvalidWithdrawReservation {
3358 error: format!(
3359 "Specified allowance {allowance} not found among the tx inputs"
3360 ),
3361 }
3362 })?;
3363 entry.insert(parse_allowance_object(object)?)
3364 }
3365 };
3366 if resolved.funder != specified_funder {
3367 return Err(UserInputError::InvalidWithdrawReservation {
3368 error: format!(
3369 "Specified funder {specified_funder} does not match the funder of \
3370 allowance {allowance}"
3371 ),
3372 });
3373 }
3374 if resolved.spender != Some(self.sender()) {
3375 return Err(UserInputError::InvalidWithdrawReservation {
3376 error: format!(
3377 "Transaction sender is not the spender of allowance {allowance}"
3378 ),
3379 });
3380 }
3381 if resolved.funds_type != requested_funds_type {
3382 return Err(UserInputError::InvalidWithdrawReservation {
3383 error: format!(
3384 "Allowance {allowance} is for {}, not {requested_funds_type}",
3385 resolved.funds_type
3386 ),
3387 });
3388 }
3389 }
3390 Ok(())
3391 }
3392
3393 fn has_funds_withdrawals(&self) -> bool {
3394 if self.is_gas_paid_from_address_balance() && self.gas_data().budget > 0 {
3395 return true;
3396 }
3397 if let TransactionKind::ProgrammableTransaction(pt) = &self.kind {
3398 for input in &pt.inputs {
3399 if matches!(input, CallArg::FundsWithdrawal(_)) {
3400 return true;
3401 }
3402 }
3403 }
3404 if self.coin_reservation_obj_refs().next().is_some() {
3405 return true;
3406 }
3407 false
3408 }
3409
3410 fn coin_reservation_obj_refs(
3411 &self,
3412 chain_identifier: ChainIdentifier,
3413 ) -> Vec<ParsedObjectRefWithdrawal> {
3414 self.coin_reservation_obj_refs()
3415 .filter_map(|obj_ref| ParsedObjectRefWithdrawal::parse(&obj_ref, chain_identifier))
3416 .collect()
3417 }
3418
3419 fn validity_check(&self, context: &TxValidityCheckContext<'_>) -> SuiResult {
3420 let config = context.config;
3421
3422 match self.expiration() {
3424 TransactionExpiration::None => (), TransactionExpiration::Epoch(max_epoch) => {
3426 if context.epoch > *max_epoch {
3427 return Err(SuiErrorKind::TransactionExpired.into());
3428 }
3429 }
3430 TransactionExpiration::ValidDuring {
3431 min_epoch,
3432 max_epoch,
3433 min_timestamp,
3434 max_timestamp,
3435 chain,
3436 ..
3437 }
3438 | TransactionExpiration::Validity {
3439 min_epoch,
3440 max_epoch,
3441 min_timestamp,
3442 max_timestamp,
3443 chain,
3444 ..
3445 } => {
3446 if matches!(self.expiration(), TransactionExpiration::Validity { .. }) {
3449 fp_ensure!(
3450 config.allowed_proposers(),
3451 UserInputError::Unsupported(
3452 "Restricting the proposers of a transaction is not supported"
3453 .to_string(),
3454 )
3455 .into()
3456 );
3457 }
3458
3459 if let Some(allowed_proposers) = self.expiration().allowed_proposers(context.epoch)
3462 {
3463 let proposers = &allowed_proposers.proposers;
3464
3465 let max_proposers = MAX_UNPAID_ALLOWED_PROPOSERS
3472 .max(self.gas_data.price / context.reference_gas_price.max(1))
3473 .min(context.committee_size as u64);
3474 fp_ensure!(
3475 proposers.len() as u64 <= max_proposers,
3476 UserInputError::InvalidExpiration {
3477 error: format!(
3478 "allowed_proposers has {} entries, but at most {max_proposers} \
3479 are permitted with gas price {}, reference gas price {}, and a \
3480 committee of {}",
3481 proposers.len(),
3482 self.gas_data.price,
3483 context.reference_gas_price,
3484 context.committee_size,
3485 ),
3486 }
3487 .into()
3488 );
3489
3490 fp_ensure!(
3493 proposers.iter().is_sorted_by(|a, b| a < b),
3494 UserInputError::InvalidExpiration {
3495 error: "allowed_proposers must be strictly increasing".to_string(),
3496 }
3497 .into()
3498 );
3499
3500 if let Some(out_of_range) =
3503 proposers.iter().find(|i| **i >= context.committee_size)
3504 {
3505 return Err(UserInputError::InvalidExpiration {
3506 error: format!(
3507 "allowed_proposers contains index {out_of_range}, but the \
3508 committee has {} members",
3509 context.committee_size,
3510 ),
3511 }
3512 .into());
3513 }
3514 }
3515
3516 if min_timestamp.is_some() || max_timestamp.is_some() {
3517 return Err(UserInputError::Unsupported(
3518 "Timestamp-based transaction expiration is not yet supported".to_string(),
3519 )
3520 .into());
3521 }
3522
3523 match (min_epoch, max_epoch) {
3528 _ if config.relax_valid_during_for_owned_inputs() => (),
3529 (Some(min), Some(max)) => {
3530 if config.enable_multi_epoch_transaction_expiration() {
3531 if !(*max == *min || *max == min.saturating_add(1)) {
3532 return Err(UserInputError::Unsupported(
3533 "max_epoch must be at most min_epoch + 1".to_string(),
3534 )
3535 .into());
3536 }
3537 } else if min != max {
3538 return Err(UserInputError::Unsupported(
3539 "min_epoch must equal max_epoch".to_string(),
3540 )
3541 .into());
3542 }
3543 }
3544 _ => {
3545 return Err(UserInputError::Unsupported(
3546 "Both min_epoch and max_epoch must be specified".to_string(),
3547 )
3548 .into());
3549 }
3550 }
3551
3552 if *chain != context.chain_identifier {
3553 return Err(UserInputError::InvalidChainId {
3554 provided: format!("{:?}", chain),
3555 expected: format!("{:?}", context.chain_identifier),
3556 }
3557 .into());
3558 }
3559
3560 if let Some(min) = min_epoch
3561 && context.epoch < *min
3562 {
3563 return Err(SuiErrorKind::TransactionExpired.into());
3564 }
3565 if let Some(max) = max_epoch
3566 && context.epoch > *max
3567 {
3568 return Err(SuiErrorKind::TransactionExpired.into());
3569 }
3570 }
3571 }
3572
3573 if self.has_funds_withdrawals() {
3574 fp_ensure!(
3577 !self.gas().is_empty() || config.enable_address_balance_gas_payments(),
3578 UserInputError::MissingGasPayment.into()
3579 );
3580
3581 fp_ensure!(
3582 config.enable_accumulators(),
3583 UserInputError::Unsupported("Address balance withdraw is not enabled".to_string())
3584 .into()
3585 );
3586
3587 let max_withdraws = 10;
3589 let mut num_reservations = 0;
3590
3591 for withdraw in self.kind.get_funds_withdrawals() {
3592 num_reservations += 1;
3593 match withdraw.withdraw_from {
3594 WithdrawFrom::Sender => (),
3595 WithdrawFrom::Sponsor => {
3596 return Err(UserInputError::InvalidWithdrawReservation {
3597 error: "Explicit sponsor withdrawals are not yet supported".to_string(),
3598 }
3599 .into());
3600 }
3601 WithdrawFrom::SenderAllowance { .. } => {
3604 fp_ensure!(
3605 config.enable_allowances(),
3606 UserInputError::Unsupported(
3607 "Allowance withdrawals are not enabled".to_string()
3608 )
3609 .into()
3610 );
3611 }
3612 }
3613
3614 match withdraw.reservation {
3615 Reservation::MaxAmountU64(amount) => {
3616 fp_ensure!(
3617 amount > 0,
3618 UserInputError::InvalidWithdrawReservation {
3619 error: "Balance withdraw reservation amount must be non-zero"
3620 .to_string(),
3621 }
3622 .into()
3623 );
3624 }
3625 };
3626 }
3627
3628 for parsed in self.parsed_coin_reservations(context.chain_identifier) {
3629 num_reservations += 1;
3630 if parsed.epoch_id() != context.epoch && parsed.epoch_id() + 1 != context.epoch {
3634 return Err(SuiErrorKind::TransactionExpired.into());
3635 }
3636 if parsed.reservation_amount() == 0 {
3637 return Err(UserInputError::InvalidWithdrawReservation {
3638 error: "Balance withdraw reservation amount must be non-zero".to_string(),
3639 }
3640 .into());
3641 }
3642 }
3643
3644 if config.enable_address_balance_gas_payments()
3646 && self.is_gas_paid_from_address_balance()
3647 {
3648 num_reservations += 1;
3649 }
3650
3651 fp_ensure!(
3652 num_reservations <= max_withdraws,
3653 UserInputError::InvalidWithdrawReservation {
3654 error: format!(
3655 "Maximum number of balance withdraw reservations is {max_withdraws}"
3656 ),
3657 }
3658 .into()
3659 );
3660 }
3661
3662 if config.enable_accumulators()
3663 && config.enable_address_balance_gas_payments()
3664 && self.is_gas_paid_from_address_balance()
3665 {
3666 if config.address_balance_gas_reject_gas_coin_arg()
3667 && let TransactionKind::ProgrammableTransaction(pt) = &self.kind
3668 {
3669 fp_ensure!(
3670 !pt.commands.iter().any(|cmd| cmd.is_gas_coin_used()),
3671 UserInputError::Unsupported(
3672 "Argument::GasCoin is not supported with address balance gas payments"
3673 .to_string(),
3674 )
3675 .into()
3676 );
3677 }
3678
3679 let is_gasless = config.enable_gasless() && self.is_gasless_transaction();
3680 if config.address_balance_gas_check_rgp_at_signing() && !is_gasless {
3681 fp_ensure!(
3682 self.gas_data.price >= context.reference_gas_price,
3683 UserInputError::GasPriceUnderRGP {
3684 gas_price: self.gas_data.price,
3685 reference_gas_price: context.reference_gas_price,
3686 }
3687 .into()
3688 );
3689 }
3690
3691 if !config.relax_valid_during_for_owned_inputs() {
3696 if matches!(self.expiration(), TransactionExpiration::None) {
3697 return Err(UserInputError::MissingGasPayment.into());
3700 }
3701
3702 if !self.expiration().is_replay_protected() {
3703 return Err(UserInputError::InvalidExpiration {
3704 error: "Address balance gas payments require ValidDuring expiration"
3705 .to_string(),
3706 }
3707 .into());
3708 }
3709 }
3710 } else {
3711 fp_ensure!(
3712 !self.gas().is_empty(),
3713 UserInputError::MissingGasPayment.into()
3714 );
3715 }
3716
3717 let gas_len = self.gas().len();
3718 let max_gas_objects = config.max_gas_payment_objects() as usize;
3719
3720 let within_limit = if config.correct_gas_payment_limit_check() {
3721 gas_len <= max_gas_objects
3722 } else {
3723 gas_len < max_gas_objects
3724 };
3725
3726 fp_ensure!(
3727 within_limit,
3728 UserInputError::SizeLimitExceeded {
3729 limit: "maximum number of gas payment objects".to_string(),
3730 value: config.max_gas_payment_objects().to_string()
3731 }
3732 .into()
3733 );
3734
3735 if !config.enable_coin_reservation_obj_refs() {
3736 for (_, _, gas_digest) in self.gas() {
3737 fp_ensure!(
3738 !ParsedDigest::is_coin_reservation_digest(gas_digest),
3739 UserInputError::GasObjectNotOwnedObject {
3740 owner: Owner::AddressOwner(self.sender)
3741 }
3742 .into()
3743 );
3744 }
3745 } else {
3746 let sui_accumulator_id =
3749 *AccumulatorValue::get_field_id(self.sender, &Balance::type_tag(GAS::type_tag()))?
3750 .inner();
3751
3752 for gas_ref in self.gas() {
3753 if let Some(parsed) =
3754 ParsedObjectRefWithdrawal::parse(gas_ref, context.chain_identifier)
3755 {
3756 fp_ensure!(
3759 self.gas_owner() == self.sender,
3760 UserInputError::GasObjectNotOwnedObject {
3761 owner: Owner::AddressOwner(self.sender)
3762 }
3763 .into()
3764 );
3765 fp_ensure!(
3766 parsed.unmasked_object_id == sui_accumulator_id,
3767 UserInputError::GasObjectNotOwnedObject {
3768 owner: Owner::AddressOwner(self.sender)
3769 }
3770 .into()
3771 );
3772 }
3773 }
3774 }
3775
3776 if !self.is_system_tx() {
3777 fp_ensure!(
3778 !check_for_gas_price_too_high(config.gas_model_version())
3779 || self.gas_data.price < config.max_gas_price(),
3780 UserInputError::GasPriceTooHigh {
3781 max_gas_price: config.max_gas_price(),
3782 }
3783 .into()
3784 );
3785 let cost_table = SuiCostTable::new(config, self.gas_data.price);
3786
3787 fp_ensure!(
3788 self.gas_data.budget <= cost_table.max_gas_budget,
3789 UserInputError::GasBudgetTooHigh {
3790 gas_budget: self.gas_data().budget,
3791 max_budget: cost_table.max_gas_budget,
3792 }
3793 .into()
3794 );
3795 let is_gasless = config.enable_gasless() && self.is_gasless_transaction();
3796 if is_gasless {
3797 fp_ensure!(
3798 self.gas_data.budget == 0,
3799 UserInputError::Unsupported(
3800 "gas_budget must be 0 for gasless transactions".to_string()
3801 )
3802 .into()
3803 );
3804 } else {
3805 fp_ensure!(
3806 self.gas_data.budget >= cost_table.min_transaction_cost,
3807 UserInputError::GasBudgetTooLow {
3808 gas_budget: self.gas_data.budget,
3809 min_budget: cost_table.min_transaction_cost,
3810 }
3811 .into()
3812 );
3813 }
3814 }
3815
3816 self.kind().validity_check(config)?;
3817
3818 if config.enable_gasless() && self.is_gasless_transaction() {
3819 let TransactionKind::ProgrammableTransaction(pt) = &self.kind else {
3820 debug_fatal!("gasless transaction is not a ProgrammableTransaction");
3821 return Err(UserInputError::Unsupported(
3822 "Gasless transactions must be programmable transactions".to_string(),
3823 )
3824 .into());
3825 };
3826 pt.validate_gasless_transaction(config)?;
3827 }
3828
3829 self.check_sponsorship()?;
3830 Ok(())
3831 }
3832
3833 fn is_sponsored_tx(&self) -> bool {
3835 self.gas_owner() != self.sender
3836 }
3837
3838 fn is_gas_paid_from_address_balance(&self) -> bool {
3842 is_gas_paid_from_address_balance(&self.gas_data, &self.kind)
3843 }
3844
3845 fn is_gasless_transaction(&self) -> bool {
3846 is_gasless_transaction(&self.gas_data, &self.kind)
3847 }
3848
3849 fn check_sponsorship(&self) -> UserInputResult {
3851 if self.gas_owner() == self.sender() {
3853 return Ok(());
3854 }
3855 if matches!(&self.kind, TransactionKind::ProgrammableTransaction(_)) {
3856 return Ok(());
3857 }
3858 Err(UserInputError::UnsupportedSponsoredTransactionKind)
3859 }
3860
3861 fn is_end_of_epoch_tx(&self) -> bool {
3862 matches!(
3863 self.kind,
3864 TransactionKind::ChangeEpoch(_) | TransactionKind::EndOfEpochTransaction(_)
3865 )
3866 }
3867
3868 fn is_consensus_commit_prologue(&self) -> bool {
3869 match &self.kind {
3870 TransactionKind::ConsensusCommitPrologue(_)
3871 | TransactionKind::ConsensusCommitPrologueV2(_)
3872 | TransactionKind::ConsensusCommitPrologueV3(_)
3873 | TransactionKind::ConsensusCommitPrologueV4(_) => true,
3874
3875 TransactionKind::ProgrammableTransaction(_)
3876 | TransactionKind::ProgrammableSystemTransaction(_)
3877 | TransactionKind::ChangeEpoch(_)
3878 | TransactionKind::Genesis(_)
3879 | TransactionKind::AuthenticatorStateUpdate(_)
3880 | TransactionKind::EndOfEpochTransaction(_)
3881 | TransactionKind::RandomnessStateUpdate(_) => false,
3882 }
3883 }
3884
3885 fn is_system_tx(&self) -> bool {
3886 self.kind.is_system_tx()
3887 }
3888
3889 fn is_genesis_tx(&self) -> bool {
3890 matches!(self.kind, TransactionKind::Genesis(_))
3891 }
3892
3893 fn sender_mut_for_testing(&mut self) -> &mut SuiAddress {
3894 &mut self.sender
3895 }
3896
3897 fn gas_data_mut(&mut self) -> &mut GasData {
3898 &mut self.gas_data
3899 }
3900
3901 fn expiration_mut_for_testing(&mut self) -> &mut TransactionExpiration {
3902 &mut self.expiration
3903 }
3904}
3905
3906impl TransactionDataV1 {
3907 fn accumulate_funds_withdrawals(
3908 &self,
3909 chain_identifier: ChainIdentifier,
3910 coin_resolver: &dyn CoinReservationResolverTrait,
3911 include_gas_payment: bool,
3912 ) -> UserInputResult<BTreeMap<AccumulatorObjId, (u64, TypeTag, SuiAddress)>> {
3913 let mut withdraws: Vec<_> = self.get_funds_withdrawals().collect();
3914
3915 for withdraw in self.parsed_coin_reservations(chain_identifier) {
3916 let withdrawal_arg =
3917 coin_resolver.resolve_funds_withdrawal(self.sender(), withdraw, None)?;
3918 withdraws.push(withdrawal_arg);
3919 }
3920
3921 if include_gas_payment {
3922 withdraws.extend(self.get_funds_withdrawal_for_gas_payment());
3923 }
3924
3925 let mut withdraw_map: BTreeMap<AccumulatorObjId, (u64, TypeTag, SuiAddress)> =
3926 BTreeMap::new();
3927 for withdraw in withdraws {
3928 let reserved_amount = match &withdraw.reservation {
3929 Reservation::MaxAmountU64(amount) => {
3930 if *amount == 0 {
3931 return Err(UserInputError::InvalidWithdrawReservation {
3932 error: "Balance withdraw reservation amount must be non-zero"
3933 .to_string(),
3934 });
3935 }
3936 *amount
3937 }
3938 };
3939
3940 let account_address = withdraw.owner_for_withdrawal(self);
3941 let type_tag = withdraw.type_arg.to_type_tag();
3942 let account_id =
3943 AccumulatorValue::get_field_id(account_address, &type_tag).map_err(|e| {
3944 UserInputError::InvalidWithdrawReservation {
3945 error: e.to_string(),
3946 }
3947 })?;
3948
3949 let (current_amount, _, _) = withdraw_map
3950 .entry(account_id)
3951 .or_insert_with(|| (0, type_tag, account_address));
3952 *current_amount = current_amount.checked_add(reserved_amount).ok_or(
3953 UserInputError::InvalidWithdrawReservation {
3954 error: "Balance withdraw reservation overflow".to_string(),
3955 },
3956 )?;
3957 }
3958
3959 Ok(withdraw_map)
3960 }
3961
3962 fn get_funds_withdrawal_for_gas_payment(&self) -> Option<FundsWithdrawalArg> {
3963 if self.is_gas_paid_from_address_balance() && self.gas_data().budget > 0 {
3964 Some(if self.sender() != self.gas_owner() {
3965 FundsWithdrawalArg::balance_from_sponsor(self.gas_data().budget, GAS::type_tag())
3966 } else {
3967 FundsWithdrawalArg::balance_from_sender(self.gas_data().budget, GAS::type_tag())
3968 })
3969 } else {
3970 None
3971 }
3972 }
3973
3974 fn get_funds_withdrawals(&self) -> impl Iterator<Item = FundsWithdrawalArg> + '_ {
3975 self.kind.get_funds_withdrawals().cloned()
3976 }
3977
3978 fn coin_reservation_obj_refs(&self) -> impl Iterator<Item = ObjectRef> {
3979 self.kind
3980 .get_coin_reservation_obj_refs()
3981 .chain(self.gas().iter().filter_map(|gas_ref| {
3982 if ParsedDigest::is_coin_reservation_digest(&gas_ref.2) {
3983 Some(*gas_ref)
3984 } else {
3985 None
3986 }
3987 }))
3988 }
3989
3990 fn parsed_coin_reservations(
3991 &self,
3992 chain_identifier: ChainIdentifier,
3993 ) -> impl Iterator<Item = ParsedObjectRefWithdrawal> {
3994 self.coin_reservation_obj_refs().map(move |obj_ref| {
3995 ParsedObjectRefWithdrawal::parse(&obj_ref, chain_identifier).unwrap()
3996 })
3997 }
3998}
3999
4000pub struct TxValidityCheckContext<'a> {
4001 pub config: &'a ProtocolConfig,
4002 pub epoch: EpochId,
4003 pub chain_identifier: ChainIdentifier,
4004 pub reference_gas_price: u64,
4005 pub committee_size: u32,
4007}
4008
4009impl<'a> TxValidityCheckContext<'a> {
4010 pub fn from_cfg_for_testing(config: &'a ProtocolConfig) -> Self {
4011 Self {
4012 config,
4013 epoch: 0,
4014 chain_identifier: ChainIdentifier::default(),
4015 reference_gas_price: 1000,
4016 committee_size: 4,
4017 }
4018 }
4019}
4020
4021#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
4022pub struct SenderSignedData(SizeOneVec<SenderSignedTransaction>);
4023
4024#[derive(Debug, Clone, PartialEq, Eq, Hash)]
4025pub struct SenderSignedTransaction {
4026 pub intent_message: IntentMessage<TransactionData>,
4027 pub tx_signatures: Vec<GenericSignature>,
4031}
4032
4033impl Serialize for SenderSignedTransaction {
4034 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
4035 where
4036 S: serde::Serializer,
4037 {
4038 #[derive(Serialize)]
4039 #[serde(rename = "SenderSignedTransaction")]
4040 struct SignedTxn<'a> {
4041 intent_message: &'a IntentMessage<TransactionData>,
4042 tx_signatures: &'a Vec<GenericSignature>,
4043 }
4044
4045 if self.intent_message().intent != Intent::sui_transaction() {
4046 return Err(serde::ser::Error::custom("invalid Intent for Transaction"));
4047 }
4048
4049 let txn = SignedTxn {
4050 intent_message: self.intent_message(),
4051 tx_signatures: &self.tx_signatures,
4052 };
4053 txn.serialize(serializer)
4054 }
4055}
4056
4057impl<'de> Deserialize<'de> for SenderSignedTransaction {
4058 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
4059 where
4060 D: serde::Deserializer<'de>,
4061 {
4062 #[derive(Deserialize)]
4063 #[serde(rename = "SenderSignedTransaction")]
4064 struct SignedTxn {
4065 intent_message: IntentMessage<TransactionData>,
4066 tx_signatures: Vec<GenericSignature>,
4067 }
4068
4069 let SignedTxn {
4070 intent_message,
4071 tx_signatures,
4072 } = Deserialize::deserialize(deserializer)?;
4073
4074 if intent_message.intent != Intent::sui_transaction() {
4075 return Err(serde::de::Error::custom("invalid Intent for Transaction"));
4076 }
4077
4078 Ok(Self {
4079 intent_message,
4080 tx_signatures,
4081 })
4082 }
4083}
4084
4085impl SenderSignedTransaction {
4086 pub(crate) fn get_signer_sig_mapping(
4088 &self,
4089 verify_legacy_zklogin_address: bool,
4090 ) -> SuiResult<BTreeMap<SuiAddress, (u8, &GenericSignature)>> {
4091 let mut mapping = BTreeMap::new();
4092 for (idx, sig) in self.tx_signatures.iter().enumerate() {
4093 if verify_legacy_zklogin_address && let GenericSignature::ZkLoginAuthenticator(z) = sig
4094 {
4095 mapping.insert(SuiAddress::try_from_padded(&z.inputs)?, (idx as u8, sig));
4097 }
4098 let address = sig.try_into()?;
4099 mapping.insert(address, (idx as u8, sig));
4100 }
4101 Ok(mapping)
4102 }
4103
4104 pub fn intent_message(&self) -> &IntentMessage<TransactionData> {
4105 &self.intent_message
4106 }
4107}
4108
4109fn mldsa65_multisig_unsupported() -> SuiError {
4113 SuiErrorKind::UserInputError {
4114 error: UserInputError::Unsupported(
4115 "ML-DSA-65 multisig members are not enabled on this network".to_string(),
4116 ),
4117 }
4118 .into()
4119}
4120
4121impl SenderSignedData {
4122 pub fn new(tx_data: TransactionData, tx_signatures: Vec<GenericSignature>) -> Self {
4123 Self(SizeOneVec::new(SenderSignedTransaction {
4124 intent_message: IntentMessage::new(Intent::sui_transaction(), tx_data),
4125 tx_signatures,
4126 }))
4127 }
4128
4129 pub fn new_from_sender_signature(tx_data: TransactionData, tx_signature: Signature) -> Self {
4130 Self(SizeOneVec::new(SenderSignedTransaction {
4131 intent_message: IntentMessage::new(Intent::sui_transaction(), tx_data),
4132 tx_signatures: vec![tx_signature.into()],
4133 }))
4134 }
4135
4136 pub fn inner(&self) -> &SenderSignedTransaction {
4137 self.0.element()
4138 }
4139
4140 pub fn into_inner(self) -> SenderSignedTransaction {
4141 self.0.into_inner()
4142 }
4143
4144 pub fn inner_mut(&mut self) -> &mut SenderSignedTransaction {
4145 self.0.element_mut()
4146 }
4147
4148 pub fn add_signature(&mut self, new_signature: Signature) {
4151 self.inner_mut().tx_signatures.push(new_signature.into());
4152 }
4153
4154 pub(crate) fn get_signer_sig_mapping(
4155 &self,
4156 verify_legacy_zklogin_address: bool,
4157 ) -> SuiResult<BTreeMap<SuiAddress, (u8, &GenericSignature)>> {
4158 self.inner()
4159 .get_signer_sig_mapping(verify_legacy_zklogin_address)
4160 }
4161
4162 pub fn transaction_data(&self) -> &TransactionData {
4163 &self.intent_message().value
4164 }
4165
4166 pub fn intent_message(&self) -> &IntentMessage<TransactionData> {
4167 self.inner().intent_message()
4168 }
4169
4170 pub fn tx_signatures(&self) -> &[GenericSignature] {
4171 &self.inner().tx_signatures
4172 }
4173
4174 pub fn has_zklogin_sig(&self) -> bool {
4175 self.tx_signatures().iter().any(|sig| sig.is_zklogin())
4176 }
4177
4178 pub fn has_upgraded_multisig(&self) -> bool {
4179 self.tx_signatures()
4180 .iter()
4181 .any(|sig| sig.is_upgraded_multisig())
4182 }
4183
4184 #[cfg(test)]
4185 pub fn intent_message_mut_for_testing(&mut self) -> &mut IntentMessage<TransactionData> {
4186 &mut self.inner_mut().intent_message
4187 }
4188
4189 pub fn tx_signatures_mut_for_testing(&mut self) -> &mut Vec<GenericSignature> {
4191 &mut self.inner_mut().tx_signatures
4192 }
4193
4194 pub fn full_message_digest_with_alias_versions(
4196 &self,
4197 alias_versions: &Vec<(SuiAddress, Option<SequenceNumber>)>,
4198 ) -> SenderSignedDataDigest {
4199 let mut digest = DefaultHash::default();
4200 bcs::serialize_into(&mut digest, self).expect("serialization should not fail");
4201 bcs::serialize_into(&mut digest, alias_versions).expect("serialization should not fail");
4202 let hash = digest.finalize();
4203 SenderSignedDataDigest::new(hash.into())
4204 }
4205
4206 pub fn serialized_size(&self) -> SuiResult<usize> {
4207 bcs::serialized_size(self).map_err(|e| {
4208 SuiErrorKind::TransactionSerializationError {
4209 error: e.to_string(),
4210 }
4211 .into()
4212 })
4213 }
4214
4215 fn check_user_signature_protocol_compatibility(&self, config: &ProtocolConfig) -> SuiResult {
4216 for sig in &self.inner().tx_signatures {
4217 match sig {
4218 GenericSignature::MultiSig(multisig) => {
4219 if !config.upgraded_multisig_supported() {
4220 return Err(SuiErrorKind::UserInputError {
4221 error: UserInputError::Unsupported(
4222 "upgraded multisig format not enabled on this network".to_string(),
4223 ),
4224 }
4225 .into());
4226 }
4227 if multisig.uses_mldsa65() && !config.mldsa65_auth() {
4228 return Err(mldsa65_multisig_unsupported());
4229 }
4230 }
4231 GenericSignature::ZkLoginAuthenticator(_) => {
4232 if !config.zklogin_auth() {
4233 return Err(SuiErrorKind::UserInputError {
4234 error: UserInputError::Unsupported(
4235 "zklogin is not enabled on this network".to_string(),
4236 ),
4237 }
4238 .into());
4239 }
4240 }
4241 GenericSignature::PasskeyAuthenticator(_) => {
4242 if !config.passkey_auth() {
4243 return Err(SuiErrorKind::UserInputError {
4244 error: UserInputError::Unsupported(
4245 "passkey is not enabled on this network".to_string(),
4246 ),
4247 }
4248 .into());
4249 }
4250 }
4251 GenericSignature::Signature(_) => {
4252 if sig.is_mldsa65() && !config.mldsa65_auth() {
4254 return Err(SuiErrorKind::UserInputError {
4255 error: UserInputError::Unsupported(
4256 "ML-DSA-65 signatures are not enabled on this network".to_string(),
4257 ),
4258 }
4259 .into());
4260 }
4261 }
4262 GenericSignature::MultiSigLegacy(multisig) => {
4263 if multisig.uses_mldsa65() && !config.mldsa65_auth() {
4264 return Err(mldsa65_multisig_unsupported());
4265 }
4266 }
4267 }
4268 }
4269
4270 Ok(())
4271 }
4272
4273 pub fn validity_check(&self, context: &TxValidityCheckContext<'_>) -> Result<usize, SuiError> {
4276 self.check_user_signature_protocol_compatibility(context.config)?;
4278
4279 let tx_data = &self.transaction_data();
4284 fp_ensure!(
4285 !tx_data.is_system_tx(),
4286 SuiErrorKind::UserInputError {
4287 error: UserInputError::Unsupported(
4288 "SenderSignedData must not contain system transaction".to_string()
4289 )
4290 }
4291 .into()
4292 );
4293
4294 let tx_size = self.serialized_size()?;
4296 let max_tx_size_bytes = context.config.max_tx_size_bytes();
4297 fp_ensure!(
4298 tx_size as u64 <= max_tx_size_bytes,
4299 SuiErrorKind::UserInputError {
4300 error: UserInputError::SizeLimitExceeded {
4301 limit: format!(
4302 "serialized transaction size exceeded maximum of {max_tx_size_bytes}"
4303 ),
4304 value: tx_size.to_string(),
4305 }
4306 }
4307 .into()
4308 );
4309
4310 if context.config.enable_gasless() && tx_data.is_gasless_transaction() {
4311 let gasless_max = context.config.get_gasless_max_tx_size_bytes();
4312 fp_ensure!(
4313 tx_size as u64 <= gasless_max,
4314 SuiErrorKind::UserInputError {
4315 error: UserInputError::SizeLimitExceeded {
4316 limit: format!(
4317 "serialized gasless transaction size exceeded maximum of {gasless_max}"
4318 ),
4319 value: tx_size.to_string(),
4320 }
4321 }
4322 .into()
4323 );
4324 }
4325
4326 tx_data.validity_check(context)?;
4327
4328 Ok(tx_size)
4329 }
4330}
4331
4332impl Message for SenderSignedData {
4333 type DigestType = TransactionDigest;
4334 const SCOPE: IntentScope = IntentScope::SenderSignedTransaction;
4335
4336 fn digest(&self) -> Self::DigestType {
4338 self.intent_message().value.digest()
4339 }
4340}
4341
4342impl<S> Envelope<SenderSignedData, S> {
4343 pub fn sender_address(&self) -> SuiAddress {
4344 self.data().intent_message().value.sender()
4345 }
4346
4347 pub fn gas_owner(&self) -> SuiAddress {
4348 self.data().intent_message().value.gas_owner()
4349 }
4350
4351 pub fn gas(&self) -> &[ObjectRef] {
4352 self.data().intent_message().value.gas()
4353 }
4354
4355 pub fn is_consensus_tx(&self) -> bool {
4356 self.transaction_data().has_funds_withdrawals()
4357 || self.shared_input_objects().next().is_some()
4358 }
4359
4360 pub fn shared_input_objects(&self) -> impl Iterator<Item = SharedInputObject> + '_ {
4361 self.data()
4362 .inner()
4363 .intent_message
4364 .value
4365 .shared_input_objects()
4366 .into_iter()
4367 }
4368
4369 pub fn key(&self) -> TransactionKey {
4371 match &self.data().intent_message().value.kind() {
4372 TransactionKind::RandomnessStateUpdate(rsu) => {
4373 TransactionKey::RandomnessRound(rsu.epoch, rsu.randomness_round)
4374 }
4375 _ => TransactionKey::Digest(*self.digest()),
4376 }
4377 }
4378
4379 pub fn non_digest_key(&self) -> Option<TransactionKey> {
4384 match &self.data().intent_message().value.kind() {
4385 TransactionKind::RandomnessStateUpdate(rsu) => Some(TransactionKey::RandomnessRound(
4386 rsu.epoch,
4387 rsu.randomness_round,
4388 )),
4389 _ => None,
4390 }
4391 }
4392
4393 pub fn is_system_tx(&self) -> bool {
4394 self.data().intent_message().value.is_system_tx()
4395 }
4396
4397 pub fn is_sponsored_tx(&self) -> bool {
4398 self.data().intent_message().value.is_sponsored_tx()
4399 }
4400}
4401
4402impl Transaction {
4403 pub fn from_data_and_signer(
4404 data: TransactionData,
4405 signers: Vec<&dyn Signer<Signature>>,
4406 ) -> Self {
4407 let signatures = {
4408 let intent_msg = IntentMessage::new(Intent::sui_transaction(), &data);
4409 signers
4410 .into_iter()
4411 .map(|s| Signature::new_secure(&intent_msg, s))
4412 .collect()
4413 };
4414 Self::from_data(data, signatures)
4415 }
4416
4417 pub fn from_data(data: TransactionData, signatures: Vec<Signature>) -> Self {
4419 Self::from_generic_sig_data(data, signatures.into_iter().map(|s| s.into()).collect())
4420 }
4421
4422 pub fn signature_from_signer(
4423 data: TransactionData,
4424 intent: Intent,
4425 signer: &dyn Signer<Signature>,
4426 ) -> Signature {
4427 let intent_msg = IntentMessage::new(intent, data);
4428 Signature::new_secure(&intent_msg, signer)
4429 }
4430
4431 pub fn from_generic_sig_data(data: TransactionData, signatures: Vec<GenericSignature>) -> Self {
4432 Self::new(SenderSignedData::new(data, signatures))
4433 }
4434
4435 pub fn to_tx_bytes_and_signatures(&self) -> (Base64, Vec<Base64>) {
4438 (
4439 Base64::from_bytes(&bcs::to_bytes(&self.data().intent_message().value).unwrap()),
4440 self.data()
4441 .inner()
4442 .tx_signatures
4443 .iter()
4444 .map(|s| Base64::from_bytes(s.as_ref()))
4445 .collect(),
4446 )
4447 }
4448}
4449
4450impl VerifiedTransaction {
4451 pub fn new_change_epoch(
4452 next_epoch: EpochId,
4453 protocol_version: ProtocolVersion,
4454 storage_charge: u64,
4455 computation_charge: u64,
4456 storage_rebate: u64,
4457 non_refundable_storage_fee: u64,
4458 epoch_start_timestamp_ms: u64,
4459 system_packages: Vec<(SequenceNumber, Vec<Vec<u8>>, Vec<ObjectID>)>,
4460 ) -> Self {
4461 ChangeEpoch {
4462 epoch: next_epoch,
4463 protocol_version,
4464 storage_charge,
4465 computation_charge,
4466 storage_rebate,
4467 non_refundable_storage_fee,
4468 epoch_start_timestamp_ms,
4469 system_packages,
4470 }
4471 .pipe(TransactionKind::ChangeEpoch)
4472 .pipe(Self::new_system_transaction)
4473 }
4474
4475 pub fn new_genesis_transaction(objects: Vec<GenesisObject>) -> Self {
4476 GenesisTransaction { objects }
4477 .pipe(TransactionKind::Genesis)
4478 .pipe(Self::new_system_transaction)
4479 }
4480
4481 pub fn new_consensus_commit_prologue(
4482 epoch: u64,
4483 round: u64,
4484 commit_timestamp_ms: CheckpointTimestamp,
4485 ) -> Self {
4486 ConsensusCommitPrologue {
4487 epoch,
4488 round,
4489 commit_timestamp_ms,
4490 }
4491 .pipe(TransactionKind::ConsensusCommitPrologue)
4492 .pipe(Self::new_system_transaction)
4493 }
4494
4495 pub fn new_consensus_commit_prologue_v2(
4496 epoch: u64,
4497 round: u64,
4498 commit_timestamp_ms: CheckpointTimestamp,
4499 consensus_commit_digest: ConsensusCommitDigest,
4500 ) -> Self {
4501 ConsensusCommitPrologueV2 {
4502 epoch,
4503 round,
4504 commit_timestamp_ms,
4505 consensus_commit_digest,
4506 }
4507 .pipe(TransactionKind::ConsensusCommitPrologueV2)
4508 .pipe(Self::new_system_transaction)
4509 }
4510
4511 pub fn new_consensus_commit_prologue_v3(
4512 epoch: u64,
4513 round: u64,
4514 commit_timestamp_ms: CheckpointTimestamp,
4515 consensus_commit_digest: ConsensusCommitDigest,
4516 consensus_determined_version_assignments: ConsensusDeterminedVersionAssignments,
4517 ) -> Self {
4518 ConsensusCommitPrologueV3 {
4519 epoch,
4520 round,
4521 sub_dag_index: None,
4523 commit_timestamp_ms,
4524 consensus_commit_digest,
4525 consensus_determined_version_assignments,
4526 }
4527 .pipe(TransactionKind::ConsensusCommitPrologueV3)
4528 .pipe(Self::new_system_transaction)
4529 }
4530
4531 pub fn new_consensus_commit_prologue_v4(
4532 epoch: u64,
4533 round: u64,
4534 commit_timestamp_ms: CheckpointTimestamp,
4535 consensus_commit_digest: ConsensusCommitDigest,
4536 consensus_determined_version_assignments: ConsensusDeterminedVersionAssignments,
4537 additional_state_digest: AdditionalConsensusStateDigest,
4538 ) -> Self {
4539 ConsensusCommitPrologueV4 {
4540 epoch,
4541 round,
4542 sub_dag_index: None,
4544 commit_timestamp_ms,
4545 consensus_commit_digest,
4546 consensus_determined_version_assignments,
4547 additional_state_digest,
4548 }
4549 .pipe(TransactionKind::ConsensusCommitPrologueV4)
4550 .pipe(Self::new_system_transaction)
4551 }
4552
4553 pub fn new_authenticator_state_update(
4554 epoch: u64,
4555 round: u64,
4556 new_active_jwks: Vec<ActiveJwk>,
4557 authenticator_obj_initial_shared_version: SequenceNumber,
4558 ) -> Self {
4559 AuthenticatorStateUpdate {
4560 epoch,
4561 round,
4562 new_active_jwks,
4563 authenticator_obj_initial_shared_version,
4564 }
4565 .pipe(TransactionKind::AuthenticatorStateUpdate)
4566 .pipe(Self::new_system_transaction)
4567 }
4568
4569 pub fn new_randomness_state_update(
4570 epoch: u64,
4571 randomness_round: RandomnessRound,
4572 random_bytes: Vec<u8>,
4573 randomness_obj_initial_shared_version: SequenceNumber,
4574 ) -> Self {
4575 RandomnessStateUpdate {
4576 epoch,
4577 randomness_round,
4578 random_bytes,
4579 randomness_obj_initial_shared_version,
4580 }
4581 .pipe(TransactionKind::RandomnessStateUpdate)
4582 .pipe(Self::new_system_transaction)
4583 }
4584
4585 pub fn new_end_of_epoch_transaction(txns: Vec<EndOfEpochTransactionKind>) -> Self {
4586 TransactionKind::EndOfEpochTransaction(txns).pipe(Self::new_system_transaction)
4587 }
4588
4589 pub fn new_system_transaction(system_transaction: TransactionKind) -> Self {
4590 system_transaction
4591 .pipe(TransactionData::new_system_transaction)
4592 .pipe(|data| {
4593 SenderSignedData::new_from_sender_signature(
4594 data,
4595 Ed25519SuiSignature::from_bytes(&[0; Ed25519SuiSignature::LENGTH])
4596 .unwrap()
4597 .into(),
4598 )
4599 })
4600 .pipe(Transaction::new)
4601 .pipe(Self::new_from_verified)
4602 }
4603}
4604
4605impl VerifiedSignedTransaction {
4606 pub fn new(
4608 epoch: EpochId,
4609 transaction: VerifiedTransaction,
4610 authority: AuthorityName,
4611 secret: &dyn Signer<AuthoritySignature>,
4612 ) -> Self {
4613 Self::new_from_verified(SignedTransaction::new(
4614 epoch,
4615 transaction.into_inner().into_data(),
4616 secret,
4617 authority,
4618 ))
4619 }
4620}
4621
4622pub type Transaction = Envelope<SenderSignedData, EmptySignInfo>;
4624pub type VerifiedTransaction = VerifiedEnvelope<SenderSignedData, EmptySignInfo>;
4625pub type TrustedTransaction = TrustedEnvelope<SenderSignedData, EmptySignInfo>;
4626
4627pub type SignedTransaction = Envelope<SenderSignedData, AuthoritySignInfo>;
4629pub type VerifiedSignedTransaction = VerifiedEnvelope<SenderSignedData, AuthoritySignInfo>;
4630
4631impl Transaction {
4632 pub fn verify_signature_for_testing(
4633 &self,
4634 current_epoch: EpochId,
4635 verify_params: &VerifyParams,
4636 ) -> SuiResult {
4637 verify_sender_signed_data_message_signatures(
4638 self.data(),
4639 current_epoch,
4640 verify_params,
4641 Arc::new(VerifiedDigestCache::new_empty()),
4642 vec![],
4643 )?;
4644 Ok(())
4645 }
4646
4647 pub fn try_into_verified_for_testing(
4648 self,
4649 current_epoch: EpochId,
4650 verify_params: &VerifyParams,
4651 ) -> SuiResult<VerifiedTransaction> {
4652 self.verify_signature_for_testing(current_epoch, verify_params)?;
4653 Ok(VerifiedTransaction::new_from_verified(self))
4654 }
4655}
4656
4657impl SignedTransaction {
4658 pub fn verify_signatures_authenticated_for_testing(
4659 &self,
4660 committee: &Committee,
4661 verify_params: &VerifyParams,
4662 ) -> SuiResult {
4663 verify_sender_signed_data_message_signatures(
4664 self.data(),
4665 committee.epoch(),
4666 verify_params,
4667 Arc::new(VerifiedDigestCache::new_empty()),
4668 vec![],
4669 )?;
4670
4671 self.auth_sig().verify_secure(
4672 self.data(),
4673 Intent::sui_app(IntentScope::SenderSignedTransaction),
4674 committee,
4675 )
4676 }
4677
4678 pub fn try_into_verified_for_testing(
4679 self,
4680 committee: &Committee,
4681 verify_params: &VerifyParams,
4682 ) -> SuiResult<VerifiedSignedTransaction> {
4683 self.verify_signatures_authenticated_for_testing(committee, verify_params)?;
4684 Ok(VerifiedSignedTransaction::new_from_verified(self))
4685 }
4686}
4687
4688pub type CertifiedTransaction = Envelope<SenderSignedData, AuthorityStrongQuorumSignInfo>;
4689
4690impl CertifiedTransaction {
4691 pub fn gas_price(&self) -> u64 {
4692 self.data().transaction_data().gas_price()
4693 }
4694}
4695
4696pub type VerifiedCertificate = VerifiedEnvelope<SenderSignedData, AuthorityStrongQuorumSignInfo>;
4697pub type TrustedCertificate = TrustedEnvelope<SenderSignedData, AuthorityStrongQuorumSignInfo>;
4698
4699#[derive(Clone, Debug, Serialize, Deserialize)]
4700pub struct WithAliases<T>(
4701 T,
4702 #[serde(with = "nonempty_as_vec")] NonEmpty<(u8, Option<SequenceNumber>)>,
4703);
4704
4705impl<T> WithAliases<T> {
4706 pub fn new(tx: T, aliases: NonEmpty<(u8, Option<SequenceNumber>)>) -> Self {
4707 Self(tx, aliases)
4708 }
4709
4710 pub fn tx(&self) -> &T {
4711 &self.0
4712 }
4713
4714 pub fn aliases(&self) -> &NonEmpty<(u8, Option<SequenceNumber>)> {
4715 &self.1
4716 }
4717
4718 pub fn into_tx(self) -> T {
4719 self.0
4720 }
4721
4722 pub fn into_aliases(self) -> NonEmpty<(u8, Option<SequenceNumber>)> {
4723 self.1
4724 }
4725
4726 pub fn into_inner(self) -> (T, NonEmpty<(u8, Option<SequenceNumber>)>) {
4727 (self.0, self.1)
4728 }
4729}
4730
4731impl<T: Message, S> WithAliases<VerifiedEnvelope<T, S>> {
4732 pub fn serializable(self) -> WithAliases<TrustedEnvelope<T, S>> {
4734 WithAliases(self.0.serializable(), self.1)
4735 }
4736}
4737
4738impl<S> WithAliases<Envelope<SenderSignedData, S>> {
4739 pub fn no_aliases(tx: Envelope<SenderSignedData, S>) -> Self {
4742 let required_signers = tx.intent_message().value.required_signers();
4743 assert_eq!(required_signers.len(), tx.tx_signatures().len());
4744 let no_aliases = required_signers
4745 .iter()
4746 .enumerate()
4747 .map(|(idx, _)| (idx as u8, None))
4748 .collect::<Vec<_>>();
4749 Self::new(
4750 tx,
4751 NonEmpty::from_vec(no_aliases).expect("must have at least one required_signer"),
4752 )
4753 }
4754}
4755
4756impl<S> WithAliases<VerifiedEnvelope<SenderSignedData, S>> {
4757 pub fn no_aliases(tx: VerifiedEnvelope<SenderSignedData, S>) -> Self {
4760 let required_signers = tx.intent_message().value.required_signers();
4761 assert_eq!(required_signers.len(), tx.tx_signatures().len());
4762 let no_aliases = required_signers
4763 .iter()
4764 .enumerate()
4765 .map(|(idx, _)| (idx as u8, None))
4766 .collect::<Vec<_>>();
4767 Self::new(
4768 tx,
4769 NonEmpty::from_vec(no_aliases).expect("must have at least one required_signer"),
4770 )
4771 }
4772}
4773
4774pub type TransactionWithAliases = WithAliases<Transaction>;
4775pub type VerifiedTransactionWithAliases = WithAliases<VerifiedTransaction>;
4776pub type TrustedTransactionWithAliases = WithAliases<TrustedTransaction>;
4777
4778#[derive(Clone, Debug, Serialize, Deserialize)]
4783pub struct DeprecatedWithAliases<T>(
4784 T,
4785 #[serde(with = "nonempty_as_vec")] NonEmpty<(SuiAddress, Option<SequenceNumber>)>,
4786);
4787
4788impl<T> DeprecatedWithAliases<T> {
4789 pub fn into_inner(self) -> (T, NonEmpty<(SuiAddress, Option<SequenceNumber>)>) {
4790 (self.0, self.1)
4791 }
4792}
4793
4794impl<T: Message, S> From<WithAliases<VerifiedEnvelope<T, S>>> for WithAliases<Envelope<T, S>> {
4795 fn from(value: WithAliases<VerifiedEnvelope<T, S>>) -> Self {
4796 Self(value.0.into(), value.1)
4797 }
4798}
4799
4800impl<T: Message, S> From<WithAliases<TrustedEnvelope<T, S>>>
4801 for WithAliases<VerifiedEnvelope<T, S>>
4802{
4803 fn from(value: WithAliases<TrustedEnvelope<T, S>>) -> Self {
4804 Self(value.0.into(), value.1)
4805 }
4806}
4807
4808mod nonempty_as_vec {
4809 use super::*;
4810 use serde::{Deserialize, Deserializer, Serialize, Serializer};
4811
4812 pub fn serialize<S, T>(value: &NonEmpty<T>, serializer: S) -> Result<S::Ok, S::Error>
4813 where
4814 S: Serializer,
4815 T: Serialize,
4816 {
4817 let vec: Vec<&T> = value.iter().collect();
4818 vec.serialize(serializer)
4819 }
4820
4821 pub fn deserialize<'de, D, T>(deserializer: D) -> Result<NonEmpty<T>, D::Error>
4822 where
4823 D: Deserializer<'de>,
4824 T: Deserialize<'de> + Clone,
4825 {
4826 use serde::de::{SeqAccess, Visitor};
4827 use std::fmt;
4828 use std::marker::PhantomData;
4829
4830 struct NonEmptyVisitor<T>(PhantomData<T>);
4831
4832 impl<'de, T> Visitor<'de> for NonEmptyVisitor<T>
4833 where
4834 T: Deserialize<'de> + Clone,
4835 {
4836 type Value = NonEmpty<T>;
4837
4838 fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
4839 formatter.write_str("a non-empty sequence")
4840 }
4841
4842 fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
4843 where
4844 A: SeqAccess<'de>,
4845 {
4846 let head = seq
4847 .next_element()?
4848 .ok_or_else(|| serde::de::Error::custom("empty vector"))?;
4849
4850 let mut tail = Vec::new();
4851 while let Some(elem) = seq.next_element()? {
4852 tail.push(elem);
4853 }
4854
4855 Ok(NonEmpty { head, tail })
4856 }
4857 }
4858
4859 deserializer.deserialize_seq(NonEmptyVisitor(PhantomData))
4860 }
4861}
4862
4863#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
4873pub enum TransactionClaim {
4874 #[deprecated(note = "Use AddressAliasesV2")]
4876 AddressAliases(
4877 #[serde(with = "nonempty_as_vec")] NonEmpty<(SuiAddress, Option<SequenceNumber>)>,
4878 ),
4879
4880 ImmutableInputObjects(Vec<ObjectID>),
4883
4884 AddressAliasesV2(#[serde(with = "nonempty_as_vec")] NonEmpty<(u8, Option<SequenceNumber>)>),
4888}
4889
4890#[derive(Clone, Debug, Serialize, Deserialize)]
4892pub struct TransactionWithClaims<T> {
4893 tx: T,
4894 claims: Vec<TransactionClaim>,
4895}
4896
4897impl<T> TransactionWithClaims<T> {
4898 pub fn new(tx: T, claims: Vec<TransactionClaim>) -> Self {
4899 Self { tx, claims }
4900 }
4901
4902 pub fn from_aliases(tx: T, aliases: NonEmpty<(u8, Option<SequenceNumber>)>) -> Self {
4904 Self {
4905 tx,
4906 claims: vec![TransactionClaim::AddressAliasesV2(aliases)],
4907 }
4908 }
4909
4910 pub fn no_aliases(tx: T) -> Self {
4912 Self { tx, claims: vec![] }
4913 }
4914
4915 pub fn tx(&self) -> &T {
4916 &self.tx
4917 }
4918
4919 pub fn into_tx(self) -> T {
4920 self.tx
4921 }
4922
4923 pub fn aliases(&self) -> Option<NonEmpty<(u8, Option<SequenceNumber>)>> {
4925 self.claims
4926 .iter()
4927 .find_map(|c| match c {
4928 TransactionClaim::AddressAliasesV2(aliases) => Some(aliases),
4929 _ => None,
4930 })
4931 .cloned()
4932 }
4933
4934 #[allow(deprecated)]
4936 pub fn aliases_v1(&self) -> Option<NonEmpty<(SuiAddress, Option<SequenceNumber>)>> {
4937 self.claims
4938 .iter()
4939 .find_map(|c| match c {
4940 TransactionClaim::AddressAliases(aliases) => Some(aliases),
4941 _ => None,
4942 })
4943 .cloned()
4944 }
4945
4946 pub fn get_immutable_objects(&self) -> Vec<ObjectID> {
4948 self.claims
4949 .iter()
4950 .find_map(|c| match c {
4951 TransactionClaim::ImmutableInputObjects(objs) => Some(objs.clone()),
4952 _ => None,
4953 })
4954 .unwrap_or_default()
4955 }
4956}
4957
4958pub type PlainTransactionWithClaims = TransactionWithClaims<Transaction>;
4959
4960impl<T: Message, S> From<WithAliases<VerifiedEnvelope<T, S>>>
4963 for TransactionWithClaims<Envelope<T, S>>
4964{
4965 fn from(value: WithAliases<VerifiedEnvelope<T, S>>) -> Self {
4966 let (tx, aliases) = value.into_inner();
4967 Self::from_aliases(tx.into(), aliases)
4968 }
4969}
4970
4971#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize, PartialOrd, Ord, Hash)]
4972pub enum InputObjectKind {
4973 MovePackage(ObjectID),
4975 ImmOrOwnedMoveObject(ObjectRef),
4977 SharedMoveObject {
4979 id: ObjectID,
4980 initial_shared_version: SequenceNumber,
4981 mutability: SharedObjectMutability,
4982 },
4983}
4984
4985#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize, PartialOrd, Ord, Hash)]
4986pub enum SharedObjectMutability {
4987 Immutable,
4989 Mutable,
4990 NonExclusiveWrite,
4994}
4995
4996impl SharedObjectMutability {
4997 pub fn is_exclusive(&self) -> bool {
4998 match self {
4999 SharedObjectMutability::Mutable => true,
5000 SharedObjectMutability::Immutable => false,
5001 SharedObjectMutability::NonExclusiveWrite => false,
5002 }
5003 }
5004}
5005
5006impl InputObjectKind {
5007 pub fn object_id(&self) -> ObjectID {
5008 self.full_object_id().id()
5009 }
5010
5011 pub fn full_object_id(&self) -> FullObjectID {
5012 match self {
5013 Self::MovePackage(id) => FullObjectID::Fastpath(*id),
5014 Self::ImmOrOwnedMoveObject((id, _, _)) => FullObjectID::Fastpath(*id),
5015 Self::SharedMoveObject {
5016 id,
5017 initial_shared_version,
5018 ..
5019 } => FullObjectID::Consensus((*id, *initial_shared_version)),
5020 }
5021 }
5022
5023 pub fn version(&self) -> Option<SequenceNumber> {
5024 match self {
5025 Self::MovePackage(..) => None,
5026 Self::ImmOrOwnedMoveObject((_, version, _)) => Some(*version),
5027 Self::SharedMoveObject { .. } => None,
5028 }
5029 }
5030
5031 pub fn object_not_found_error(&self) -> UserInputError {
5032 match *self {
5033 Self::MovePackage(package_id) => {
5034 UserInputError::DependentPackageNotFound { package_id }
5035 }
5036 Self::ImmOrOwnedMoveObject((object_id, version, _)) => UserInputError::ObjectNotFound {
5037 object_id,
5038 version: Some(version),
5039 },
5040 Self::SharedMoveObject { id, .. } => UserInputError::ObjectNotFound {
5041 object_id: id,
5042 version: None,
5043 },
5044 }
5045 }
5046
5047 pub fn is_shared_object(&self) -> bool {
5048 matches!(self, Self::SharedMoveObject { .. })
5049 }
5050}
5051
5052#[derive(Clone, Debug)]
5055pub struct ObjectReadResult {
5056 pub input_object_kind: InputObjectKind,
5057 pub object: ObjectReadResultKind,
5058}
5059
5060#[derive(Clone)]
5061pub enum ObjectReadResultKind {
5062 Object(Object),
5063 ObjectConsensusStreamEnded(SequenceNumber, TransactionDigest),
5066 CancelledTransactionSharedObject(SequenceNumber),
5068}
5069
5070impl ObjectReadResultKind {
5071 pub fn is_cancelled(&self) -> bool {
5072 matches!(
5073 self,
5074 ObjectReadResultKind::CancelledTransactionSharedObject(_)
5075 )
5076 }
5077
5078 pub fn version(&self) -> SequenceNumber {
5079 match self {
5080 ObjectReadResultKind::Object(object) => object.version(),
5081 ObjectReadResultKind::ObjectConsensusStreamEnded(seq, _) => *seq,
5082 ObjectReadResultKind::CancelledTransactionSharedObject(seq) => *seq,
5083 }
5084 }
5085}
5086
5087impl std::fmt::Debug for ObjectReadResultKind {
5088 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
5089 match self {
5090 ObjectReadResultKind::Object(obj) => {
5091 write!(f, "Object({:?})", obj.compute_object_reference())
5092 }
5093 ObjectReadResultKind::ObjectConsensusStreamEnded(seq, digest) => {
5094 write!(f, "ObjectConsensusStreamEnded({}, {:?})", seq, digest)
5095 }
5096 ObjectReadResultKind::CancelledTransactionSharedObject(seq) => {
5097 write!(f, "CancelledTransactionSharedObject({})", seq)
5098 }
5099 }
5100 }
5101}
5102
5103impl From<Object> for ObjectReadResultKind {
5104 fn from(object: Object) -> Self {
5105 Self::Object(object)
5106 }
5107}
5108
5109impl ObjectReadResult {
5110 pub fn new(input_object_kind: InputObjectKind, object: ObjectReadResultKind) -> Self {
5111 if let (
5112 InputObjectKind::ImmOrOwnedMoveObject(_),
5113 ObjectReadResultKind::ObjectConsensusStreamEnded(_, _),
5114 ) = (&input_object_kind, &object)
5115 {
5116 panic!("only consensus objects can be ObjectConsensusStreamEnded");
5117 }
5118
5119 if let (
5120 InputObjectKind::ImmOrOwnedMoveObject(_),
5121 ObjectReadResultKind::CancelledTransactionSharedObject(_),
5122 ) = (&input_object_kind, &object)
5123 {
5124 panic!("only consensus objects can be CancelledTransactionSharedObject");
5125 }
5126
5127 Self {
5128 input_object_kind,
5129 object,
5130 }
5131 }
5132
5133 pub fn id(&self) -> ObjectID {
5134 self.input_object_kind.object_id()
5135 }
5136
5137 pub fn as_object(&self) -> Option<&Object> {
5138 match &self.object {
5139 ObjectReadResultKind::Object(object) => Some(object),
5140 ObjectReadResultKind::ObjectConsensusStreamEnded(_, _) => None,
5141 ObjectReadResultKind::CancelledTransactionSharedObject(_) => None,
5142 }
5143 }
5144
5145 pub fn new_from_gas_object(gas: &Object) -> Self {
5146 let objref = gas.compute_object_reference();
5147 Self {
5148 input_object_kind: InputObjectKind::ImmOrOwnedMoveObject(objref),
5149 object: ObjectReadResultKind::Object(gas.clone()),
5150 }
5151 }
5152
5153 pub fn is_mutable(&self) -> bool {
5154 match (&self.input_object_kind, &self.object) {
5155 (InputObjectKind::MovePackage(_), _) => false,
5156 (InputObjectKind::ImmOrOwnedMoveObject(_), ObjectReadResultKind::Object(object)) => {
5157 !object.is_immutable()
5158 }
5159 (
5160 InputObjectKind::ImmOrOwnedMoveObject(_),
5161 ObjectReadResultKind::ObjectConsensusStreamEnded(_, _),
5162 ) => unreachable!(),
5163 (
5164 InputObjectKind::ImmOrOwnedMoveObject(_),
5165 ObjectReadResultKind::CancelledTransactionSharedObject(_),
5166 ) => unreachable!(),
5167 (InputObjectKind::SharedMoveObject { mutability, .. }, _) => match mutability {
5168 SharedObjectMutability::Mutable => true,
5169 SharedObjectMutability::Immutable => false,
5170 SharedObjectMutability::NonExclusiveWrite => false,
5171 },
5172 }
5173 }
5174
5175 pub fn is_shared_object(&self) -> bool {
5176 self.input_object_kind.is_shared_object()
5177 }
5178
5179 pub fn is_consensus_stream_ended(&self) -> bool {
5180 self.consensus_stream_end_info().is_some()
5181 }
5182
5183 pub fn consensus_stream_end_info(&self) -> Option<(SequenceNumber, TransactionDigest)> {
5184 match &self.object {
5185 ObjectReadResultKind::ObjectConsensusStreamEnded(v, tx) => Some((*v, *tx)),
5186 _ => None,
5187 }
5188 }
5189
5190 pub fn get_address_owned_objref(&self) -> Option<ObjectRef> {
5192 match (&self.input_object_kind, &self.object) {
5193 (InputObjectKind::MovePackage(_), _) => None,
5194 (
5195 InputObjectKind::ImmOrOwnedMoveObject(objref),
5196 ObjectReadResultKind::Object(object),
5197 ) => {
5198 if object.is_immutable() {
5199 None
5200 } else {
5201 Some(*objref)
5202 }
5203 }
5204 (
5205 InputObjectKind::ImmOrOwnedMoveObject(_),
5206 ObjectReadResultKind::ObjectConsensusStreamEnded(_, _),
5207 ) => unreachable!(),
5208 (
5209 InputObjectKind::ImmOrOwnedMoveObject(_),
5210 ObjectReadResultKind::CancelledTransactionSharedObject(_),
5211 ) => unreachable!(),
5212 (InputObjectKind::SharedMoveObject { .. }, _) => None,
5213 }
5214 }
5215
5216 pub fn is_address_owned(&self) -> bool {
5217 self.get_address_owned_objref().is_some()
5218 }
5219
5220 pub fn is_replay_protected_input(&self) -> bool {
5221 if let InputObjectKind::ImmOrOwnedMoveObject(obj_ref) = &self.input_object_kind
5222 && ParsedDigest::is_coin_reservation_digest(&obj_ref.2)
5223 {
5224 true
5225 } else {
5226 self.is_address_owned()
5227 }
5228 }
5229
5230 pub fn to_shared_input(&self) -> Option<SharedInput> {
5231 match self.input_object_kind {
5232 InputObjectKind::MovePackage(_) => None,
5233 InputObjectKind::ImmOrOwnedMoveObject(_) => None,
5234 InputObjectKind::SharedMoveObject { id, mutability, .. } => Some(match &self.object {
5235 ObjectReadResultKind::Object(obj) => {
5236 SharedInput::Existing(obj.compute_object_reference())
5237 }
5238 ObjectReadResultKind::ObjectConsensusStreamEnded(seq, digest) => {
5239 SharedInput::ConsensusStreamEnded((id, *seq, mutability, *digest))
5240 }
5241 ObjectReadResultKind::CancelledTransactionSharedObject(seq) => {
5242 SharedInput::Cancelled((id, *seq))
5243 }
5244 }),
5245 }
5246 }
5247
5248 pub fn get_previous_transaction(&self) -> Option<TransactionDigest> {
5249 match &self.object {
5250 ObjectReadResultKind::Object(obj) => Some(obj.previous_transaction),
5251 ObjectReadResultKind::ObjectConsensusStreamEnded(_, digest) => Some(*digest),
5252 ObjectReadResultKind::CancelledTransactionSharedObject(_) => None,
5253 }
5254 }
5255}
5256
5257#[derive(Clone)]
5258pub struct InputObjects {
5259 objects: Vec<ObjectReadResult>,
5260}
5261
5262impl std::fmt::Debug for InputObjects {
5263 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
5264 f.debug_list().entries(self.objects.iter()).finish()
5265 }
5266}
5267
5268#[derive(Clone)]
5271pub struct CheckedInputObjects(InputObjects);
5272
5273impl CheckedInputObjects {
5279 pub fn new_with_checked_transaction_inputs(inputs: InputObjects) -> Self {
5281 Self(inputs)
5282 }
5283
5284 pub fn new_for_genesis(input_objects: Vec<ObjectReadResult>) -> Self {
5286 Self(InputObjects::new(input_objects))
5287 }
5288
5289 pub fn new_for_replay(input_objects: InputObjects) -> Self {
5291 Self(input_objects)
5292 }
5293
5294 pub fn inner(&self) -> &InputObjects {
5295 &self.0
5296 }
5297
5298 pub fn into_inner(self) -> InputObjects {
5299 self.0
5300 }
5301}
5302
5303impl From<Vec<ObjectReadResult>> for InputObjects {
5304 fn from(objects: Vec<ObjectReadResult>) -> Self {
5305 Self::new(objects)
5306 }
5307}
5308
5309impl InputObjects {
5310 pub fn new(objects: Vec<ObjectReadResult>) -> Self {
5311 Self { objects }
5312 }
5313
5314 pub fn len(&self) -> usize {
5315 self.objects.len()
5316 }
5317
5318 pub fn is_empty(&self) -> bool {
5319 self.objects.is_empty()
5320 }
5321
5322 pub fn contains_consensus_stream_ended_objects(&self) -> bool {
5323 self.objects
5324 .iter()
5325 .any(|obj| obj.is_consensus_stream_ended())
5326 }
5327
5328 pub fn get_cancelled_objects(&self) -> Option<(Vec<ObjectID>, SequenceNumber)> {
5331 let mut contains_cancelled = false;
5332 let mut cancel_reason = None;
5333 let mut cancelled_objects = Vec::new();
5334 for obj in &self.objects {
5335 if let ObjectReadResultKind::CancelledTransactionSharedObject(version) = obj.object {
5336 contains_cancelled = true;
5337 if version == SequenceNumber::CONGESTED
5338 || version == SequenceNumber::RANDOMNESS_UNAVAILABLE
5339 {
5340 assert!(cancel_reason.is_none() || cancel_reason == Some(version));
5342 cancel_reason = Some(version);
5343 cancelled_objects.push(obj.id());
5344 }
5345 }
5346 }
5347
5348 if !cancelled_objects.is_empty() {
5349 Some((
5350 cancelled_objects,
5351 cancel_reason
5352 .expect("there should be a cancel reason if there are cancelled objects"),
5353 ))
5354 } else {
5355 assert!(!contains_cancelled);
5356 None
5357 }
5358 }
5359
5360 pub fn filter_owned_objects(&self) -> Vec<ObjectRef> {
5361 let owned_objects: Vec<_> = self
5362 .objects
5363 .iter()
5364 .filter_map(|obj| obj.get_address_owned_objref())
5365 .collect();
5366
5367 trace!(
5368 num_mutable_objects = owned_objects.len(),
5369 "Checked locks and found mutable objects"
5370 );
5371
5372 owned_objects
5373 }
5374
5375 pub fn filter_shared_objects(&self) -> Vec<SharedInput> {
5376 self.objects
5377 .iter()
5378 .filter(|obj| obj.is_shared_object())
5379 .map(|obj| {
5380 obj.to_shared_input()
5381 .expect("already filtered for shared objects")
5382 })
5383 .collect()
5384 }
5385
5386 pub fn transaction_dependencies(&self) -> BTreeSet<TransactionDigest> {
5387 self.objects
5388 .iter()
5389 .filter_map(|obj| obj.get_previous_transaction())
5390 .collect()
5391 }
5392
5393 pub fn exclusive_mutable_inputs(&self) -> BTreeMap<ObjectID, (VersionDigest, Owner)> {
5396 self.mutables_with_input_kinds()
5397 .filter_map(|(id, (version, owner, kind))| match kind {
5398 InputObjectKind::SharedMoveObject { mutability, .. } => match mutability {
5399 SharedObjectMutability::Mutable => Some((id, (version, owner))),
5400 SharedObjectMutability::Immutable => None,
5401 SharedObjectMutability::NonExclusiveWrite => None,
5402 },
5403 _ => Some((id, (version, owner))),
5404 })
5405 .collect()
5406 }
5407
5408 pub fn non_exclusive_input_objects(&self) -> BTreeMap<ObjectID, Object> {
5409 self.objects
5410 .iter()
5411 .filter_map(|read_result| {
5412 match (read_result.as_object(), read_result.input_object_kind) {
5413 (
5414 Some(object),
5415 InputObjectKind::SharedMoveObject {
5416 mutability: SharedObjectMutability::NonExclusiveWrite,
5417 ..
5418 },
5419 ) => Some((read_result.id(), object.clone())),
5420 _ => None,
5421 }
5422 })
5423 .collect()
5424 }
5425
5426 pub fn all_mutable_inputs(&self) -> BTreeMap<ObjectID, (VersionDigest, Owner)> {
5429 self.mutables_with_input_kinds()
5430 .filter_map(|(id, (version, owner, kind))| match kind {
5431 InputObjectKind::SharedMoveObject { mutability, .. } => match mutability {
5432 SharedObjectMutability::Mutable => Some((id, (version, owner))),
5433 SharedObjectMutability::Immutable => None,
5434 SharedObjectMutability::NonExclusiveWrite => Some((id, (version, owner))),
5435 },
5436 _ => Some((id, (version, owner))),
5437 })
5438 .collect()
5439 }
5440
5441 fn mutables_with_input_kinds(
5442 &self,
5443 ) -> impl Iterator<Item = (ObjectID, (VersionDigest, Owner, InputObjectKind))> + '_ {
5444 self.objects.iter().filter_map(
5445 |ObjectReadResult {
5446 input_object_kind,
5447 object,
5448 }| match (input_object_kind, object) {
5449 (InputObjectKind::MovePackage(_), _) => None,
5450 (
5451 InputObjectKind::ImmOrOwnedMoveObject(object_ref),
5452 ObjectReadResultKind::Object(object),
5453 ) => {
5454 if object.is_immutable() {
5455 None
5456 } else {
5457 Some((
5458 object_ref.0,
5459 (
5460 (object_ref.1, object_ref.2),
5461 object.owner.clone(),
5462 *input_object_kind,
5463 ),
5464 ))
5465 }
5466 }
5467 (
5468 InputObjectKind::ImmOrOwnedMoveObject(_),
5469 ObjectReadResultKind::ObjectConsensusStreamEnded(_, _),
5470 ) => {
5471 unreachable!()
5472 }
5473 (
5474 InputObjectKind::SharedMoveObject { .. },
5475 ObjectReadResultKind::ObjectConsensusStreamEnded(_, _),
5476 ) => None,
5477 (
5478 InputObjectKind::SharedMoveObject { mutability, .. },
5479 ObjectReadResultKind::Object(object),
5480 ) => match *mutability {
5481 SharedObjectMutability::Mutable => {
5482 let oref = object.compute_object_reference();
5483 Some((
5484 oref.0,
5485 ((oref.1, oref.2), object.owner.clone(), *input_object_kind),
5486 ))
5487 }
5488 SharedObjectMutability::Immutable => None,
5489 SharedObjectMutability::NonExclusiveWrite => {
5490 let oref = object.compute_object_reference();
5491 Some((
5492 oref.0,
5493 ((oref.1, oref.2), object.owner.clone(), *input_object_kind),
5494 ))
5495 }
5496 },
5497 (
5498 InputObjectKind::ImmOrOwnedMoveObject(_),
5499 ObjectReadResultKind::CancelledTransactionSharedObject(_),
5500 ) => {
5501 unreachable!()
5502 }
5503 (
5504 InputObjectKind::SharedMoveObject { .. },
5505 ObjectReadResultKind::CancelledTransactionSharedObject(_),
5506 ) => None,
5507 },
5508 )
5509 }
5510
5511 pub fn lamport_timestamp(&self, receiving_objects: &[ObjectRef]) -> SequenceNumber {
5515 let input_versions = self
5516 .objects
5517 .iter()
5518 .filter_map(|object| match &object.object {
5519 ObjectReadResultKind::Object(object) => {
5520 object.data.try_as_move().map(MoveObject::version)
5521 }
5522 ObjectReadResultKind::ObjectConsensusStreamEnded(v, _) => Some(*v),
5523 ObjectReadResultKind::CancelledTransactionSharedObject(_) => None,
5524 })
5525 .chain(receiving_objects.iter().map(|object_ref| object_ref.1));
5526
5527 SequenceNumber::lamport_increment(input_versions)
5528 }
5529
5530 pub fn object_kinds(&self) -> impl Iterator<Item = &InputObjectKind> {
5531 self.objects.iter().map(
5532 |ObjectReadResult {
5533 input_object_kind, ..
5534 }| input_object_kind,
5535 )
5536 }
5537
5538 pub fn consensus_stream_ended_objects(&self) -> BTreeMap<ObjectID, SequenceNumber> {
5539 self.objects
5540 .iter()
5541 .filter_map(|obj| {
5542 if let InputObjectKind::SharedMoveObject {
5543 id,
5544 initial_shared_version,
5545 ..
5546 } = obj.input_object_kind
5547 {
5548 obj.is_consensus_stream_ended()
5549 .then_some((id, initial_shared_version))
5550 } else {
5551 None
5552 }
5553 })
5554 .collect()
5555 }
5556
5557 pub fn into_object_map(self) -> BTreeMap<ObjectID, Object> {
5558 self.objects
5559 .into_iter()
5560 .filter_map(|o| o.as_object().map(|object| (o.id(), object.clone())))
5561 .collect()
5562 }
5563
5564 pub fn push(&mut self, object: ObjectReadResult) {
5565 self.objects.push(object);
5566 }
5567
5568 pub fn iter(&self) -> impl Iterator<Item = &ObjectReadResult> {
5569 self.objects.iter()
5570 }
5571
5572 pub fn iter_objects(&self) -> impl Iterator<Item = &Object> {
5573 self.objects.iter().filter_map(|o| o.as_object())
5574 }
5575
5576 pub fn non_exclusive_mutable_inputs(
5577 &self,
5578 ) -> impl Iterator<Item = (ObjectID, SequenceNumber)> + '_ {
5579 self.objects.iter().filter_map(
5580 |ObjectReadResult {
5581 input_object_kind,
5582 object,
5583 }| match input_object_kind {
5584 InputObjectKind::SharedMoveObject {
5588 id,
5589 mutability: SharedObjectMutability::NonExclusiveWrite,
5590 ..
5591 } if !object.is_cancelled() => Some((*id, object.version())),
5592 _ => None,
5593 },
5594 )
5595 }
5596}
5597
5598#[derive(Clone, Debug)]
5602pub enum ReceivingObjectReadResultKind {
5603 Object(Object),
5604 PreviouslyReceivedObject,
5606}
5607
5608impl ReceivingObjectReadResultKind {
5609 pub fn as_object(&self) -> Option<&Object> {
5610 match &self {
5611 Self::Object(object) => Some(object),
5612 Self::PreviouslyReceivedObject => None,
5613 }
5614 }
5615}
5616
5617pub struct ReceivingObjectReadResult {
5618 pub object_ref: ObjectRef,
5619 pub object: ReceivingObjectReadResultKind,
5620}
5621
5622impl ReceivingObjectReadResult {
5623 pub fn new(object_ref: ObjectRef, object: ReceivingObjectReadResultKind) -> Self {
5624 Self { object_ref, object }
5625 }
5626
5627 pub fn is_previously_received(&self) -> bool {
5628 matches!(
5629 self.object,
5630 ReceivingObjectReadResultKind::PreviouslyReceivedObject
5631 )
5632 }
5633}
5634
5635impl From<Object> for ReceivingObjectReadResultKind {
5636 fn from(object: Object) -> Self {
5637 Self::Object(object)
5638 }
5639}
5640
5641pub struct ReceivingObjects {
5642 pub objects: Vec<ReceivingObjectReadResult>,
5643}
5644
5645impl ReceivingObjects {
5646 pub fn iter(&self) -> impl Iterator<Item = &ReceivingObjectReadResult> {
5647 self.objects.iter()
5648 }
5649
5650 pub fn iter_objects(&self) -> impl Iterator<Item = &Object> {
5651 self.objects.iter().filter_map(|o| o.object.as_object())
5652 }
5653}
5654
5655impl From<Vec<ReceivingObjectReadResult>> for ReceivingObjects {
5656 fn from(objects: Vec<ReceivingObjectReadResult>) -> Self {
5657 Self { objects }
5658 }
5659}
5660
5661impl Display for CertifiedTransaction {
5662 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
5663 let mut writer = String::new();
5664 writeln!(writer, "Transaction Hash: {:?}", self.digest())?;
5665 writeln!(
5666 writer,
5667 "Signed Authorities Bitmap : {:?}",
5668 self.auth_sig().signers_map
5669 )?;
5670 write!(writer, "{}", self.data().intent_message().value.kind())?;
5671 write!(f, "{}", writer)
5672 }
5673}
5674
5675#[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
5679pub enum TransactionKey {
5680 Digest(TransactionDigest),
5681 RandomnessRound(EpochId, RandomnessRound),
5682 AccumulatorSettlement(EpochId, u64 ),
5683 ConsensusCommitPrologue(EpochId, u64 , u32 ),
5684}
5685
5686impl TransactionKey {
5687 pub fn unwrap_digest(&self) -> &TransactionDigest {
5688 match self {
5689 TransactionKey::Digest(d) => d,
5690 _ => panic!("called unwrap_digest on a non-Digest TransactionKey: {self:?}"),
5691 }
5692 }
5693
5694 pub fn as_digest(&self) -> Option<&TransactionDigest> {
5695 match self {
5696 TransactionKey::Digest(d) => Some(d),
5697 _ => None,
5698 }
5699 }
5700}