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
2499pub fn paid_proposer_count(gas_price: u64, reference_gas_price: u64) -> u64 {
2504 MAX_UNPAID_ALLOWED_PROPOSERS.max(gas_price / reference_gas_price.max(1))
2505}
2506
2507impl TransactionExpiration {
2508 pub fn is_replay_protected(&self) -> bool {
2513 matches!(
2514 self,
2515 TransactionExpiration::ValidDuring {
2516 min_epoch: Some(min_epoch),
2517 max_epoch: Some(max_epoch),
2518 ..
2519 }
2520 | TransactionExpiration::Validity {
2521 min_epoch: Some(min_epoch),
2522 max_epoch: Some(max_epoch),
2523 ..
2524 } if *max_epoch == *min_epoch || *max_epoch == min_epoch.saturating_add(1)
2525 )
2526 }
2527
2528 pub fn allowed_proposers(&self, epoch: EpochId) -> Option<&AllowedProposers> {
2536 match self {
2537 TransactionExpiration::Validity {
2538 allowed_proposers: Some(allowed),
2539 ..
2540 } if allowed.epoch == epoch => Some(allowed),
2541 _ => None,
2542 }
2543 }
2544
2545 pub fn restricts_proposers(&self, epoch: EpochId) -> bool {
2548 self.allowed_proposers(epoch).is_some()
2549 }
2550
2551 pub fn is_allowed_proposer(&self, proposer: u32, epoch: EpochId) -> bool {
2554 self.allowed_proposers(epoch).is_none_or(|allowed| {
2555 allowed
2559 .proposers
2560 .iter()
2561 .find(|p| **p >= proposer)
2562 .is_some_and(|p| *p == proposer)
2563 })
2564 }
2565}
2566
2567#[enum_dispatch(TransactionDataAPI)]
2568#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)]
2569pub enum TransactionData {
2570 V1(TransactionDataV1),
2571 }
2574
2575#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)]
2576pub struct TransactionDataV1 {
2577 pub kind: TransactionKind,
2578 pub sender: SuiAddress,
2579 pub gas_data: GasData,
2580 pub expiration: TransactionExpiration,
2581}
2582
2583impl TransactionData {
2584 pub fn as_v1(&self) -> &TransactionDataV1 {
2585 match self {
2586 TransactionData::V1(v1) => v1,
2587 }
2588 }
2589
2590 pub fn has_replay_protection(
2595 &self,
2596 input_objects: impl IntoIterator<Item = impl std::borrow::Borrow<ObjectReadResult>>,
2597 ) -> bool {
2598 self.expiration().is_replay_protected()
2599 || !self.gas_data().payment.is_empty()
2600 || input_objects
2601 .into_iter()
2602 .any(|object| object.borrow().is_replay_protected_input())
2603 }
2604 fn new_system_transaction(kind: TransactionKind) -> Self {
2605 assert!(kind.is_system_tx());
2607 let sender = SuiAddress::default();
2608 TransactionData::V1(TransactionDataV1 {
2609 kind,
2610 sender,
2611 gas_data: GasData {
2612 price: GAS_PRICE_FOR_SYSTEM_TX,
2613 owner: sender,
2614 payment: vec![(ObjectID::ZERO, SequenceNumber::default(), ObjectDigest::MIN)],
2615 budget: 0,
2616 },
2617 expiration: TransactionExpiration::None,
2618 })
2619 }
2620
2621 pub fn new(
2622 kind: TransactionKind,
2623 sender: SuiAddress,
2624 gas_payment: ObjectRef,
2625 gas_budget: u64,
2626 gas_price: u64,
2627 ) -> Self {
2628 TransactionData::V1(TransactionDataV1 {
2629 kind,
2630 sender,
2631 gas_data: GasData {
2632 price: gas_price,
2633 owner: sender,
2634 payment: vec![gas_payment],
2635 budget: gas_budget,
2636 },
2637 expiration: TransactionExpiration::None,
2638 })
2639 }
2640
2641 pub fn new_with_gas_coins(
2642 kind: TransactionKind,
2643 sender: SuiAddress,
2644 gas_payment: Vec<ObjectRef>,
2645 gas_budget: u64,
2646 gas_price: u64,
2647 ) -> Self {
2648 Self::new_with_gas_coins_allow_sponsor(
2649 kind,
2650 sender,
2651 gas_payment,
2652 gas_budget,
2653 gas_price,
2654 sender,
2655 )
2656 }
2657
2658 pub fn new_with_gas_coins_allow_sponsor(
2659 kind: TransactionKind,
2660 sender: SuiAddress,
2661 gas_payment: Vec<ObjectRef>,
2662 gas_budget: u64,
2663 gas_price: u64,
2664 gas_sponsor: SuiAddress,
2665 ) -> Self {
2666 TransactionData::V1(TransactionDataV1 {
2667 kind,
2668 sender,
2669 gas_data: GasData {
2670 price: gas_price,
2671 owner: gas_sponsor,
2672 payment: gas_payment,
2673 budget: gas_budget,
2674 },
2675 expiration: TransactionExpiration::None,
2676 })
2677 }
2678
2679 pub fn new_with_gas_data(kind: TransactionKind, sender: SuiAddress, gas_data: GasData) -> Self {
2680 TransactionData::V1(TransactionDataV1 {
2681 kind,
2682 sender,
2683 gas_data,
2684 expiration: TransactionExpiration::None,
2685 })
2686 }
2687
2688 pub fn new_with_gas_data_and_expiration(
2689 kind: TransactionKind,
2690 sender: SuiAddress,
2691 gas_data: GasData,
2692 expiration: TransactionExpiration,
2693 ) -> Self {
2694 TransactionData::V1(TransactionDataV1 {
2695 kind,
2696 sender,
2697 gas_data,
2698 expiration,
2699 })
2700 }
2701
2702 pub fn new_move_call(
2703 sender: SuiAddress,
2704 package: ObjectID,
2705 module: Identifier,
2706 function: Identifier,
2707 type_arguments: Vec<TypeTag>,
2708 gas_payment: ObjectRef,
2709 arguments: Vec<CallArg>,
2710 gas_budget: u64,
2711 gas_price: u64,
2712 ) -> anyhow::Result<Self> {
2713 Self::new_move_call_with_gas_coins(
2714 sender,
2715 package,
2716 module,
2717 function,
2718 type_arguments,
2719 vec![gas_payment],
2720 arguments,
2721 gas_budget,
2722 gas_price,
2723 )
2724 }
2725
2726 pub fn new_move_call_with_gas_coins(
2727 sender: SuiAddress,
2728 package: ObjectID,
2729 module: Identifier,
2730 function: Identifier,
2731 type_arguments: Vec<TypeTag>,
2732 gas_payment: Vec<ObjectRef>,
2733 arguments: Vec<CallArg>,
2734 gas_budget: u64,
2735 gas_price: u64,
2736 ) -> anyhow::Result<Self> {
2737 let pt = {
2738 let mut builder = ProgrammableTransactionBuilder::new();
2739 builder.move_call(package, module, function, type_arguments, arguments)?;
2740 builder.finish()
2741 };
2742 Ok(Self::new_programmable(
2743 sender,
2744 gas_payment,
2745 pt,
2746 gas_budget,
2747 gas_price,
2748 ))
2749 }
2750
2751 pub fn new_transfer(
2752 recipient: SuiAddress,
2753 full_object_ref: FullObjectRef,
2754 sender: SuiAddress,
2755 gas_payment: ObjectRef,
2756 gas_budget: u64,
2757 gas_price: u64,
2758 ) -> Self {
2759 let pt = {
2760 let mut builder = ProgrammableTransactionBuilder::new();
2761 builder.transfer_object(recipient, full_object_ref).unwrap();
2762 builder.finish()
2763 };
2764 Self::new_programmable(sender, vec![gas_payment], pt, gas_budget, gas_price)
2765 }
2766
2767 pub fn new_transfer_sui(
2768 recipient: SuiAddress,
2769 sender: SuiAddress,
2770 amount: Option<u64>,
2771 gas_payment: ObjectRef,
2772 gas_budget: u64,
2773 gas_price: u64,
2774 ) -> Self {
2775 Self::new_transfer_sui_allow_sponsor(
2776 recipient,
2777 sender,
2778 amount,
2779 gas_payment,
2780 gas_budget,
2781 gas_price,
2782 sender,
2783 )
2784 }
2785
2786 pub fn new_transfer_sui_allow_sponsor(
2787 recipient: SuiAddress,
2788 sender: SuiAddress,
2789 amount: Option<u64>,
2790 gas_payment: ObjectRef,
2791 gas_budget: u64,
2792 gas_price: u64,
2793 gas_sponsor: SuiAddress,
2794 ) -> Self {
2795 let pt = {
2796 let mut builder = ProgrammableTransactionBuilder::new();
2797 builder.transfer_sui(recipient, amount);
2798 builder.finish()
2799 };
2800 Self::new_programmable_allow_sponsor(
2801 sender,
2802 vec![gas_payment],
2803 pt,
2804 gas_budget,
2805 gas_price,
2806 gas_sponsor,
2807 )
2808 }
2809
2810 pub fn new_pay(
2811 sender: SuiAddress,
2812 coins: Vec<ObjectRef>,
2813 recipients: Vec<SuiAddress>,
2814 amounts: Vec<u64>,
2815 gas_payment: ObjectRef,
2816 gas_budget: u64,
2817 gas_price: u64,
2818 ) -> anyhow::Result<Self> {
2819 let pt = {
2820 let mut builder = ProgrammableTransactionBuilder::new();
2821 builder.pay(coins, recipients, amounts)?;
2822 builder.finish()
2823 };
2824 Ok(Self::new_programmable(
2825 sender,
2826 vec![gas_payment],
2827 pt,
2828 gas_budget,
2829 gas_price,
2830 ))
2831 }
2832
2833 pub fn new_pay_sui(
2834 sender: SuiAddress,
2835 mut coins: Vec<ObjectRef>,
2836 recipients: Vec<SuiAddress>,
2837 amounts: Vec<u64>,
2838 gas_payment: ObjectRef,
2839 gas_budget: u64,
2840 gas_price: u64,
2841 ) -> anyhow::Result<Self> {
2842 coins.insert(0, gas_payment);
2843 let pt = {
2844 let mut builder = ProgrammableTransactionBuilder::new();
2845 builder.pay_sui(recipients, amounts)?;
2846 builder.finish()
2847 };
2848 Ok(Self::new_programmable(
2849 sender, coins, pt, gas_budget, gas_price,
2850 ))
2851 }
2852
2853 pub fn new_pay_all_sui(
2854 sender: SuiAddress,
2855 mut coins: Vec<ObjectRef>,
2856 recipient: SuiAddress,
2857 gas_payment: ObjectRef,
2858 gas_budget: u64,
2859 gas_price: u64,
2860 ) -> Self {
2861 coins.insert(0, gas_payment);
2862 let pt = {
2863 let mut builder = ProgrammableTransactionBuilder::new();
2864 builder.pay_all_sui(recipient);
2865 builder.finish()
2866 };
2867 Self::new_programmable(sender, coins, pt, gas_budget, gas_price)
2868 }
2869
2870 pub fn new_split_coin(
2871 sender: SuiAddress,
2872 coin: ObjectRef,
2873 amounts: Vec<u64>,
2874 gas_payment: ObjectRef,
2875 gas_budget: u64,
2876 gas_price: u64,
2877 ) -> Self {
2878 let pt = {
2879 let mut builder = ProgrammableTransactionBuilder::new();
2880 builder.split_coin(sender, coin, amounts);
2881 builder.finish()
2882 };
2883 Self::new_programmable(sender, vec![gas_payment], pt, gas_budget, gas_price)
2884 }
2885
2886 pub fn new_module(
2887 sender: SuiAddress,
2888 gas_payment: ObjectRef,
2889 modules: Vec<Vec<u8>>,
2890 dep_ids: Vec<ObjectID>,
2891 gas_budget: u64,
2892 gas_price: u64,
2893 ) -> Self {
2894 let pt = {
2895 let mut builder = ProgrammableTransactionBuilder::new();
2896 let upgrade_cap = builder.publish_upgradeable(modules, dep_ids);
2897 builder.transfer_arg(sender, upgrade_cap);
2898 builder.finish()
2899 };
2900 Self::new_programmable(sender, vec![gas_payment], pt, gas_budget, gas_price)
2901 }
2902
2903 pub fn new_upgrade(
2904 sender: SuiAddress,
2905 gas_payment: ObjectRef,
2906 package_id: ObjectID,
2907 modules: Vec<Vec<u8>>,
2908 dep_ids: Vec<ObjectID>,
2909 (upgrade_capability, capability_owner): (ObjectRef, Owner),
2910 upgrade_policy: u8,
2911 digest: Vec<u8>,
2912 gas_budget: u64,
2913 gas_price: u64,
2914 ) -> anyhow::Result<Self> {
2915 let pt = {
2916 let mut builder = ProgrammableTransactionBuilder::new();
2917 let capability_arg = match capability_owner {
2918 Owner::AddressOwner(_) => ObjectArg::ImmOrOwnedObject(upgrade_capability),
2919 Owner::Shared {
2920 initial_shared_version,
2921 }
2922 | Owner::ConsensusAddressOwner {
2923 start_version: initial_shared_version,
2924 ..
2925 }
2926 | Owner::Party {
2927 start_version: initial_shared_version,
2928 ..
2929 } => ObjectArg::SharedObject {
2930 id: upgrade_capability.0,
2931 initial_shared_version,
2932 mutability: SharedObjectMutability::Mutable,
2933 },
2934 Owner::Immutable => {
2935 return Err(anyhow::anyhow!(
2936 "Upgrade capability is stored immutably and cannot be used for upgrades"
2937 ));
2938 }
2939 Owner::ObjectOwner(_) => {
2942 return Err(anyhow::anyhow!("Upgrade capability controlled by object"));
2943 }
2944 };
2945 builder.obj(capability_arg).unwrap();
2946 let upgrade_arg = builder.pure(upgrade_policy).unwrap();
2947 let digest_arg = builder.pure(digest).unwrap();
2948 let upgrade_ticket = builder.programmable_move_call(
2949 SUI_FRAMEWORK_PACKAGE_ID,
2950 ident_str!("package").to_owned(),
2951 ident_str!("authorize_upgrade").to_owned(),
2952 vec![],
2953 vec![Argument::Input(0), upgrade_arg, digest_arg],
2954 );
2955 let upgrade_receipt = builder.upgrade(package_id, upgrade_ticket, dep_ids, modules);
2956
2957 builder.programmable_move_call(
2958 SUI_FRAMEWORK_PACKAGE_ID,
2959 ident_str!("package").to_owned(),
2960 ident_str!("commit_upgrade").to_owned(),
2961 vec![],
2962 vec![Argument::Input(0), upgrade_receipt],
2963 );
2964
2965 builder.finish()
2966 };
2967 Ok(Self::new_programmable(
2968 sender,
2969 vec![gas_payment],
2970 pt,
2971 gas_budget,
2972 gas_price,
2973 ))
2974 }
2975
2976 pub fn new_programmable(
2977 sender: SuiAddress,
2978 gas_payment: Vec<ObjectRef>,
2979 pt: ProgrammableTransaction,
2980 gas_budget: u64,
2981 gas_price: u64,
2982 ) -> Self {
2983 Self::new_programmable_allow_sponsor(sender, gas_payment, pt, gas_budget, gas_price, sender)
2984 }
2985
2986 pub fn new_programmable_allow_sponsor(
2987 sender: SuiAddress,
2988 gas_payment: Vec<ObjectRef>,
2989 pt: ProgrammableTransaction,
2990 gas_budget: u64,
2991 gas_price: u64,
2992 sponsor: SuiAddress,
2993 ) -> Self {
2994 let kind = TransactionKind::ProgrammableTransaction(pt);
2995 Self::new_with_gas_coins_allow_sponsor(
2996 kind,
2997 sender,
2998 gas_payment,
2999 gas_budget,
3000 gas_price,
3001 sponsor,
3002 )
3003 }
3004
3005 pub fn new_programmable_with_address_balance_gas(
3006 sender: SuiAddress,
3007 pt: ProgrammableTransaction,
3008 gas_budget: u64,
3009 gas_price: u64,
3010 chain_identifier: ChainIdentifier,
3011 current_epoch: EpochId,
3012 nonce: u32,
3013 ) -> Self {
3014 TransactionData::V1(TransactionDataV1 {
3015 kind: TransactionKind::ProgrammableTransaction(pt),
3016 sender,
3017 gas_data: GasData {
3018 payment: vec![],
3019 owner: sender,
3020 price: gas_price,
3021 budget: gas_budget,
3022 },
3023 expiration: TransactionExpiration::ValidDuring {
3024 min_epoch: Some(current_epoch),
3025 max_epoch: Some(current_epoch + 1),
3026 min_timestamp: None,
3027 max_timestamp: None,
3028 chain: chain_identifier,
3029 nonce,
3030 },
3031 })
3032 }
3033
3034 pub fn message_version(&self) -> u64 {
3035 match self {
3036 TransactionData::V1(_) => 1,
3037 }
3038 }
3039
3040 pub fn execution_parts(&self) -> (TransactionKind, SuiAddress, GasData) {
3041 (self.kind().clone(), self.sender(), self.gas_data().clone())
3042 }
3043
3044 pub fn uses_randomness(&self) -> bool {
3045 self.kind()
3046 .shared_input_objects()
3047 .any(|obj| obj.id() == SUI_RANDOMNESS_STATE_OBJECT_ID)
3048 }
3049
3050 pub fn digest(&self) -> TransactionDigest {
3051 TransactionDigest::new(default_hash(self))
3052 }
3053}
3054
3055#[enum_dispatch]
3056pub trait TransactionDataAPI {
3057 fn sender(&self) -> SuiAddress;
3058
3059 fn kind(&self) -> &TransactionKind;
3062
3063 fn kind_mut(&mut self) -> &mut TransactionKind;
3065
3066 fn into_kind(self) -> TransactionKind;
3068
3069 fn required_signers(&self) -> NonEmpty<SuiAddress>;
3071
3072 fn gas_data(&self) -> &GasData;
3073
3074 fn gas_owner(&self) -> SuiAddress;
3075
3076 fn gas(&self) -> &[ObjectRef];
3077
3078 fn gas_price(&self) -> u64;
3079
3080 fn gas_budget(&self) -> u64;
3081
3082 fn expiration(&self) -> &TransactionExpiration;
3083
3084 fn expiration_mut(&mut self) -> &mut TransactionExpiration;
3085
3086 fn move_calls(&self) -> Vec<(usize, &ObjectID, &str, &str)>;
3087
3088 fn input_objects(&self) -> UserInputResult<Vec<InputObjectKind>>;
3089
3090 fn shared_input_objects(&self) -> Vec<SharedInputObject>;
3091
3092 fn receiving_objects(&self) -> Vec<ObjectRef>;
3093
3094 fn fastpath_dependency_objects(
3098 &self,
3099 ) -> UserInputResult<(Vec<ObjectRef>, Vec<ObjectID>, Vec<ObjectRef>)>;
3100
3101 fn process_funds_withdrawals_for_signing(
3109 &self,
3110 chain_identifier: ChainIdentifier,
3111 coin_resolver: &dyn CoinReservationResolverTrait,
3112 ) -> UserInputResult<BTreeMap<AccumulatorObjId, (u64, TypeTag, SuiAddress)>>;
3113
3114 fn process_funds_withdrawals_for_estimation(
3118 &self,
3119 chain_identifier: ChainIdentifier,
3120 coin_resolver: &dyn CoinReservationResolverTrait,
3121 ) -> UserInputResult<BTreeMap<AccumulatorObjId, (u64, TypeTag, SuiAddress)>>;
3122
3123 fn process_funds_withdrawals_for_execution(
3126 &self,
3127 chain_identifier: ChainIdentifier,
3128 ) -> BTreeMap<AccumulatorObjId, u64>;
3129
3130 fn check_allowance_inputs(&self, input_objects: &InputObjects) -> UserInputResult<()>;
3134
3135 fn has_funds_withdrawals(&self) -> bool;
3137
3138 fn coin_reservation_obj_refs(
3139 &self,
3140 chain_identifier: ChainIdentifier,
3141 ) -> Vec<ParsedObjectRefWithdrawal>;
3142
3143 fn validity_check(&self, context: &TxValidityCheckContext<'_>) -> SuiResult;
3144
3145 fn check_sponsorship(&self) -> UserInputResult;
3147
3148 fn is_system_tx(&self) -> bool;
3149 fn is_genesis_tx(&self) -> bool;
3150
3151 fn is_end_of_epoch_tx(&self) -> bool;
3154
3155 fn is_consensus_commit_prologue(&self) -> bool;
3156
3157 fn is_sponsored_tx(&self) -> bool;
3159
3160 fn is_gas_paid_from_address_balance(&self) -> bool;
3161
3162 fn is_gasless_transaction(&self) -> bool;
3163
3164 fn sender_mut_for_testing(&mut self) -> &mut SuiAddress;
3165
3166 fn gas_data_mut(&mut self) -> &mut GasData;
3167
3168 fn expiration_mut_for_testing(&mut self) -> &mut TransactionExpiration;
3170}
3171
3172impl TransactionDataAPI for TransactionDataV1 {
3173 fn sender(&self) -> SuiAddress {
3174 self.sender
3175 }
3176
3177 fn kind(&self) -> &TransactionKind {
3178 &self.kind
3179 }
3180
3181 fn kind_mut(&mut self) -> &mut TransactionKind {
3182 &mut self.kind
3183 }
3184
3185 fn into_kind(self) -> TransactionKind {
3186 self.kind
3187 }
3188
3189 fn required_signers(&self) -> NonEmpty<SuiAddress> {
3191 let mut signers = nonempty![self.sender];
3192 if self.gas_owner() != self.sender {
3193 signers.push(self.gas_owner());
3194 }
3195 signers
3196 }
3197
3198 fn gas_data(&self) -> &GasData {
3199 &self.gas_data
3200 }
3201
3202 fn gas_owner(&self) -> SuiAddress {
3203 self.gas_data.owner
3204 }
3205
3206 fn gas(&self) -> &[ObjectRef] {
3207 &self.gas_data.payment
3208 }
3209
3210 fn gas_price(&self) -> u64 {
3211 self.gas_data.price
3212 }
3213
3214 fn gas_budget(&self) -> u64 {
3215 self.gas_data.budget
3216 }
3217
3218 fn expiration(&self) -> &TransactionExpiration {
3219 &self.expiration
3220 }
3221
3222 fn expiration_mut(&mut self) -> &mut TransactionExpiration {
3223 &mut self.expiration
3224 }
3225
3226 fn move_calls(&self) -> Vec<(usize, &ObjectID, &str, &str)> {
3227 self.kind.move_calls()
3228 }
3229
3230 fn input_objects(&self) -> UserInputResult<Vec<InputObjectKind>> {
3231 let mut inputs = self.kind.input_objects()?;
3232
3233 if !self.kind.is_system_tx() {
3234 inputs.extend(
3235 self.gas()
3236 .iter()
3237 .filter(|obj_ref| !ParsedDigest::is_coin_reservation_digest(&obj_ref.2))
3238 .map(|obj_ref| InputObjectKind::ImmOrOwnedMoveObject(*obj_ref)),
3239 );
3240 }
3241 Ok(inputs)
3242 }
3243
3244 fn shared_input_objects(&self) -> Vec<SharedInputObject> {
3245 self.kind.shared_input_objects().collect()
3246 }
3247
3248 fn receiving_objects(&self) -> Vec<ObjectRef> {
3249 self.kind.receiving_objects()
3250 }
3251
3252 fn fastpath_dependency_objects(
3253 &self,
3254 ) -> UserInputResult<(Vec<ObjectRef>, Vec<ObjectID>, Vec<ObjectRef>)> {
3255 let mut move_objects = vec![];
3256 let mut packages = vec![];
3257 let mut receiving_objects = vec![];
3258 self.input_objects()?.iter().for_each(|o| match o {
3259 InputObjectKind::ImmOrOwnedMoveObject(object_ref) => {
3260 move_objects.push(*object_ref);
3261 }
3262 InputObjectKind::MovePackage(package_id) => {
3263 packages.push(*package_id);
3264 }
3265 InputObjectKind::SharedMoveObject { .. } => {}
3266 });
3267 self.receiving_objects().iter().for_each(|object_ref| {
3268 receiving_objects.push(*object_ref);
3269 });
3270 Ok((move_objects, packages, receiving_objects))
3271 }
3272
3273 fn process_funds_withdrawals_for_signing(
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, true)
3279 }
3280
3281 fn process_funds_withdrawals_for_estimation(
3282 &self,
3283 chain_identifier: ChainIdentifier,
3284 coin_resolver: &dyn CoinReservationResolverTrait,
3285 ) -> UserInputResult<BTreeMap<AccumulatorObjId, (u64, TypeTag, SuiAddress)>> {
3286 self.accumulate_funds_withdrawals(chain_identifier, coin_resolver, false)
3287 }
3288
3289 fn process_funds_withdrawals_for_execution(
3290 &self,
3291 chain_identifier: ChainIdentifier,
3292 ) -> BTreeMap<AccumulatorObjId, u64> {
3293 let mut withdraws: Vec<_> = self.get_funds_withdrawals().collect();
3294 withdraws.extend(self.get_funds_withdrawal_for_gas_payment());
3295
3296 let mut withdraw_map: BTreeMap<AccumulatorObjId, u64> = BTreeMap::new();
3298 for withdraw in withdraws {
3299 let reserved_amount = match &withdraw.reservation {
3300 Reservation::MaxAmountU64(amount) => {
3301 assert!(*amount > 0, "verified in validity check");
3302 *amount
3303 }
3304 };
3305
3306 let withdrawal_owner = withdraw.owner_for_withdrawal(self);
3307
3308 let account_id =
3310 AccumulatorValue::get_field_id(withdrawal_owner, &withdraw.type_arg.to_type_tag())
3311 .unwrap();
3312
3313 let value = withdraw_map.entry(account_id).or_default();
3314 *value = value.checked_add(reserved_amount).unwrap();
3316 }
3317
3318 for obj in self.coin_reservation_obj_refs() {
3322 assert_reachable!("processing coin reservation withdrawal");
3323 let parsed = ParsedObjectRefWithdrawal::parse(&obj, chain_identifier).unwrap();
3325 let value = withdraw_map
3326 .entry(AccumulatorObjId::new_unchecked(parsed.unmasked_object_id))
3331 .or_default();
3332 *value = value.checked_add(parsed.reservation_amount()).unwrap();
3334 }
3335
3336 withdraw_map
3337 }
3338
3339 fn check_allowance_inputs(&self, input_objects: &InputObjects) -> UserInputResult<()> {
3340 let allowance_withdrawals: Vec<_> = self
3341 .get_funds_withdrawals()
3342 .filter_map(|w| match w.withdraw_from {
3343 WithdrawFrom::SenderAllowance { funder, allowance } => {
3344 Some((funder, allowance, w.type_arg.to_type_tag()))
3345 }
3346 _ => None,
3347 })
3348 .collect();
3349 if allowance_withdrawals.is_empty() {
3350 return Ok(());
3351 }
3352 let objects_by_id: BTreeMap<ObjectID, &Object> = input_objects
3354 .iter()
3355 .filter_map(|input| Some((input.id(), input.as_object()?)))
3356 .collect();
3357 let mut resolved_allowances: BTreeMap<ObjectID, ResolvedAllowance> = BTreeMap::new();
3360 for (specified_funder, allowance, requested_funds_type) in allowance_withdrawals {
3361 let resolved: &ResolvedAllowance = match resolved_allowances.entry(allowance) {
3362 Entry::Occupied(entry) => entry.into_mut(),
3363 Entry::Vacant(entry) => {
3364 let object = objects_by_id.get(&allowance).ok_or_else(|| {
3365 UserInputError::InvalidWithdrawReservation {
3366 error: format!(
3367 "Specified allowance {allowance} not found among the tx inputs"
3368 ),
3369 }
3370 })?;
3371 entry.insert(parse_allowance_object(object)?)
3372 }
3373 };
3374 if resolved.funder != specified_funder {
3375 return Err(UserInputError::InvalidWithdrawReservation {
3376 error: format!(
3377 "Specified funder {specified_funder} does not match the funder of \
3378 allowance {allowance}"
3379 ),
3380 });
3381 }
3382 if resolved.spender != Some(self.sender()) {
3383 return Err(UserInputError::InvalidWithdrawReservation {
3384 error: format!(
3385 "Transaction sender is not the spender of allowance {allowance}"
3386 ),
3387 });
3388 }
3389 if resolved.funds_type != requested_funds_type {
3390 return Err(UserInputError::InvalidWithdrawReservation {
3391 error: format!(
3392 "Allowance {allowance} is for {}, not {requested_funds_type}",
3393 resolved.funds_type
3394 ),
3395 });
3396 }
3397 }
3398 Ok(())
3399 }
3400
3401 fn has_funds_withdrawals(&self) -> bool {
3402 if self.is_gas_paid_from_address_balance() && self.gas_data().budget > 0 {
3403 return true;
3404 }
3405 if let TransactionKind::ProgrammableTransaction(pt) = &self.kind {
3406 for input in &pt.inputs {
3407 if matches!(input, CallArg::FundsWithdrawal(_)) {
3408 return true;
3409 }
3410 }
3411 }
3412 if self.coin_reservation_obj_refs().next().is_some() {
3413 return true;
3414 }
3415 false
3416 }
3417
3418 fn coin_reservation_obj_refs(
3419 &self,
3420 chain_identifier: ChainIdentifier,
3421 ) -> Vec<ParsedObjectRefWithdrawal> {
3422 self.coin_reservation_obj_refs()
3423 .filter_map(|obj_ref| ParsedObjectRefWithdrawal::parse(&obj_ref, chain_identifier))
3424 .collect()
3425 }
3426
3427 fn validity_check(&self, context: &TxValidityCheckContext<'_>) -> SuiResult {
3428 let config = context.config;
3429
3430 match self.expiration() {
3432 TransactionExpiration::None => (), TransactionExpiration::Epoch(max_epoch) => {
3434 if context.epoch > *max_epoch {
3435 return Err(SuiErrorKind::TransactionExpired.into());
3436 }
3437 }
3438 TransactionExpiration::ValidDuring {
3439 min_epoch,
3440 max_epoch,
3441 min_timestamp,
3442 max_timestamp,
3443 chain,
3444 ..
3445 }
3446 | TransactionExpiration::Validity {
3447 min_epoch,
3448 max_epoch,
3449 min_timestamp,
3450 max_timestamp,
3451 chain,
3452 ..
3453 } => {
3454 if matches!(self.expiration(), TransactionExpiration::Validity { .. }) {
3457 fp_ensure!(
3458 config.allowed_proposers(),
3459 UserInputError::Unsupported(
3460 "Restricting the proposers of a transaction is not supported"
3461 .to_string(),
3462 )
3463 .into()
3464 );
3465 }
3466
3467 if let Some(allowed_proposers) = self.expiration().allowed_proposers(context.epoch)
3470 {
3471 let proposers = &allowed_proposers.proposers;
3472
3473 let max_proposers =
3480 paid_proposer_count(self.gas_data.price, context.reference_gas_price)
3481 .min(context.committee_size as u64);
3482 fp_ensure!(
3483 proposers.len() as u64 <= max_proposers,
3484 UserInputError::InvalidExpiration {
3485 error: format!(
3486 "allowed_proposers has {} entries, but at most {max_proposers} \
3487 are permitted with gas price {}, reference gas price {}, and a \
3488 committee of {}",
3489 proposers.len(),
3490 self.gas_data.price,
3491 context.reference_gas_price,
3492 context.committee_size,
3493 ),
3494 }
3495 .into()
3496 );
3497
3498 fp_ensure!(
3501 proposers.iter().is_sorted_by(|a, b| a < b),
3502 UserInputError::InvalidExpiration {
3503 error: "allowed_proposers must be strictly increasing".to_string(),
3504 }
3505 .into()
3506 );
3507
3508 if let Some(out_of_range) =
3511 proposers.iter().find(|i| **i >= context.committee_size)
3512 {
3513 return Err(UserInputError::InvalidExpiration {
3514 error: format!(
3515 "allowed_proposers contains index {out_of_range}, but the \
3516 committee has {} members",
3517 context.committee_size,
3518 ),
3519 }
3520 .into());
3521 }
3522 }
3523
3524 if min_timestamp.is_some() || max_timestamp.is_some() {
3525 return Err(UserInputError::Unsupported(
3526 "Timestamp-based transaction expiration is not yet supported".to_string(),
3527 )
3528 .into());
3529 }
3530
3531 match (min_epoch, max_epoch) {
3536 _ if config.relax_valid_during_for_owned_inputs() => (),
3537 (Some(min), Some(max)) => {
3538 if config.enable_multi_epoch_transaction_expiration() {
3539 if !(*max == *min || *max == min.saturating_add(1)) {
3540 return Err(UserInputError::Unsupported(
3541 "max_epoch must be at most min_epoch + 1".to_string(),
3542 )
3543 .into());
3544 }
3545 } else if min != max {
3546 return Err(UserInputError::Unsupported(
3547 "min_epoch must equal max_epoch".to_string(),
3548 )
3549 .into());
3550 }
3551 }
3552 _ => {
3553 return Err(UserInputError::Unsupported(
3554 "Both min_epoch and max_epoch must be specified".to_string(),
3555 )
3556 .into());
3557 }
3558 }
3559
3560 if *chain != context.chain_identifier {
3561 return Err(UserInputError::InvalidChainId {
3562 provided: format!("{:?}", chain),
3563 expected: format!("{:?}", context.chain_identifier),
3564 }
3565 .into());
3566 }
3567
3568 if let Some(min) = min_epoch
3569 && context.epoch < *min
3570 {
3571 return Err(SuiErrorKind::TransactionExpired.into());
3572 }
3573 if let Some(max) = max_epoch
3574 && context.epoch > *max
3575 {
3576 return Err(SuiErrorKind::TransactionExpired.into());
3577 }
3578 }
3579 }
3580
3581 if self.has_funds_withdrawals() {
3582 fp_ensure!(
3585 !self.gas().is_empty() || config.enable_address_balance_gas_payments(),
3586 UserInputError::MissingGasPayment.into()
3587 );
3588
3589 fp_ensure!(
3590 config.enable_accumulators(),
3591 UserInputError::Unsupported("Address balance withdraw is not enabled".to_string())
3592 .into()
3593 );
3594
3595 let max_withdraws = 10;
3597 let mut num_reservations = 0;
3598
3599 for withdraw in self.kind.get_funds_withdrawals() {
3600 num_reservations += 1;
3601 match withdraw.withdraw_from {
3602 WithdrawFrom::Sender => (),
3603 WithdrawFrom::Sponsor => {
3604 return Err(UserInputError::InvalidWithdrawReservation {
3605 error: "Explicit sponsor withdrawals are not yet supported".to_string(),
3606 }
3607 .into());
3608 }
3609 WithdrawFrom::SenderAllowance { .. } => {
3612 fp_ensure!(
3613 config.enable_allowances(),
3614 UserInputError::Unsupported(
3615 "Allowance withdrawals are not enabled".to_string()
3616 )
3617 .into()
3618 );
3619 }
3620 }
3621
3622 match withdraw.reservation {
3623 Reservation::MaxAmountU64(amount) => {
3624 fp_ensure!(
3625 amount > 0,
3626 UserInputError::InvalidWithdrawReservation {
3627 error: "Balance withdraw reservation amount must be non-zero"
3628 .to_string(),
3629 }
3630 .into()
3631 );
3632 }
3633 };
3634 }
3635
3636 for parsed in self.parsed_coin_reservations(context.chain_identifier) {
3637 num_reservations += 1;
3638 if parsed.epoch_id() != context.epoch && parsed.epoch_id() + 1 != context.epoch {
3642 return Err(SuiErrorKind::TransactionExpired.into());
3643 }
3644 if parsed.reservation_amount() == 0 {
3645 return Err(UserInputError::InvalidWithdrawReservation {
3646 error: "Balance withdraw reservation amount must be non-zero".to_string(),
3647 }
3648 .into());
3649 }
3650 }
3651
3652 if config.enable_address_balance_gas_payments()
3654 && self.is_gas_paid_from_address_balance()
3655 {
3656 num_reservations += 1;
3657 }
3658
3659 fp_ensure!(
3660 num_reservations <= max_withdraws,
3661 UserInputError::InvalidWithdrawReservation {
3662 error: format!(
3663 "Maximum number of balance withdraw reservations is {max_withdraws}"
3664 ),
3665 }
3666 .into()
3667 );
3668 }
3669
3670 if config.enable_accumulators()
3671 && config.enable_address_balance_gas_payments()
3672 && self.is_gas_paid_from_address_balance()
3673 {
3674 if config.address_balance_gas_reject_gas_coin_arg()
3675 && let TransactionKind::ProgrammableTransaction(pt) = &self.kind
3676 {
3677 fp_ensure!(
3678 !pt.commands.iter().any(|cmd| cmd.is_gas_coin_used()),
3679 UserInputError::Unsupported(
3680 "Argument::GasCoin is not supported with address balance gas payments"
3681 .to_string(),
3682 )
3683 .into()
3684 );
3685 }
3686
3687 let is_gasless = config.enable_gasless() && self.is_gasless_transaction();
3688 if config.address_balance_gas_check_rgp_at_signing() && !is_gasless {
3689 fp_ensure!(
3690 self.gas_data.price >= context.reference_gas_price,
3691 UserInputError::GasPriceUnderRGP {
3692 gas_price: self.gas_data.price,
3693 reference_gas_price: context.reference_gas_price,
3694 }
3695 .into()
3696 );
3697 }
3698
3699 if !config.relax_valid_during_for_owned_inputs() {
3704 if matches!(self.expiration(), TransactionExpiration::None) {
3705 return Err(UserInputError::MissingGasPayment.into());
3708 }
3709
3710 if !self.expiration().is_replay_protected() {
3711 return Err(UserInputError::InvalidExpiration {
3712 error: "Address balance gas payments require ValidDuring expiration"
3713 .to_string(),
3714 }
3715 .into());
3716 }
3717 }
3718 } else {
3719 fp_ensure!(
3720 !self.gas().is_empty(),
3721 UserInputError::MissingGasPayment.into()
3722 );
3723 }
3724
3725 let gas_len = self.gas().len();
3726 let max_gas_objects = config.max_gas_payment_objects() as usize;
3727
3728 let within_limit = if config.correct_gas_payment_limit_check() {
3729 gas_len <= max_gas_objects
3730 } else {
3731 gas_len < max_gas_objects
3732 };
3733
3734 fp_ensure!(
3735 within_limit,
3736 UserInputError::SizeLimitExceeded {
3737 limit: "maximum number of gas payment objects".to_string(),
3738 value: config.max_gas_payment_objects().to_string()
3739 }
3740 .into()
3741 );
3742
3743 if !config.enable_coin_reservation_obj_refs() {
3744 for (_, _, gas_digest) in self.gas() {
3745 fp_ensure!(
3746 !ParsedDigest::is_coin_reservation_digest(gas_digest),
3747 UserInputError::GasObjectNotOwnedObject {
3748 owner: Owner::AddressOwner(self.sender)
3749 }
3750 .into()
3751 );
3752 }
3753 } else {
3754 let sui_accumulator_id =
3757 *AccumulatorValue::get_field_id(self.sender, &Balance::type_tag(GAS::type_tag()))?
3758 .inner();
3759
3760 for gas_ref in self.gas() {
3761 if let Some(parsed) =
3762 ParsedObjectRefWithdrawal::parse(gas_ref, context.chain_identifier)
3763 {
3764 fp_ensure!(
3767 self.gas_owner() == self.sender,
3768 UserInputError::GasObjectNotOwnedObject {
3769 owner: Owner::AddressOwner(self.sender)
3770 }
3771 .into()
3772 );
3773 fp_ensure!(
3774 parsed.unmasked_object_id == sui_accumulator_id,
3775 UserInputError::GasObjectNotOwnedObject {
3776 owner: Owner::AddressOwner(self.sender)
3777 }
3778 .into()
3779 );
3780 }
3781 }
3782 }
3783
3784 if !self.is_system_tx() {
3785 fp_ensure!(
3786 !check_for_gas_price_too_high(config.gas_model_version())
3787 || self.gas_data.price < config.max_gas_price(),
3788 UserInputError::GasPriceTooHigh {
3789 max_gas_price: config.max_gas_price(),
3790 }
3791 .into()
3792 );
3793 let cost_table = SuiCostTable::new(config, self.gas_data.price);
3794
3795 fp_ensure!(
3796 self.gas_data.budget <= cost_table.max_gas_budget,
3797 UserInputError::GasBudgetTooHigh {
3798 gas_budget: self.gas_data().budget,
3799 max_budget: cost_table.max_gas_budget,
3800 }
3801 .into()
3802 );
3803 let is_gasless = config.enable_gasless() && self.is_gasless_transaction();
3804 if is_gasless {
3805 fp_ensure!(
3806 self.gas_data.budget == 0,
3807 UserInputError::Unsupported(
3808 "gas_budget must be 0 for gasless transactions".to_string()
3809 )
3810 .into()
3811 );
3812 } else {
3813 fp_ensure!(
3814 self.gas_data.budget >= cost_table.min_transaction_cost,
3815 UserInputError::GasBudgetTooLow {
3816 gas_budget: self.gas_data.budget,
3817 min_budget: cost_table.min_transaction_cost,
3818 }
3819 .into()
3820 );
3821 }
3822 }
3823
3824 self.kind().validity_check(config)?;
3825
3826 if config.enable_gasless() && self.is_gasless_transaction() {
3827 let TransactionKind::ProgrammableTransaction(pt) = &self.kind else {
3828 debug_fatal!("gasless transaction is not a ProgrammableTransaction");
3829 return Err(UserInputError::Unsupported(
3830 "Gasless transactions must be programmable transactions".to_string(),
3831 )
3832 .into());
3833 };
3834 pt.validate_gasless_transaction(config)?;
3835 }
3836
3837 self.check_sponsorship()?;
3838 Ok(())
3839 }
3840
3841 fn is_sponsored_tx(&self) -> bool {
3843 self.gas_owner() != self.sender
3844 }
3845
3846 fn is_gas_paid_from_address_balance(&self) -> bool {
3850 is_gas_paid_from_address_balance(&self.gas_data, &self.kind)
3851 }
3852
3853 fn is_gasless_transaction(&self) -> bool {
3854 is_gasless_transaction(&self.gas_data, &self.kind)
3855 }
3856
3857 fn check_sponsorship(&self) -> UserInputResult {
3859 if self.gas_owner() == self.sender() {
3861 return Ok(());
3862 }
3863 if matches!(&self.kind, TransactionKind::ProgrammableTransaction(_)) {
3864 return Ok(());
3865 }
3866 Err(UserInputError::UnsupportedSponsoredTransactionKind)
3867 }
3868
3869 fn is_end_of_epoch_tx(&self) -> bool {
3870 matches!(
3871 self.kind,
3872 TransactionKind::ChangeEpoch(_) | TransactionKind::EndOfEpochTransaction(_)
3873 )
3874 }
3875
3876 fn is_consensus_commit_prologue(&self) -> bool {
3877 match &self.kind {
3878 TransactionKind::ConsensusCommitPrologue(_)
3879 | TransactionKind::ConsensusCommitPrologueV2(_)
3880 | TransactionKind::ConsensusCommitPrologueV3(_)
3881 | TransactionKind::ConsensusCommitPrologueV4(_) => true,
3882
3883 TransactionKind::ProgrammableTransaction(_)
3884 | TransactionKind::ProgrammableSystemTransaction(_)
3885 | TransactionKind::ChangeEpoch(_)
3886 | TransactionKind::Genesis(_)
3887 | TransactionKind::AuthenticatorStateUpdate(_)
3888 | TransactionKind::EndOfEpochTransaction(_)
3889 | TransactionKind::RandomnessStateUpdate(_) => false,
3890 }
3891 }
3892
3893 fn is_system_tx(&self) -> bool {
3894 self.kind.is_system_tx()
3895 }
3896
3897 fn is_genesis_tx(&self) -> bool {
3898 matches!(self.kind, TransactionKind::Genesis(_))
3899 }
3900
3901 fn sender_mut_for_testing(&mut self) -> &mut SuiAddress {
3902 &mut self.sender
3903 }
3904
3905 fn gas_data_mut(&mut self) -> &mut GasData {
3906 &mut self.gas_data
3907 }
3908
3909 fn expiration_mut_for_testing(&mut self) -> &mut TransactionExpiration {
3910 &mut self.expiration
3911 }
3912}
3913
3914impl TransactionDataV1 {
3915 fn accumulate_funds_withdrawals(
3916 &self,
3917 chain_identifier: ChainIdentifier,
3918 coin_resolver: &dyn CoinReservationResolverTrait,
3919 include_gas_payment: bool,
3920 ) -> UserInputResult<BTreeMap<AccumulatorObjId, (u64, TypeTag, SuiAddress)>> {
3921 let mut withdraws: Vec<_> = self.get_funds_withdrawals().collect();
3922
3923 for withdraw in self.parsed_coin_reservations(chain_identifier) {
3924 let withdrawal_arg =
3925 coin_resolver.resolve_funds_withdrawal(self.sender(), withdraw, None)?;
3926 withdraws.push(withdrawal_arg);
3927 }
3928
3929 if include_gas_payment {
3930 withdraws.extend(self.get_funds_withdrawal_for_gas_payment());
3931 }
3932
3933 let mut withdraw_map: BTreeMap<AccumulatorObjId, (u64, TypeTag, SuiAddress)> =
3934 BTreeMap::new();
3935 for withdraw in withdraws {
3936 let reserved_amount = match &withdraw.reservation {
3937 Reservation::MaxAmountU64(amount) => {
3938 if *amount == 0 {
3939 return Err(UserInputError::InvalidWithdrawReservation {
3940 error: "Balance withdraw reservation amount must be non-zero"
3941 .to_string(),
3942 });
3943 }
3944 *amount
3945 }
3946 };
3947
3948 let account_address = withdraw.owner_for_withdrawal(self);
3949 let type_tag = withdraw.type_arg.to_type_tag();
3950 let account_id =
3951 AccumulatorValue::get_field_id(account_address, &type_tag).map_err(|e| {
3952 UserInputError::InvalidWithdrawReservation {
3953 error: e.to_string(),
3954 }
3955 })?;
3956
3957 let (current_amount, _, _) = withdraw_map
3958 .entry(account_id)
3959 .or_insert_with(|| (0, type_tag, account_address));
3960 *current_amount = current_amount.checked_add(reserved_amount).ok_or(
3961 UserInputError::InvalidWithdrawReservation {
3962 error: "Balance withdraw reservation overflow".to_string(),
3963 },
3964 )?;
3965 }
3966
3967 Ok(withdraw_map)
3968 }
3969
3970 fn get_funds_withdrawal_for_gas_payment(&self) -> Option<FundsWithdrawalArg> {
3971 if self.is_gas_paid_from_address_balance() && self.gas_data().budget > 0 {
3972 Some(if self.sender() != self.gas_owner() {
3973 FundsWithdrawalArg::balance_from_sponsor(self.gas_data().budget, GAS::type_tag())
3974 } else {
3975 FundsWithdrawalArg::balance_from_sender(self.gas_data().budget, GAS::type_tag())
3976 })
3977 } else {
3978 None
3979 }
3980 }
3981
3982 fn get_funds_withdrawals(&self) -> impl Iterator<Item = FundsWithdrawalArg> + '_ {
3983 self.kind.get_funds_withdrawals().cloned()
3984 }
3985
3986 fn coin_reservation_obj_refs(&self) -> impl Iterator<Item = ObjectRef> {
3987 self.kind
3988 .get_coin_reservation_obj_refs()
3989 .chain(self.gas().iter().filter_map(|gas_ref| {
3990 if ParsedDigest::is_coin_reservation_digest(&gas_ref.2) {
3991 Some(*gas_ref)
3992 } else {
3993 None
3994 }
3995 }))
3996 }
3997
3998 fn parsed_coin_reservations(
3999 &self,
4000 chain_identifier: ChainIdentifier,
4001 ) -> impl Iterator<Item = ParsedObjectRefWithdrawal> {
4002 self.coin_reservation_obj_refs().map(move |obj_ref| {
4003 ParsedObjectRefWithdrawal::parse(&obj_ref, chain_identifier).unwrap()
4004 })
4005 }
4006}
4007
4008pub struct TxValidityCheckContext<'a> {
4009 pub config: &'a ProtocolConfig,
4010 pub epoch: EpochId,
4011 pub chain_identifier: ChainIdentifier,
4012 pub reference_gas_price: u64,
4013 pub committee_size: u32,
4015}
4016
4017impl<'a> TxValidityCheckContext<'a> {
4018 pub fn from_cfg_for_testing(config: &'a ProtocolConfig) -> Self {
4019 Self {
4020 config,
4021 epoch: 0,
4022 chain_identifier: ChainIdentifier::default(),
4023 reference_gas_price: 1000,
4024 committee_size: 4,
4025 }
4026 }
4027}
4028
4029#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
4030pub struct SenderSignedData(SizeOneVec<SenderSignedTransaction>);
4031
4032#[derive(Debug, Clone, PartialEq, Eq, Hash)]
4033pub struct SenderSignedTransaction {
4034 pub intent_message: IntentMessage<TransactionData>,
4035 pub tx_signatures: Vec<GenericSignature>,
4039}
4040
4041impl Serialize for SenderSignedTransaction {
4042 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
4043 where
4044 S: serde::Serializer,
4045 {
4046 #[derive(Serialize)]
4047 #[serde(rename = "SenderSignedTransaction")]
4048 struct SignedTxn<'a> {
4049 intent_message: &'a IntentMessage<TransactionData>,
4050 tx_signatures: &'a Vec<GenericSignature>,
4051 }
4052
4053 if self.intent_message().intent != Intent::sui_transaction() {
4054 return Err(serde::ser::Error::custom("invalid Intent for Transaction"));
4055 }
4056
4057 let txn = SignedTxn {
4058 intent_message: self.intent_message(),
4059 tx_signatures: &self.tx_signatures,
4060 };
4061 txn.serialize(serializer)
4062 }
4063}
4064
4065impl<'de> Deserialize<'de> for SenderSignedTransaction {
4066 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
4067 where
4068 D: serde::Deserializer<'de>,
4069 {
4070 #[derive(Deserialize)]
4071 #[serde(rename = "SenderSignedTransaction")]
4072 struct SignedTxn {
4073 intent_message: IntentMessage<TransactionData>,
4074 tx_signatures: Vec<GenericSignature>,
4075 }
4076
4077 let SignedTxn {
4078 intent_message,
4079 tx_signatures,
4080 } = Deserialize::deserialize(deserializer)?;
4081
4082 if intent_message.intent != Intent::sui_transaction() {
4083 return Err(serde::de::Error::custom("invalid Intent for Transaction"));
4084 }
4085
4086 Ok(Self {
4087 intent_message,
4088 tx_signatures,
4089 })
4090 }
4091}
4092
4093impl SenderSignedTransaction {
4094 pub(crate) fn get_signer_sig_mapping(
4096 &self,
4097 verify_legacy_zklogin_address: bool,
4098 ) -> SuiResult<BTreeMap<SuiAddress, (u8, &GenericSignature)>> {
4099 let mut mapping = BTreeMap::new();
4100 for (idx, sig) in self.tx_signatures.iter().enumerate() {
4101 if verify_legacy_zklogin_address && let GenericSignature::ZkLoginAuthenticator(z) = sig
4102 {
4103 mapping.insert(SuiAddress::try_from_padded(&z.inputs)?, (idx as u8, sig));
4105 }
4106 let address = sig.try_into()?;
4107 mapping.insert(address, (idx as u8, sig));
4108 }
4109 Ok(mapping)
4110 }
4111
4112 pub fn intent_message(&self) -> &IntentMessage<TransactionData> {
4113 &self.intent_message
4114 }
4115}
4116
4117fn mldsa65_multisig_unsupported() -> SuiError {
4121 SuiErrorKind::UserInputError {
4122 error: UserInputError::Unsupported(
4123 "ML-DSA-65 multisig members are not enabled on this network".to_string(),
4124 ),
4125 }
4126 .into()
4127}
4128
4129impl SenderSignedData {
4130 pub fn new(tx_data: TransactionData, tx_signatures: Vec<GenericSignature>) -> Self {
4131 Self(SizeOneVec::new(SenderSignedTransaction {
4132 intent_message: IntentMessage::new(Intent::sui_transaction(), tx_data),
4133 tx_signatures,
4134 }))
4135 }
4136
4137 pub fn new_from_sender_signature(tx_data: TransactionData, tx_signature: Signature) -> Self {
4138 Self(SizeOneVec::new(SenderSignedTransaction {
4139 intent_message: IntentMessage::new(Intent::sui_transaction(), tx_data),
4140 tx_signatures: vec![tx_signature.into()],
4141 }))
4142 }
4143
4144 pub fn inner(&self) -> &SenderSignedTransaction {
4145 self.0.element()
4146 }
4147
4148 pub fn into_inner(self) -> SenderSignedTransaction {
4149 self.0.into_inner()
4150 }
4151
4152 pub fn inner_mut(&mut self) -> &mut SenderSignedTransaction {
4153 self.0.element_mut()
4154 }
4155
4156 pub fn add_signature(&mut self, new_signature: Signature) {
4159 self.inner_mut().tx_signatures.push(new_signature.into());
4160 }
4161
4162 pub(crate) fn get_signer_sig_mapping(
4163 &self,
4164 verify_legacy_zklogin_address: bool,
4165 ) -> SuiResult<BTreeMap<SuiAddress, (u8, &GenericSignature)>> {
4166 self.inner()
4167 .get_signer_sig_mapping(verify_legacy_zklogin_address)
4168 }
4169
4170 pub fn transaction_data(&self) -> &TransactionData {
4171 &self.intent_message().value
4172 }
4173
4174 pub fn intent_message(&self) -> &IntentMessage<TransactionData> {
4175 self.inner().intent_message()
4176 }
4177
4178 pub fn tx_signatures(&self) -> &[GenericSignature] {
4179 &self.inner().tx_signatures
4180 }
4181
4182 pub fn has_zklogin_sig(&self) -> bool {
4183 self.tx_signatures().iter().any(|sig| sig.is_zklogin())
4184 }
4185
4186 pub fn has_upgraded_multisig(&self) -> bool {
4187 self.tx_signatures()
4188 .iter()
4189 .any(|sig| sig.is_upgraded_multisig())
4190 }
4191
4192 #[cfg(test)]
4193 pub fn intent_message_mut_for_testing(&mut self) -> &mut IntentMessage<TransactionData> {
4194 &mut self.inner_mut().intent_message
4195 }
4196
4197 pub fn tx_signatures_mut_for_testing(&mut self) -> &mut Vec<GenericSignature> {
4199 &mut self.inner_mut().tx_signatures
4200 }
4201
4202 pub fn full_message_digest_with_alias_versions(
4204 &self,
4205 alias_versions: &Vec<(SuiAddress, Option<SequenceNumber>)>,
4206 ) -> SenderSignedDataDigest {
4207 let mut digest = DefaultHash::default();
4208 bcs::serialize_into(&mut digest, self).expect("serialization should not fail");
4209 bcs::serialize_into(&mut digest, alias_versions).expect("serialization should not fail");
4210 let hash = digest.finalize();
4211 SenderSignedDataDigest::new(hash.into())
4212 }
4213
4214 pub fn serialized_size(&self) -> SuiResult<usize> {
4215 bcs::serialized_size(self).map_err(|e| {
4216 SuiErrorKind::TransactionSerializationError {
4217 error: e.to_string(),
4218 }
4219 .into()
4220 })
4221 }
4222
4223 fn check_user_signature_protocol_compatibility(&self, config: &ProtocolConfig) -> SuiResult {
4224 for sig in &self.inner().tx_signatures {
4225 match sig {
4226 GenericSignature::MultiSig(multisig) => {
4227 if !config.upgraded_multisig_supported() {
4228 return Err(SuiErrorKind::UserInputError {
4229 error: UserInputError::Unsupported(
4230 "upgraded multisig format not enabled on this network".to_string(),
4231 ),
4232 }
4233 .into());
4234 }
4235 if multisig.uses_mldsa65() && !config.mldsa65_auth() {
4236 return Err(mldsa65_multisig_unsupported());
4237 }
4238 }
4239 GenericSignature::ZkLoginAuthenticator(_) => {
4240 if !config.zklogin_auth() {
4241 return Err(SuiErrorKind::UserInputError {
4242 error: UserInputError::Unsupported(
4243 "zklogin is not enabled on this network".to_string(),
4244 ),
4245 }
4246 .into());
4247 }
4248 }
4249 GenericSignature::PasskeyAuthenticator(_) => {
4250 if !config.passkey_auth() {
4251 return Err(SuiErrorKind::UserInputError {
4252 error: UserInputError::Unsupported(
4253 "passkey is not enabled on this network".to_string(),
4254 ),
4255 }
4256 .into());
4257 }
4258 }
4259 GenericSignature::Signature(_) => {
4260 if sig.is_mldsa65() && !config.mldsa65_auth() {
4262 return Err(SuiErrorKind::UserInputError {
4263 error: UserInputError::Unsupported(
4264 "ML-DSA-65 signatures are not enabled on this network".to_string(),
4265 ),
4266 }
4267 .into());
4268 }
4269 }
4270 GenericSignature::MultiSigLegacy(multisig) => {
4271 if multisig.uses_mldsa65() && !config.mldsa65_auth() {
4272 return Err(mldsa65_multisig_unsupported());
4273 }
4274 }
4275 }
4276 }
4277
4278 Ok(())
4279 }
4280
4281 pub fn validity_check(&self, context: &TxValidityCheckContext<'_>) -> Result<usize, SuiError> {
4284 self.check_user_signature_protocol_compatibility(context.config)?;
4286
4287 let tx_data = &self.transaction_data();
4292 fp_ensure!(
4293 !tx_data.is_system_tx(),
4294 SuiErrorKind::UserInputError {
4295 error: UserInputError::Unsupported(
4296 "SenderSignedData must not contain system transaction".to_string()
4297 )
4298 }
4299 .into()
4300 );
4301
4302 let tx_size = self.serialized_size()?;
4304 let max_tx_size_bytes = context.config.max_tx_size_bytes();
4305 fp_ensure!(
4306 tx_size as u64 <= max_tx_size_bytes,
4307 SuiErrorKind::UserInputError {
4308 error: UserInputError::SizeLimitExceeded {
4309 limit: format!(
4310 "serialized transaction size exceeded maximum of {max_tx_size_bytes}"
4311 ),
4312 value: tx_size.to_string(),
4313 }
4314 }
4315 .into()
4316 );
4317
4318 if context.config.enable_gasless() && tx_data.is_gasless_transaction() {
4319 let gasless_max = context.config.get_gasless_max_tx_size_bytes();
4320 fp_ensure!(
4321 tx_size as u64 <= gasless_max,
4322 SuiErrorKind::UserInputError {
4323 error: UserInputError::SizeLimitExceeded {
4324 limit: format!(
4325 "serialized gasless transaction size exceeded maximum of {gasless_max}"
4326 ),
4327 value: tx_size.to_string(),
4328 }
4329 }
4330 .into()
4331 );
4332 }
4333
4334 tx_data.validity_check(context)?;
4335
4336 Ok(tx_size)
4337 }
4338}
4339
4340impl Message for SenderSignedData {
4341 type DigestType = TransactionDigest;
4342 const SCOPE: IntentScope = IntentScope::SenderSignedTransaction;
4343
4344 fn digest(&self) -> Self::DigestType {
4346 self.intent_message().value.digest()
4347 }
4348}
4349
4350impl<S> Envelope<SenderSignedData, S> {
4351 pub fn sender_address(&self) -> SuiAddress {
4352 self.data().intent_message().value.sender()
4353 }
4354
4355 pub fn gas_owner(&self) -> SuiAddress {
4356 self.data().intent_message().value.gas_owner()
4357 }
4358
4359 pub fn gas(&self) -> &[ObjectRef] {
4360 self.data().intent_message().value.gas()
4361 }
4362
4363 pub fn is_consensus_tx(&self) -> bool {
4364 self.transaction_data().has_funds_withdrawals()
4365 || self.shared_input_objects().next().is_some()
4366 }
4367
4368 pub fn shared_input_objects(&self) -> impl Iterator<Item = SharedInputObject> + '_ {
4369 self.data()
4370 .inner()
4371 .intent_message
4372 .value
4373 .shared_input_objects()
4374 .into_iter()
4375 }
4376
4377 pub fn key(&self) -> TransactionKey {
4379 match &self.data().intent_message().value.kind() {
4380 TransactionKind::RandomnessStateUpdate(rsu) => {
4381 TransactionKey::RandomnessRound(rsu.epoch, rsu.randomness_round)
4382 }
4383 _ => TransactionKey::Digest(*self.digest()),
4384 }
4385 }
4386
4387 pub fn non_digest_key(&self) -> Option<TransactionKey> {
4392 match &self.data().intent_message().value.kind() {
4393 TransactionKind::RandomnessStateUpdate(rsu) => Some(TransactionKey::RandomnessRound(
4394 rsu.epoch,
4395 rsu.randomness_round,
4396 )),
4397 _ => None,
4398 }
4399 }
4400
4401 pub fn is_system_tx(&self) -> bool {
4402 self.data().intent_message().value.is_system_tx()
4403 }
4404
4405 pub fn is_sponsored_tx(&self) -> bool {
4406 self.data().intent_message().value.is_sponsored_tx()
4407 }
4408}
4409
4410impl Transaction {
4411 pub fn from_data_and_signer(
4412 data: TransactionData,
4413 signers: Vec<&dyn Signer<Signature>>,
4414 ) -> Self {
4415 let signatures = {
4416 let intent_msg = IntentMessage::new(Intent::sui_transaction(), &data);
4417 signers
4418 .into_iter()
4419 .map(|s| Signature::new_secure(&intent_msg, s))
4420 .collect()
4421 };
4422 Self::from_data(data, signatures)
4423 }
4424
4425 pub fn from_data(data: TransactionData, signatures: Vec<Signature>) -> Self {
4427 Self::from_generic_sig_data(data, signatures.into_iter().map(|s| s.into()).collect())
4428 }
4429
4430 pub fn signature_from_signer(
4431 data: TransactionData,
4432 intent: Intent,
4433 signer: &dyn Signer<Signature>,
4434 ) -> Signature {
4435 let intent_msg = IntentMessage::new(intent, data);
4436 Signature::new_secure(&intent_msg, signer)
4437 }
4438
4439 pub fn from_generic_sig_data(data: TransactionData, signatures: Vec<GenericSignature>) -> Self {
4440 Self::new(SenderSignedData::new(data, signatures))
4441 }
4442
4443 pub fn to_tx_bytes_and_signatures(&self) -> (Base64, Vec<Base64>) {
4446 (
4447 Base64::from_bytes(&bcs::to_bytes(&self.data().intent_message().value).unwrap()),
4448 self.data()
4449 .inner()
4450 .tx_signatures
4451 .iter()
4452 .map(|s| Base64::from_bytes(s.as_ref()))
4453 .collect(),
4454 )
4455 }
4456}
4457
4458impl VerifiedTransaction {
4459 pub fn new_change_epoch(
4460 next_epoch: EpochId,
4461 protocol_version: ProtocolVersion,
4462 storage_charge: u64,
4463 computation_charge: u64,
4464 storage_rebate: u64,
4465 non_refundable_storage_fee: u64,
4466 epoch_start_timestamp_ms: u64,
4467 system_packages: Vec<(SequenceNumber, Vec<Vec<u8>>, Vec<ObjectID>)>,
4468 ) -> Self {
4469 ChangeEpoch {
4470 epoch: next_epoch,
4471 protocol_version,
4472 storage_charge,
4473 computation_charge,
4474 storage_rebate,
4475 non_refundable_storage_fee,
4476 epoch_start_timestamp_ms,
4477 system_packages,
4478 }
4479 .pipe(TransactionKind::ChangeEpoch)
4480 .pipe(Self::new_system_transaction)
4481 }
4482
4483 pub fn new_genesis_transaction(objects: Vec<GenesisObject>) -> Self {
4484 GenesisTransaction { objects }
4485 .pipe(TransactionKind::Genesis)
4486 .pipe(Self::new_system_transaction)
4487 }
4488
4489 pub fn new_consensus_commit_prologue(
4490 epoch: u64,
4491 round: u64,
4492 commit_timestamp_ms: CheckpointTimestamp,
4493 ) -> Self {
4494 ConsensusCommitPrologue {
4495 epoch,
4496 round,
4497 commit_timestamp_ms,
4498 }
4499 .pipe(TransactionKind::ConsensusCommitPrologue)
4500 .pipe(Self::new_system_transaction)
4501 }
4502
4503 pub fn new_consensus_commit_prologue_v2(
4504 epoch: u64,
4505 round: u64,
4506 commit_timestamp_ms: CheckpointTimestamp,
4507 consensus_commit_digest: ConsensusCommitDigest,
4508 ) -> Self {
4509 ConsensusCommitPrologueV2 {
4510 epoch,
4511 round,
4512 commit_timestamp_ms,
4513 consensus_commit_digest,
4514 }
4515 .pipe(TransactionKind::ConsensusCommitPrologueV2)
4516 .pipe(Self::new_system_transaction)
4517 }
4518
4519 pub fn new_consensus_commit_prologue_v3(
4520 epoch: u64,
4521 round: u64,
4522 commit_timestamp_ms: CheckpointTimestamp,
4523 consensus_commit_digest: ConsensusCommitDigest,
4524 consensus_determined_version_assignments: ConsensusDeterminedVersionAssignments,
4525 ) -> Self {
4526 ConsensusCommitPrologueV3 {
4527 epoch,
4528 round,
4529 sub_dag_index: None,
4531 commit_timestamp_ms,
4532 consensus_commit_digest,
4533 consensus_determined_version_assignments,
4534 }
4535 .pipe(TransactionKind::ConsensusCommitPrologueV3)
4536 .pipe(Self::new_system_transaction)
4537 }
4538
4539 pub fn new_consensus_commit_prologue_v4(
4540 epoch: u64,
4541 round: u64,
4542 commit_timestamp_ms: CheckpointTimestamp,
4543 consensus_commit_digest: ConsensusCommitDigest,
4544 consensus_determined_version_assignments: ConsensusDeterminedVersionAssignments,
4545 additional_state_digest: AdditionalConsensusStateDigest,
4546 ) -> Self {
4547 ConsensusCommitPrologueV4 {
4548 epoch,
4549 round,
4550 sub_dag_index: None,
4552 commit_timestamp_ms,
4553 consensus_commit_digest,
4554 consensus_determined_version_assignments,
4555 additional_state_digest,
4556 }
4557 .pipe(TransactionKind::ConsensusCommitPrologueV4)
4558 .pipe(Self::new_system_transaction)
4559 }
4560
4561 pub fn new_authenticator_state_update(
4562 epoch: u64,
4563 round: u64,
4564 new_active_jwks: Vec<ActiveJwk>,
4565 authenticator_obj_initial_shared_version: SequenceNumber,
4566 ) -> Self {
4567 AuthenticatorStateUpdate {
4568 epoch,
4569 round,
4570 new_active_jwks,
4571 authenticator_obj_initial_shared_version,
4572 }
4573 .pipe(TransactionKind::AuthenticatorStateUpdate)
4574 .pipe(Self::new_system_transaction)
4575 }
4576
4577 pub fn new_randomness_state_update(
4578 epoch: u64,
4579 randomness_round: RandomnessRound,
4580 random_bytes: Vec<u8>,
4581 randomness_obj_initial_shared_version: SequenceNumber,
4582 ) -> Self {
4583 RandomnessStateUpdate {
4584 epoch,
4585 randomness_round,
4586 random_bytes,
4587 randomness_obj_initial_shared_version,
4588 }
4589 .pipe(TransactionKind::RandomnessStateUpdate)
4590 .pipe(Self::new_system_transaction)
4591 }
4592
4593 pub fn new_end_of_epoch_transaction(txns: Vec<EndOfEpochTransactionKind>) -> Self {
4594 TransactionKind::EndOfEpochTransaction(txns).pipe(Self::new_system_transaction)
4595 }
4596
4597 pub fn new_system_transaction(system_transaction: TransactionKind) -> Self {
4598 system_transaction
4599 .pipe(TransactionData::new_system_transaction)
4600 .pipe(|data| {
4601 SenderSignedData::new_from_sender_signature(
4602 data,
4603 Ed25519SuiSignature::from_bytes(&[0; Ed25519SuiSignature::LENGTH])
4604 .unwrap()
4605 .into(),
4606 )
4607 })
4608 .pipe(Transaction::new)
4609 .pipe(Self::new_from_verified)
4610 }
4611}
4612
4613impl VerifiedSignedTransaction {
4614 pub fn new(
4616 epoch: EpochId,
4617 transaction: VerifiedTransaction,
4618 authority: AuthorityName,
4619 secret: &dyn Signer<AuthoritySignature>,
4620 ) -> Self {
4621 Self::new_from_verified(SignedTransaction::new(
4622 epoch,
4623 transaction.into_inner().into_data(),
4624 secret,
4625 authority,
4626 ))
4627 }
4628}
4629
4630pub type Transaction = Envelope<SenderSignedData, EmptySignInfo>;
4632pub type VerifiedTransaction = VerifiedEnvelope<SenderSignedData, EmptySignInfo>;
4633pub type TrustedTransaction = TrustedEnvelope<SenderSignedData, EmptySignInfo>;
4634
4635pub type SignedTransaction = Envelope<SenderSignedData, AuthoritySignInfo>;
4637pub type VerifiedSignedTransaction = VerifiedEnvelope<SenderSignedData, AuthoritySignInfo>;
4638
4639impl Transaction {
4640 pub fn verify_signature_for_testing(
4641 &self,
4642 current_epoch: EpochId,
4643 verify_params: &VerifyParams,
4644 ) -> SuiResult {
4645 verify_sender_signed_data_message_signatures(
4646 self.data(),
4647 current_epoch,
4648 verify_params,
4649 Arc::new(VerifiedDigestCache::new_empty()),
4650 vec![],
4651 )?;
4652 Ok(())
4653 }
4654
4655 pub fn try_into_verified_for_testing(
4656 self,
4657 current_epoch: EpochId,
4658 verify_params: &VerifyParams,
4659 ) -> SuiResult<VerifiedTransaction> {
4660 self.verify_signature_for_testing(current_epoch, verify_params)?;
4661 Ok(VerifiedTransaction::new_from_verified(self))
4662 }
4663}
4664
4665impl SignedTransaction {
4666 pub fn verify_signatures_authenticated_for_testing(
4667 &self,
4668 committee: &Committee,
4669 verify_params: &VerifyParams,
4670 ) -> SuiResult {
4671 verify_sender_signed_data_message_signatures(
4672 self.data(),
4673 committee.epoch(),
4674 verify_params,
4675 Arc::new(VerifiedDigestCache::new_empty()),
4676 vec![],
4677 )?;
4678
4679 self.auth_sig().verify_secure(
4680 self.data(),
4681 Intent::sui_app(IntentScope::SenderSignedTransaction),
4682 committee,
4683 )
4684 }
4685
4686 pub fn try_into_verified_for_testing(
4687 self,
4688 committee: &Committee,
4689 verify_params: &VerifyParams,
4690 ) -> SuiResult<VerifiedSignedTransaction> {
4691 self.verify_signatures_authenticated_for_testing(committee, verify_params)?;
4692 Ok(VerifiedSignedTransaction::new_from_verified(self))
4693 }
4694}
4695
4696pub type CertifiedTransaction = Envelope<SenderSignedData, AuthorityStrongQuorumSignInfo>;
4697
4698impl CertifiedTransaction {
4699 pub fn gas_price(&self) -> u64 {
4700 self.data().transaction_data().gas_price()
4701 }
4702}
4703
4704pub type VerifiedCertificate = VerifiedEnvelope<SenderSignedData, AuthorityStrongQuorumSignInfo>;
4705pub type TrustedCertificate = TrustedEnvelope<SenderSignedData, AuthorityStrongQuorumSignInfo>;
4706
4707#[derive(Clone, Debug, Serialize, Deserialize)]
4708pub struct WithAliases<T>(
4709 T,
4710 #[serde(with = "nonempty_as_vec")] NonEmpty<(u8, Option<SequenceNumber>)>,
4711);
4712
4713impl<T> WithAliases<T> {
4714 pub fn new(tx: T, aliases: NonEmpty<(u8, Option<SequenceNumber>)>) -> Self {
4715 Self(tx, aliases)
4716 }
4717
4718 pub fn tx(&self) -> &T {
4719 &self.0
4720 }
4721
4722 pub fn aliases(&self) -> &NonEmpty<(u8, Option<SequenceNumber>)> {
4723 &self.1
4724 }
4725
4726 pub fn into_tx(self) -> T {
4727 self.0
4728 }
4729
4730 pub fn into_aliases(self) -> NonEmpty<(u8, Option<SequenceNumber>)> {
4731 self.1
4732 }
4733
4734 pub fn into_inner(self) -> (T, NonEmpty<(u8, Option<SequenceNumber>)>) {
4735 (self.0, self.1)
4736 }
4737}
4738
4739impl<T: Message, S> WithAliases<VerifiedEnvelope<T, S>> {
4740 pub fn serializable(self) -> WithAliases<TrustedEnvelope<T, S>> {
4742 WithAliases(self.0.serializable(), self.1)
4743 }
4744}
4745
4746impl<S> WithAliases<Envelope<SenderSignedData, S>> {
4747 pub fn no_aliases(tx: Envelope<SenderSignedData, S>) -> Self {
4750 let required_signers = tx.intent_message().value.required_signers();
4751 assert_eq!(required_signers.len(), tx.tx_signatures().len());
4752 let no_aliases = required_signers
4753 .iter()
4754 .enumerate()
4755 .map(|(idx, _)| (idx as u8, None))
4756 .collect::<Vec<_>>();
4757 Self::new(
4758 tx,
4759 NonEmpty::from_vec(no_aliases).expect("must have at least one required_signer"),
4760 )
4761 }
4762}
4763
4764impl<S> WithAliases<VerifiedEnvelope<SenderSignedData, S>> {
4765 pub fn no_aliases(tx: VerifiedEnvelope<SenderSignedData, S>) -> Self {
4768 let required_signers = tx.intent_message().value.required_signers();
4769 assert_eq!(required_signers.len(), tx.tx_signatures().len());
4770 let no_aliases = required_signers
4771 .iter()
4772 .enumerate()
4773 .map(|(idx, _)| (idx as u8, None))
4774 .collect::<Vec<_>>();
4775 Self::new(
4776 tx,
4777 NonEmpty::from_vec(no_aliases).expect("must have at least one required_signer"),
4778 )
4779 }
4780}
4781
4782pub type TransactionWithAliases = WithAliases<Transaction>;
4783pub type VerifiedTransactionWithAliases = WithAliases<VerifiedTransaction>;
4784pub type TrustedTransactionWithAliases = WithAliases<TrustedTransaction>;
4785
4786#[derive(Clone, Debug, Serialize, Deserialize)]
4791pub struct DeprecatedWithAliases<T>(
4792 T,
4793 #[serde(with = "nonempty_as_vec")] NonEmpty<(SuiAddress, Option<SequenceNumber>)>,
4794);
4795
4796impl<T> DeprecatedWithAliases<T> {
4797 pub fn into_inner(self) -> (T, NonEmpty<(SuiAddress, Option<SequenceNumber>)>) {
4798 (self.0, self.1)
4799 }
4800}
4801
4802impl<T: Message, S> From<WithAliases<VerifiedEnvelope<T, S>>> for WithAliases<Envelope<T, S>> {
4803 fn from(value: WithAliases<VerifiedEnvelope<T, S>>) -> Self {
4804 Self(value.0.into(), value.1)
4805 }
4806}
4807
4808impl<T: Message, S> From<WithAliases<TrustedEnvelope<T, S>>>
4809 for WithAliases<VerifiedEnvelope<T, S>>
4810{
4811 fn from(value: WithAliases<TrustedEnvelope<T, S>>) -> Self {
4812 Self(value.0.into(), value.1)
4813 }
4814}
4815
4816mod nonempty_as_vec {
4817 use super::*;
4818 use serde::{Deserialize, Deserializer, Serialize, Serializer};
4819
4820 pub fn serialize<S, T>(value: &NonEmpty<T>, serializer: S) -> Result<S::Ok, S::Error>
4821 where
4822 S: Serializer,
4823 T: Serialize,
4824 {
4825 let vec: Vec<&T> = value.iter().collect();
4826 vec.serialize(serializer)
4827 }
4828
4829 pub fn deserialize<'de, D, T>(deserializer: D) -> Result<NonEmpty<T>, D::Error>
4830 where
4831 D: Deserializer<'de>,
4832 T: Deserialize<'de> + Clone,
4833 {
4834 use serde::de::{SeqAccess, Visitor};
4835 use std::fmt;
4836 use std::marker::PhantomData;
4837
4838 struct NonEmptyVisitor<T>(PhantomData<T>);
4839
4840 impl<'de, T> Visitor<'de> for NonEmptyVisitor<T>
4841 where
4842 T: Deserialize<'de> + Clone,
4843 {
4844 type Value = NonEmpty<T>;
4845
4846 fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
4847 formatter.write_str("a non-empty sequence")
4848 }
4849
4850 fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
4851 where
4852 A: SeqAccess<'de>,
4853 {
4854 let head = seq
4855 .next_element()?
4856 .ok_or_else(|| serde::de::Error::custom("empty vector"))?;
4857
4858 let mut tail = Vec::new();
4859 while let Some(elem) = seq.next_element()? {
4860 tail.push(elem);
4861 }
4862
4863 Ok(NonEmpty { head, tail })
4864 }
4865 }
4866
4867 deserializer.deserialize_seq(NonEmptyVisitor(PhantomData))
4868 }
4869}
4870
4871#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
4881pub enum TransactionClaim {
4882 #[deprecated(note = "Use AddressAliasesV2")]
4884 AddressAliases(
4885 #[serde(with = "nonempty_as_vec")] NonEmpty<(SuiAddress, Option<SequenceNumber>)>,
4886 ),
4887
4888 ImmutableInputObjects(Vec<ObjectID>),
4891
4892 AddressAliasesV2(#[serde(with = "nonempty_as_vec")] NonEmpty<(u8, Option<SequenceNumber>)>),
4896}
4897
4898#[derive(Clone, Debug, Serialize, Deserialize)]
4900pub struct TransactionWithClaims<T> {
4901 tx: T,
4902 claims: Vec<TransactionClaim>,
4903}
4904
4905impl<T> TransactionWithClaims<T> {
4906 pub fn new(tx: T, claims: Vec<TransactionClaim>) -> Self {
4907 Self { tx, claims }
4908 }
4909
4910 pub fn from_aliases(tx: T, aliases: NonEmpty<(u8, Option<SequenceNumber>)>) -> Self {
4912 Self {
4913 tx,
4914 claims: vec![TransactionClaim::AddressAliasesV2(aliases)],
4915 }
4916 }
4917
4918 pub fn no_aliases(tx: T) -> Self {
4920 Self { tx, claims: vec![] }
4921 }
4922
4923 pub fn tx(&self) -> &T {
4924 &self.tx
4925 }
4926
4927 pub fn into_tx(self) -> T {
4928 self.tx
4929 }
4930
4931 pub fn aliases(&self) -> Option<NonEmpty<(u8, Option<SequenceNumber>)>> {
4933 self.claims
4934 .iter()
4935 .find_map(|c| match c {
4936 TransactionClaim::AddressAliasesV2(aliases) => Some(aliases),
4937 _ => None,
4938 })
4939 .cloned()
4940 }
4941
4942 #[allow(deprecated)]
4944 pub fn aliases_v1(&self) -> Option<NonEmpty<(SuiAddress, Option<SequenceNumber>)>> {
4945 self.claims
4946 .iter()
4947 .find_map(|c| match c {
4948 TransactionClaim::AddressAliases(aliases) => Some(aliases),
4949 _ => None,
4950 })
4951 .cloned()
4952 }
4953
4954 pub fn get_immutable_objects(&self) -> Vec<ObjectID> {
4956 self.claims
4957 .iter()
4958 .find_map(|c| match c {
4959 TransactionClaim::ImmutableInputObjects(objs) => Some(objs.clone()),
4960 _ => None,
4961 })
4962 .unwrap_or_default()
4963 }
4964}
4965
4966pub type PlainTransactionWithClaims = TransactionWithClaims<Transaction>;
4967
4968impl<T: Message, S> From<WithAliases<VerifiedEnvelope<T, S>>>
4971 for TransactionWithClaims<Envelope<T, S>>
4972{
4973 fn from(value: WithAliases<VerifiedEnvelope<T, S>>) -> Self {
4974 let (tx, aliases) = value.into_inner();
4975 Self::from_aliases(tx.into(), aliases)
4976 }
4977}
4978
4979#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize, PartialOrd, Ord, Hash)]
4980pub enum InputObjectKind {
4981 MovePackage(ObjectID),
4983 ImmOrOwnedMoveObject(ObjectRef),
4985 SharedMoveObject {
4987 id: ObjectID,
4988 initial_shared_version: SequenceNumber,
4989 mutability: SharedObjectMutability,
4990 },
4991}
4992
4993#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize, PartialOrd, Ord, Hash)]
4994pub enum SharedObjectMutability {
4995 Immutable,
4997 Mutable,
4998 NonExclusiveWrite,
5002}
5003
5004impl SharedObjectMutability {
5005 pub fn is_exclusive(&self) -> bool {
5006 match self {
5007 SharedObjectMutability::Mutable => true,
5008 SharedObjectMutability::Immutable => false,
5009 SharedObjectMutability::NonExclusiveWrite => false,
5010 }
5011 }
5012}
5013
5014impl InputObjectKind {
5015 pub fn object_id(&self) -> ObjectID {
5016 self.full_object_id().id()
5017 }
5018
5019 pub fn full_object_id(&self) -> FullObjectID {
5020 match self {
5021 Self::MovePackage(id) => FullObjectID::Fastpath(*id),
5022 Self::ImmOrOwnedMoveObject((id, _, _)) => FullObjectID::Fastpath(*id),
5023 Self::SharedMoveObject {
5024 id,
5025 initial_shared_version,
5026 ..
5027 } => FullObjectID::Consensus((*id, *initial_shared_version)),
5028 }
5029 }
5030
5031 pub fn version(&self) -> Option<SequenceNumber> {
5032 match self {
5033 Self::MovePackage(..) => None,
5034 Self::ImmOrOwnedMoveObject((_, version, _)) => Some(*version),
5035 Self::SharedMoveObject { .. } => None,
5036 }
5037 }
5038
5039 pub fn object_not_found_error(&self) -> UserInputError {
5040 match *self {
5041 Self::MovePackage(package_id) => {
5042 UserInputError::DependentPackageNotFound { package_id }
5043 }
5044 Self::ImmOrOwnedMoveObject((object_id, version, _)) => UserInputError::ObjectNotFound {
5045 object_id,
5046 version: Some(version),
5047 },
5048 Self::SharedMoveObject { id, .. } => UserInputError::ObjectNotFound {
5049 object_id: id,
5050 version: None,
5051 },
5052 }
5053 }
5054
5055 pub fn is_shared_object(&self) -> bool {
5056 matches!(self, Self::SharedMoveObject { .. })
5057 }
5058}
5059
5060#[derive(Clone, Debug)]
5063pub struct ObjectReadResult {
5064 pub input_object_kind: InputObjectKind,
5065 pub object: ObjectReadResultKind,
5066}
5067
5068#[derive(Clone)]
5069pub enum ObjectReadResultKind {
5070 Object(Object),
5071 ObjectConsensusStreamEnded(SequenceNumber, TransactionDigest),
5074 CancelledTransactionSharedObject(SequenceNumber),
5076}
5077
5078impl ObjectReadResultKind {
5079 pub fn is_cancelled(&self) -> bool {
5080 matches!(
5081 self,
5082 ObjectReadResultKind::CancelledTransactionSharedObject(_)
5083 )
5084 }
5085
5086 pub fn version(&self) -> SequenceNumber {
5087 match self {
5088 ObjectReadResultKind::Object(object) => object.version(),
5089 ObjectReadResultKind::ObjectConsensusStreamEnded(seq, _) => *seq,
5090 ObjectReadResultKind::CancelledTransactionSharedObject(seq) => *seq,
5091 }
5092 }
5093}
5094
5095impl std::fmt::Debug for ObjectReadResultKind {
5096 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
5097 match self {
5098 ObjectReadResultKind::Object(obj) => {
5099 write!(f, "Object({:?})", obj.compute_object_reference())
5100 }
5101 ObjectReadResultKind::ObjectConsensusStreamEnded(seq, digest) => {
5102 write!(f, "ObjectConsensusStreamEnded({}, {:?})", seq, digest)
5103 }
5104 ObjectReadResultKind::CancelledTransactionSharedObject(seq) => {
5105 write!(f, "CancelledTransactionSharedObject({})", seq)
5106 }
5107 }
5108 }
5109}
5110
5111impl From<Object> for ObjectReadResultKind {
5112 fn from(object: Object) -> Self {
5113 Self::Object(object)
5114 }
5115}
5116
5117impl ObjectReadResult {
5118 pub fn new(input_object_kind: InputObjectKind, object: ObjectReadResultKind) -> Self {
5119 if let (
5120 InputObjectKind::ImmOrOwnedMoveObject(_),
5121 ObjectReadResultKind::ObjectConsensusStreamEnded(_, _),
5122 ) = (&input_object_kind, &object)
5123 {
5124 panic!("only consensus objects can be ObjectConsensusStreamEnded");
5125 }
5126
5127 if let (
5128 InputObjectKind::ImmOrOwnedMoveObject(_),
5129 ObjectReadResultKind::CancelledTransactionSharedObject(_),
5130 ) = (&input_object_kind, &object)
5131 {
5132 panic!("only consensus objects can be CancelledTransactionSharedObject");
5133 }
5134
5135 Self {
5136 input_object_kind,
5137 object,
5138 }
5139 }
5140
5141 pub fn id(&self) -> ObjectID {
5142 self.input_object_kind.object_id()
5143 }
5144
5145 pub fn as_object(&self) -> Option<&Object> {
5146 match &self.object {
5147 ObjectReadResultKind::Object(object) => Some(object),
5148 ObjectReadResultKind::ObjectConsensusStreamEnded(_, _) => None,
5149 ObjectReadResultKind::CancelledTransactionSharedObject(_) => None,
5150 }
5151 }
5152
5153 pub fn new_from_gas_object(gas: &Object) -> Self {
5154 let objref = gas.compute_object_reference();
5155 Self {
5156 input_object_kind: InputObjectKind::ImmOrOwnedMoveObject(objref),
5157 object: ObjectReadResultKind::Object(gas.clone()),
5158 }
5159 }
5160
5161 pub fn is_mutable(&self) -> bool {
5162 match (&self.input_object_kind, &self.object) {
5163 (InputObjectKind::MovePackage(_), _) => false,
5164 (InputObjectKind::ImmOrOwnedMoveObject(_), ObjectReadResultKind::Object(object)) => {
5165 !object.is_immutable()
5166 }
5167 (
5168 InputObjectKind::ImmOrOwnedMoveObject(_),
5169 ObjectReadResultKind::ObjectConsensusStreamEnded(_, _),
5170 ) => unreachable!(),
5171 (
5172 InputObjectKind::ImmOrOwnedMoveObject(_),
5173 ObjectReadResultKind::CancelledTransactionSharedObject(_),
5174 ) => unreachable!(),
5175 (InputObjectKind::SharedMoveObject { mutability, .. }, _) => match mutability {
5176 SharedObjectMutability::Mutable => true,
5177 SharedObjectMutability::Immutable => false,
5178 SharedObjectMutability::NonExclusiveWrite => false,
5179 },
5180 }
5181 }
5182
5183 pub fn is_shared_object(&self) -> bool {
5184 self.input_object_kind.is_shared_object()
5185 }
5186
5187 pub fn is_consensus_stream_ended(&self) -> bool {
5188 self.consensus_stream_end_info().is_some()
5189 }
5190
5191 pub fn consensus_stream_end_info(&self) -> Option<(SequenceNumber, TransactionDigest)> {
5192 match &self.object {
5193 ObjectReadResultKind::ObjectConsensusStreamEnded(v, tx) => Some((*v, *tx)),
5194 _ => None,
5195 }
5196 }
5197
5198 pub fn get_address_owned_objref(&self) -> Option<ObjectRef> {
5200 match (&self.input_object_kind, &self.object) {
5201 (InputObjectKind::MovePackage(_), _) => None,
5202 (
5203 InputObjectKind::ImmOrOwnedMoveObject(objref),
5204 ObjectReadResultKind::Object(object),
5205 ) => {
5206 if object.is_immutable() {
5207 None
5208 } else {
5209 Some(*objref)
5210 }
5211 }
5212 (
5213 InputObjectKind::ImmOrOwnedMoveObject(_),
5214 ObjectReadResultKind::ObjectConsensusStreamEnded(_, _),
5215 ) => unreachable!(),
5216 (
5217 InputObjectKind::ImmOrOwnedMoveObject(_),
5218 ObjectReadResultKind::CancelledTransactionSharedObject(_),
5219 ) => unreachable!(),
5220 (InputObjectKind::SharedMoveObject { .. }, _) => None,
5221 }
5222 }
5223
5224 pub fn is_address_owned(&self) -> bool {
5225 self.get_address_owned_objref().is_some()
5226 }
5227
5228 pub fn is_replay_protected_input(&self) -> bool {
5229 if let InputObjectKind::ImmOrOwnedMoveObject(obj_ref) = &self.input_object_kind
5230 && ParsedDigest::is_coin_reservation_digest(&obj_ref.2)
5231 {
5232 true
5233 } else {
5234 self.is_address_owned()
5235 }
5236 }
5237
5238 pub fn to_shared_input(&self) -> Option<SharedInput> {
5239 match self.input_object_kind {
5240 InputObjectKind::MovePackage(_) => None,
5241 InputObjectKind::ImmOrOwnedMoveObject(_) => None,
5242 InputObjectKind::SharedMoveObject { id, mutability, .. } => Some(match &self.object {
5243 ObjectReadResultKind::Object(obj) => {
5244 SharedInput::Existing(obj.compute_object_reference())
5245 }
5246 ObjectReadResultKind::ObjectConsensusStreamEnded(seq, digest) => {
5247 SharedInput::ConsensusStreamEnded((id, *seq, mutability, *digest))
5248 }
5249 ObjectReadResultKind::CancelledTransactionSharedObject(seq) => {
5250 SharedInput::Cancelled((id, *seq))
5251 }
5252 }),
5253 }
5254 }
5255
5256 pub fn get_previous_transaction(&self) -> Option<TransactionDigest> {
5257 match &self.object {
5258 ObjectReadResultKind::Object(obj) => Some(obj.previous_transaction),
5259 ObjectReadResultKind::ObjectConsensusStreamEnded(_, digest) => Some(*digest),
5260 ObjectReadResultKind::CancelledTransactionSharedObject(_) => None,
5261 }
5262 }
5263}
5264
5265#[derive(Clone)]
5266pub struct InputObjects {
5267 objects: Vec<ObjectReadResult>,
5268}
5269
5270impl std::fmt::Debug for InputObjects {
5271 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
5272 f.debug_list().entries(self.objects.iter()).finish()
5273 }
5274}
5275
5276#[derive(Clone)]
5279pub struct CheckedInputObjects(InputObjects);
5280
5281impl CheckedInputObjects {
5287 pub fn new_with_checked_transaction_inputs(inputs: InputObjects) -> Self {
5289 Self(inputs)
5290 }
5291
5292 pub fn new_for_genesis(input_objects: Vec<ObjectReadResult>) -> Self {
5294 Self(InputObjects::new(input_objects))
5295 }
5296
5297 pub fn new_for_replay(input_objects: InputObjects) -> Self {
5299 Self(input_objects)
5300 }
5301
5302 pub fn inner(&self) -> &InputObjects {
5303 &self.0
5304 }
5305
5306 pub fn into_inner(self) -> InputObjects {
5307 self.0
5308 }
5309}
5310
5311impl From<Vec<ObjectReadResult>> for InputObjects {
5312 fn from(objects: Vec<ObjectReadResult>) -> Self {
5313 Self::new(objects)
5314 }
5315}
5316
5317impl InputObjects {
5318 pub fn new(objects: Vec<ObjectReadResult>) -> Self {
5319 Self { objects }
5320 }
5321
5322 pub fn len(&self) -> usize {
5323 self.objects.len()
5324 }
5325
5326 pub fn is_empty(&self) -> bool {
5327 self.objects.is_empty()
5328 }
5329
5330 pub fn contains_consensus_stream_ended_objects(&self) -> bool {
5331 self.objects
5332 .iter()
5333 .any(|obj| obj.is_consensus_stream_ended())
5334 }
5335
5336 pub fn get_cancelled_objects(&self) -> Option<(Vec<ObjectID>, SequenceNumber)> {
5339 let mut contains_cancelled = false;
5340 let mut cancel_reason = None;
5341 let mut cancelled_objects = Vec::new();
5342 for obj in &self.objects {
5343 if let ObjectReadResultKind::CancelledTransactionSharedObject(version) = obj.object {
5344 contains_cancelled = true;
5345 if version == SequenceNumber::CONGESTED
5346 || version == SequenceNumber::RANDOMNESS_UNAVAILABLE
5347 {
5348 assert!(cancel_reason.is_none() || cancel_reason == Some(version));
5350 cancel_reason = Some(version);
5351 cancelled_objects.push(obj.id());
5352 }
5353 }
5354 }
5355
5356 if !cancelled_objects.is_empty() {
5357 Some((
5358 cancelled_objects,
5359 cancel_reason
5360 .expect("there should be a cancel reason if there are cancelled objects"),
5361 ))
5362 } else {
5363 assert!(!contains_cancelled);
5364 None
5365 }
5366 }
5367
5368 pub fn filter_owned_objects(&self) -> Vec<ObjectRef> {
5369 let owned_objects: Vec<_> = self
5370 .objects
5371 .iter()
5372 .filter_map(|obj| obj.get_address_owned_objref())
5373 .collect();
5374
5375 trace!(
5376 num_mutable_objects = owned_objects.len(),
5377 "Checked locks and found mutable objects"
5378 );
5379
5380 owned_objects
5381 }
5382
5383 pub fn filter_shared_objects(&self) -> Vec<SharedInput> {
5384 self.objects
5385 .iter()
5386 .filter(|obj| obj.is_shared_object())
5387 .map(|obj| {
5388 obj.to_shared_input()
5389 .expect("already filtered for shared objects")
5390 })
5391 .collect()
5392 }
5393
5394 pub fn transaction_dependencies(&self) -> BTreeSet<TransactionDigest> {
5395 self.objects
5396 .iter()
5397 .filter_map(|obj| obj.get_previous_transaction())
5398 .collect()
5399 }
5400
5401 pub fn exclusive_mutable_inputs(&self) -> BTreeMap<ObjectID, (VersionDigest, Owner)> {
5404 self.mutables_with_input_kinds()
5405 .filter_map(|(id, (version, owner, kind))| match kind {
5406 InputObjectKind::SharedMoveObject { mutability, .. } => match mutability {
5407 SharedObjectMutability::Mutable => Some((id, (version, owner))),
5408 SharedObjectMutability::Immutable => None,
5409 SharedObjectMutability::NonExclusiveWrite => None,
5410 },
5411 _ => Some((id, (version, owner))),
5412 })
5413 .collect()
5414 }
5415
5416 pub fn non_exclusive_input_objects(&self) -> BTreeMap<ObjectID, Object> {
5417 self.objects
5418 .iter()
5419 .filter_map(|read_result| {
5420 match (read_result.as_object(), read_result.input_object_kind) {
5421 (
5422 Some(object),
5423 InputObjectKind::SharedMoveObject {
5424 mutability: SharedObjectMutability::NonExclusiveWrite,
5425 ..
5426 },
5427 ) => Some((read_result.id(), object.clone())),
5428 _ => None,
5429 }
5430 })
5431 .collect()
5432 }
5433
5434 pub fn all_mutable_inputs(&self) -> BTreeMap<ObjectID, (VersionDigest, Owner)> {
5437 self.mutables_with_input_kinds()
5438 .filter_map(|(id, (version, owner, kind))| match kind {
5439 InputObjectKind::SharedMoveObject { mutability, .. } => match mutability {
5440 SharedObjectMutability::Mutable => Some((id, (version, owner))),
5441 SharedObjectMutability::Immutable => None,
5442 SharedObjectMutability::NonExclusiveWrite => Some((id, (version, owner))),
5443 },
5444 _ => Some((id, (version, owner))),
5445 })
5446 .collect()
5447 }
5448
5449 fn mutables_with_input_kinds(
5450 &self,
5451 ) -> impl Iterator<Item = (ObjectID, (VersionDigest, Owner, InputObjectKind))> + '_ {
5452 self.objects.iter().filter_map(
5453 |ObjectReadResult {
5454 input_object_kind,
5455 object,
5456 }| match (input_object_kind, object) {
5457 (InputObjectKind::MovePackage(_), _) => None,
5458 (
5459 InputObjectKind::ImmOrOwnedMoveObject(object_ref),
5460 ObjectReadResultKind::Object(object),
5461 ) => {
5462 if object.is_immutable() {
5463 None
5464 } else {
5465 Some((
5466 object_ref.0,
5467 (
5468 (object_ref.1, object_ref.2),
5469 object.owner.clone(),
5470 *input_object_kind,
5471 ),
5472 ))
5473 }
5474 }
5475 (
5476 InputObjectKind::ImmOrOwnedMoveObject(_),
5477 ObjectReadResultKind::ObjectConsensusStreamEnded(_, _),
5478 ) => {
5479 unreachable!()
5480 }
5481 (
5482 InputObjectKind::SharedMoveObject { .. },
5483 ObjectReadResultKind::ObjectConsensusStreamEnded(_, _),
5484 ) => None,
5485 (
5486 InputObjectKind::SharedMoveObject { mutability, .. },
5487 ObjectReadResultKind::Object(object),
5488 ) => match *mutability {
5489 SharedObjectMutability::Mutable => {
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 SharedObjectMutability::Immutable => None,
5497 SharedObjectMutability::NonExclusiveWrite => {
5498 let oref = object.compute_object_reference();
5499 Some((
5500 oref.0,
5501 ((oref.1, oref.2), object.owner.clone(), *input_object_kind),
5502 ))
5503 }
5504 },
5505 (
5506 InputObjectKind::ImmOrOwnedMoveObject(_),
5507 ObjectReadResultKind::CancelledTransactionSharedObject(_),
5508 ) => {
5509 unreachable!()
5510 }
5511 (
5512 InputObjectKind::SharedMoveObject { .. },
5513 ObjectReadResultKind::CancelledTransactionSharedObject(_),
5514 ) => None,
5515 },
5516 )
5517 }
5518
5519 pub fn lamport_timestamp(&self, receiving_objects: &[ObjectRef]) -> SequenceNumber {
5523 let input_versions = self
5524 .objects
5525 .iter()
5526 .filter_map(|object| match &object.object {
5527 ObjectReadResultKind::Object(object) => {
5528 object.data.try_as_move().map(MoveObject::version)
5529 }
5530 ObjectReadResultKind::ObjectConsensusStreamEnded(v, _) => Some(*v),
5531 ObjectReadResultKind::CancelledTransactionSharedObject(_) => None,
5532 })
5533 .chain(receiving_objects.iter().map(|object_ref| object_ref.1));
5534
5535 SequenceNumber::lamport_increment(input_versions)
5536 }
5537
5538 pub fn object_kinds(&self) -> impl Iterator<Item = &InputObjectKind> {
5539 self.objects.iter().map(
5540 |ObjectReadResult {
5541 input_object_kind, ..
5542 }| input_object_kind,
5543 )
5544 }
5545
5546 pub fn consensus_stream_ended_objects(&self) -> BTreeMap<ObjectID, SequenceNumber> {
5547 self.objects
5548 .iter()
5549 .filter_map(|obj| {
5550 if let InputObjectKind::SharedMoveObject {
5551 id,
5552 initial_shared_version,
5553 ..
5554 } = obj.input_object_kind
5555 {
5556 obj.is_consensus_stream_ended()
5557 .then_some((id, initial_shared_version))
5558 } else {
5559 None
5560 }
5561 })
5562 .collect()
5563 }
5564
5565 pub fn into_object_map(self) -> BTreeMap<ObjectID, Object> {
5566 self.objects
5567 .into_iter()
5568 .filter_map(|o| o.as_object().map(|object| (o.id(), object.clone())))
5569 .collect()
5570 }
5571
5572 pub fn push(&mut self, object: ObjectReadResult) {
5573 self.objects.push(object);
5574 }
5575
5576 pub fn iter(&self) -> impl Iterator<Item = &ObjectReadResult> {
5577 self.objects.iter()
5578 }
5579
5580 pub fn iter_objects(&self) -> impl Iterator<Item = &Object> {
5581 self.objects.iter().filter_map(|o| o.as_object())
5582 }
5583
5584 pub fn non_exclusive_mutable_inputs(
5585 &self,
5586 ) -> impl Iterator<Item = (ObjectID, SequenceNumber)> + '_ {
5587 self.objects.iter().filter_map(
5588 |ObjectReadResult {
5589 input_object_kind,
5590 object,
5591 }| match input_object_kind {
5592 InputObjectKind::SharedMoveObject {
5596 id,
5597 mutability: SharedObjectMutability::NonExclusiveWrite,
5598 ..
5599 } if !object.is_cancelled() => Some((*id, object.version())),
5600 _ => None,
5601 },
5602 )
5603 }
5604}
5605
5606#[derive(Clone, Debug)]
5610pub enum ReceivingObjectReadResultKind {
5611 Object(Object),
5612 PreviouslyReceivedObject,
5614}
5615
5616impl ReceivingObjectReadResultKind {
5617 pub fn as_object(&self) -> Option<&Object> {
5618 match &self {
5619 Self::Object(object) => Some(object),
5620 Self::PreviouslyReceivedObject => None,
5621 }
5622 }
5623}
5624
5625pub struct ReceivingObjectReadResult {
5626 pub object_ref: ObjectRef,
5627 pub object: ReceivingObjectReadResultKind,
5628}
5629
5630impl ReceivingObjectReadResult {
5631 pub fn new(object_ref: ObjectRef, object: ReceivingObjectReadResultKind) -> Self {
5632 Self { object_ref, object }
5633 }
5634
5635 pub fn is_previously_received(&self) -> bool {
5636 matches!(
5637 self.object,
5638 ReceivingObjectReadResultKind::PreviouslyReceivedObject
5639 )
5640 }
5641}
5642
5643impl From<Object> for ReceivingObjectReadResultKind {
5644 fn from(object: Object) -> Self {
5645 Self::Object(object)
5646 }
5647}
5648
5649pub struct ReceivingObjects {
5650 pub objects: Vec<ReceivingObjectReadResult>,
5651}
5652
5653impl ReceivingObjects {
5654 pub fn iter(&self) -> impl Iterator<Item = &ReceivingObjectReadResult> {
5655 self.objects.iter()
5656 }
5657
5658 pub fn iter_objects(&self) -> impl Iterator<Item = &Object> {
5659 self.objects.iter().filter_map(|o| o.object.as_object())
5660 }
5661}
5662
5663impl From<Vec<ReceivingObjectReadResult>> for ReceivingObjects {
5664 fn from(objects: Vec<ReceivingObjectReadResult>) -> Self {
5665 Self { objects }
5666 }
5667}
5668
5669impl Display for CertifiedTransaction {
5670 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
5671 let mut writer = String::new();
5672 writeln!(writer, "Transaction Hash: {:?}", self.digest())?;
5673 writeln!(
5674 writer,
5675 "Signed Authorities Bitmap : {:?}",
5676 self.auth_sig().signers_map
5677 )?;
5678 write!(writer, "{}", self.data().intent_message().value.kind())?;
5679 write!(f, "{}", writer)
5680 }
5681}
5682
5683#[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
5687pub enum TransactionKey {
5688 Digest(TransactionDigest),
5689 RandomnessRound(EpochId, RandomnessRound),
5690 AccumulatorSettlement(EpochId, u64 ),
5691 ConsensusCommitPrologue(EpochId, u64 , u32 ),
5692}
5693
5694impl TransactionKey {
5695 pub fn unwrap_digest(&self) -> &TransactionDigest {
5696 match self {
5697 TransactionKey::Digest(d) => d,
5698 _ => panic!("called unwrap_digest on a non-Digest TransactionKey: {self:?}"),
5699 }
5700 }
5701
5702 pub fn as_digest(&self) -> Option<&TransactionDigest> {
5703 match self {
5704 TransactionKey::Digest(d) => Some(d),
5705 _ => None,
5706 }
5707 }
5708}