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 for input in inputs {
1703 input.validity_check(config)?
1704 }
1705 if let Some(max_publish_commands) = config.max_publish_or_upgrade_per_ptb_as_option() {
1706 let publish_count = commands
1707 .iter()
1708 .filter(|c| matches!(c, Command::Publish(_, _) | Command::Upgrade(_, _, _, _)))
1709 .count() as u64;
1710 fp_ensure!(
1711 publish_count <= max_publish_commands,
1712 UserInputError::MaxPublishCountExceeded {
1713 max_publish_commands,
1714 publish_count,
1715 }
1716 );
1717 }
1718 for command in commands {
1719 command.validity_check(config)?;
1720 }
1721 if config.validate_ptb_argument_indices() {
1722 self.validate_argument_indices()?;
1723 }
1724
1725 if let Some(random_index) = inputs.iter().position(|obj| {
1728 matches!(
1729 obj,
1730 CallArg::Object(ObjectArg::SharedObject { id, .. }) if *id == SUI_RANDOMNESS_STATE_OBJECT_ID
1731 )
1732 }) {
1733 fp_ensure!(
1734 config.random_beacon(),
1735 UserInputError::Unsupported(
1736 "randomness is not enabled on this network".to_string(),
1737 )
1738 );
1739 let mut used_random_object = false;
1740 let random_index = random_index.try_into().unwrap();
1741 for command in commands {
1742 if !used_random_object {
1743 used_random_object = command.is_input_arg_used(random_index);
1744 } else {
1745 fp_ensure!(
1746 matches!(
1747 command,
1748 Command::TransferObjects(_, _) | Command::MergeCoins(_, _)
1749 ),
1750 UserInputError::PostRandomCommandRestrictions
1751 );
1752 }
1753 }
1754 }
1755
1756 Ok(())
1757 }
1758
1759 pub fn coin_reservation_obj_refs(&self) -> impl Iterator<Item = ObjectRef> + '_ {
1761 self.inputs.iter().filter_map(|arg| match arg {
1762 CallArg::Object(ObjectArg::ImmOrOwnedObject(obj_ref))
1763 if ParsedDigest::is_coin_reservation_digest(&obj_ref.2) =>
1764 {
1765 Some(*obj_ref)
1766 }
1767 _ => None,
1768 })
1769 }
1770
1771 pub fn shared_input_objects(&self) -> impl Iterator<Item = SharedInputObject> + '_ {
1772 self.inputs.iter().filter_map(|arg| match arg {
1773 CallArg::Pure(_)
1774 | CallArg::Object(ObjectArg::Receiving(_))
1775 | CallArg::Object(ObjectArg::ImmOrOwnedObject(_))
1776 | CallArg::FundsWithdrawal(_) => None,
1777 CallArg::Object(ObjectArg::SharedObject {
1778 id,
1779 initial_shared_version,
1780 mutability,
1781 }) => Some(SharedInputObject {
1782 id: *id,
1783 initial_shared_version: *initial_shared_version,
1784 mutability: *mutability,
1785 }),
1786 })
1787 }
1788
1789 fn move_calls(&self) -> Vec<(usize, &ObjectID, &str, &str)> {
1790 self.commands
1791 .iter()
1792 .enumerate()
1793 .filter_map(|(idx, command)| match command {
1794 Command::MoveCall(m) => {
1795 Some((idx, &m.package, m.module.as_str(), m.function.as_str()))
1796 }
1797 _ => None,
1798 })
1799 .collect()
1800 }
1801
1802 pub fn non_system_packages_to_be_published(&self) -> impl Iterator<Item = &Vec<Vec<u8>>> + '_ {
1803 self.commands
1804 .iter()
1805 .filter_map(|q| q.non_system_packages_to_be_published())
1806 }
1807}
1808
1809impl Display for Argument {
1810 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
1811 match self {
1812 Argument::GasCoin => write!(f, "GasCoin"),
1813 Argument::Input(i) => write!(f, "Input({i})"),
1814 Argument::Result(i) => write!(f, "Result({i})"),
1815 Argument::NestedResult(i, j) => write!(f, "NestedResult({i},{j})"),
1816 }
1817 }
1818}
1819
1820impl Display for ProgrammableMoveCall {
1821 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
1822 let ProgrammableMoveCall {
1823 package,
1824 module,
1825 function,
1826 type_arguments,
1827 arguments,
1828 } = self;
1829 write!(f, "{package}::{module}::{function}")?;
1830 if !type_arguments.is_empty() {
1831 write!(f, "<")?;
1832 write_sep(f, type_arguments, ",")?;
1833 write!(f, ">")?;
1834 }
1835 write!(f, "(")?;
1836 write_sep(f, arguments, ",")?;
1837 write!(f, ")")
1838 }
1839}
1840
1841impl Display for Command {
1842 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
1843 match self {
1844 Command::MoveCall(p) => {
1845 write!(f, "MoveCall({p})")
1846 }
1847 Command::MakeMoveVec(ty_opt, elems) => {
1848 write!(f, "MakeMoveVec(")?;
1849 if let Some(ty) = ty_opt {
1850 write!(f, "Some{ty}")?;
1851 } else {
1852 write!(f, "None")?;
1853 }
1854 write!(f, ",[")?;
1855 write_sep(f, elems, ",")?;
1856 write!(f, "])")
1857 }
1858 Command::TransferObjects(objs, addr) => {
1859 write!(f, "TransferObjects([")?;
1860 write_sep(f, objs, ",")?;
1861 write!(f, "],{addr})")
1862 }
1863 Command::SplitCoins(coin, amounts) => {
1864 write!(f, "SplitCoins({coin}")?;
1865 write_sep(f, amounts, ",")?;
1866 write!(f, ")")
1867 }
1868 Command::MergeCoins(target, coins) => {
1869 write!(f, "MergeCoins({target},")?;
1870 write_sep(f, coins, ",")?;
1871 write!(f, ")")
1872 }
1873 Command::Publish(_bytes, deps) => {
1874 write!(f, "Publish(_,")?;
1875 write_sep(f, deps, ",")?;
1876 write!(f, ")")
1877 }
1878 Command::Upgrade(_bytes, deps, current_package_id, ticket) => {
1879 write!(f, "Upgrade(_,")?;
1880 write_sep(f, deps, ",")?;
1881 write!(f, ", {current_package_id}")?;
1882 write!(f, ", {ticket}")?;
1883 write!(f, ")")
1884 }
1885 }
1886 }
1887}
1888
1889impl Display for ProgrammableTransaction {
1890 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
1891 let ProgrammableTransaction { inputs, commands } = self;
1892 writeln!(f, "Inputs: {inputs:?}")?;
1893 writeln!(f, "Commands: [")?;
1894 for c in commands {
1895 writeln!(f, " {c},")?;
1896 }
1897 writeln!(f, "]")
1898 }
1899}
1900
1901#[derive(Debug, PartialEq, Eq)]
1902pub struct SharedInputObject {
1903 pub id: ObjectID,
1904 pub initial_shared_version: SequenceNumber,
1905 pub mutability: SharedObjectMutability,
1906}
1907
1908impl SharedInputObject {
1909 pub const SUI_SYSTEM_OBJ: Self = Self {
1910 id: SUI_SYSTEM_STATE_OBJECT_ID,
1911 initial_shared_version: SUI_SYSTEM_STATE_OBJECT_SHARED_VERSION,
1912 mutability: SharedObjectMutability::Mutable,
1913 };
1914
1915 pub fn id(&self) -> ObjectID {
1916 self.id
1917 }
1918
1919 pub fn id_and_version(&self) -> (ObjectID, SequenceNumber) {
1920 (self.id, self.initial_shared_version)
1921 }
1922
1923 pub fn into_id_and_version(self) -> (ObjectID, SequenceNumber) {
1924 (self.id, self.initial_shared_version)
1925 }
1926
1927 pub fn is_accessed_exclusively(&self) -> bool {
1928 self.mutability.is_exclusive()
1929 }
1930
1931 pub fn may_mutate(&self) -> bool {
1933 match self.mutability {
1934 SharedObjectMutability::Immutable => false,
1935 SharedObjectMutability::Mutable | SharedObjectMutability::NonExclusiveWrite => true,
1936 }
1937 }
1938}
1939
1940impl TransactionKind {
1941 pub fn programmable(pt: ProgrammableTransaction) -> Self {
1944 TransactionKind::ProgrammableTransaction(pt)
1945 }
1946
1947 pub fn is_system_tx(&self) -> bool {
1948 match self {
1950 TransactionKind::ChangeEpoch(_)
1951 | TransactionKind::Genesis(_)
1952 | TransactionKind::ConsensusCommitPrologue(_)
1953 | TransactionKind::ConsensusCommitPrologueV2(_)
1954 | TransactionKind::ConsensusCommitPrologueV3(_)
1955 | TransactionKind::ConsensusCommitPrologueV4(_)
1956 | TransactionKind::AuthenticatorStateUpdate(_)
1957 | TransactionKind::RandomnessStateUpdate(_)
1958 | TransactionKind::EndOfEpochTransaction(_)
1959 | TransactionKind::ProgrammableSystemTransaction(_) => true,
1960 TransactionKind::ProgrammableTransaction(_) => false,
1961 }
1962 }
1963
1964 pub fn is_end_of_epoch_tx(&self) -> bool {
1965 matches!(
1966 self,
1967 TransactionKind::EndOfEpochTransaction(_) | TransactionKind::ChangeEpoch(_)
1968 )
1969 }
1970
1971 pub fn mutates_implicitly_read_system_object(&self) -> bool {
1972 self.shared_input_objects()
1973 .any(|obj| obj.may_mutate() && obj.id.is_implicitly_read_system_object())
1974 }
1975
1976 pub fn is_accumulator_barrier_settle_tx(&self) -> bool {
1977 matches!(self, TransactionKind::ProgrammableSystemTransaction(_))
1978 && self.shared_input_objects().any(|obj| {
1979 obj.id == SUI_ACCUMULATOR_ROOT_OBJECT_ID
1980 && obj.mutability == SharedObjectMutability::Mutable
1981 })
1982 }
1983
1984 pub fn accumulator_barrier_settlement_key(&self) -> Option<TransactionKey> {
1988 let TransactionKind::ProgrammableSystemTransaction(pt) = self else {
1989 return None;
1990 };
1991 let has_mutable_acc_root = pt.inputs.iter().any(|input| {
1992 matches!(
1993 input,
1994 CallArg::Object(ObjectArg::SharedObject {
1995 id,
1996 mutability: SharedObjectMutability::Mutable,
1997 ..
1998 }) if *id == SUI_ACCUMULATOR_ROOT_OBJECT_ID
1999 )
2000 });
2001 if !has_mutable_acc_root {
2002 return None;
2003 }
2004 let epoch = pt.inputs.get(1).and_then(|arg| match arg {
2007 CallArg::Pure(bytes) => bcs::from_bytes::<u64>(bytes).ok(),
2008 _ => None,
2009 })?;
2010 let checkpoint_height = pt.inputs.get(2).and_then(|arg| match arg {
2011 CallArg::Pure(bytes) => bcs::from_bytes::<u64>(bytes).ok(),
2012 _ => None,
2013 })?;
2014 Some(TransactionKey::AccumulatorSettlement(
2015 epoch,
2016 checkpoint_height,
2017 ))
2018 }
2019
2020 pub fn get_advance_epoch_tx_gas_summary(&self) -> Option<(u64, u64)> {
2024 let e = match self {
2025 Self::ChangeEpoch(e) => e,
2026 Self::EndOfEpochTransaction(txns) => {
2027 if let EndOfEpochTransactionKind::ChangeEpoch(e) =
2028 txns.last().expect("at least one end-of-epoch txn required")
2029 {
2030 e
2031 } else {
2032 panic!("final end-of-epoch txn must be ChangeEpoch")
2033 }
2034 }
2035 _ => return None,
2036 };
2037
2038 Some((e.computation_charge + e.storage_charge, e.storage_rebate))
2039 }
2040
2041 pub fn shared_input_objects(&self) -> impl Iterator<Item = SharedInputObject> + '_ {
2044 match &self {
2045 Self::ChangeEpoch(_) => {
2046 Either::Left(Either::Left(iter::once(SharedInputObject::SUI_SYSTEM_OBJ)))
2047 }
2048
2049 Self::ConsensusCommitPrologue(_)
2050 | Self::ConsensusCommitPrologueV2(_)
2051 | Self::ConsensusCommitPrologueV3(_)
2052 | Self::ConsensusCommitPrologueV4(_) => {
2053 Either::Left(Either::Left(iter::once(SharedInputObject {
2054 id: SUI_CLOCK_OBJECT_ID,
2055 initial_shared_version: SUI_CLOCK_OBJECT_SHARED_VERSION,
2056 mutability: SharedObjectMutability::Mutable,
2057 })))
2058 }
2059 Self::AuthenticatorStateUpdate(update) => {
2060 Either::Left(Either::Left(iter::once(SharedInputObject {
2061 id: SUI_AUTHENTICATOR_STATE_OBJECT_ID,
2062 initial_shared_version: update.authenticator_obj_initial_shared_version,
2063 mutability: SharedObjectMutability::Mutable,
2064 })))
2065 }
2066 Self::RandomnessStateUpdate(update) => {
2067 Either::Left(Either::Left(iter::once(SharedInputObject {
2068 id: SUI_RANDOMNESS_STATE_OBJECT_ID,
2069 initial_shared_version: update.randomness_obj_initial_shared_version,
2070 mutability: SharedObjectMutability::Mutable,
2071 })))
2072 }
2073 Self::EndOfEpochTransaction(txns) => Either::Left(Either::Right(
2074 txns.iter().flat_map(|txn| txn.shared_input_objects()),
2075 )),
2076 Self::ProgrammableTransaction(pt) | Self::ProgrammableSystemTransaction(pt) => {
2077 Either::Right(Either::Left(pt.shared_input_objects()))
2078 }
2079 Self::Genesis(_) => Either::Right(Either::Right(iter::empty())),
2080 }
2081 }
2082
2083 fn move_calls(&self) -> Vec<(usize, &ObjectID, &str, &str)> {
2084 match &self {
2085 Self::ProgrammableTransaction(pt) => pt.move_calls(),
2086 _ => vec![],
2087 }
2088 }
2089
2090 pub fn receiving_objects(&self) -> Vec<ObjectRef> {
2091 match &self {
2092 TransactionKind::ChangeEpoch(_)
2093 | TransactionKind::Genesis(_)
2094 | TransactionKind::ConsensusCommitPrologue(_)
2095 | TransactionKind::ConsensusCommitPrologueV2(_)
2096 | TransactionKind::ConsensusCommitPrologueV3(_)
2097 | TransactionKind::ConsensusCommitPrologueV4(_)
2098 | TransactionKind::AuthenticatorStateUpdate(_)
2099 | TransactionKind::RandomnessStateUpdate(_)
2100 | TransactionKind::EndOfEpochTransaction(_)
2101 | TransactionKind::ProgrammableSystemTransaction(_) => vec![],
2102 TransactionKind::ProgrammableTransaction(pt) => pt.receiving_objects(),
2103 }
2104 }
2105
2106 pub fn input_objects(&self) -> UserInputResult<Vec<InputObjectKind>> {
2111 let input_objects = match &self {
2112 Self::ChangeEpoch(_) => {
2113 vec![InputObjectKind::SharedMoveObject {
2114 id: SUI_SYSTEM_STATE_OBJECT_ID,
2115 initial_shared_version: SUI_SYSTEM_STATE_OBJECT_SHARED_VERSION,
2116 mutability: SharedObjectMutability::Mutable,
2117 }]
2118 }
2119 Self::Genesis(_) => {
2120 vec![]
2121 }
2122 Self::ConsensusCommitPrologue(_)
2123 | Self::ConsensusCommitPrologueV2(_)
2124 | Self::ConsensusCommitPrologueV3(_)
2125 | Self::ConsensusCommitPrologueV4(_) => {
2126 vec![InputObjectKind::SharedMoveObject {
2127 id: SUI_CLOCK_OBJECT_ID,
2128 initial_shared_version: SUI_CLOCK_OBJECT_SHARED_VERSION,
2129 mutability: SharedObjectMutability::Mutable,
2130 }]
2131 }
2132 Self::AuthenticatorStateUpdate(update) => {
2133 vec![InputObjectKind::SharedMoveObject {
2134 id: SUI_AUTHENTICATOR_STATE_OBJECT_ID,
2135 initial_shared_version: update.authenticator_obj_initial_shared_version(),
2136 mutability: SharedObjectMutability::Mutable,
2137 }]
2138 }
2139 Self::RandomnessStateUpdate(update) => {
2140 vec![InputObjectKind::SharedMoveObject {
2141 id: SUI_RANDOMNESS_STATE_OBJECT_ID,
2142 initial_shared_version: update.randomness_obj_initial_shared_version(),
2143 mutability: SharedObjectMutability::Mutable,
2144 }]
2145 }
2146 Self::EndOfEpochTransaction(txns) => {
2147 let before_dedup: Vec<_> =
2150 txns.iter().flat_map(|txn| txn.input_objects()).collect();
2151 let mut has_seen = HashSet::new();
2152 let mut after_dedup = vec![];
2153 for obj in before_dedup {
2154 if has_seen.insert(obj) {
2155 after_dedup.push(obj);
2156 }
2157 }
2158 after_dedup
2159 }
2160 Self::ProgrammableTransaction(p) | Self::ProgrammableSystemTransaction(p) => {
2161 return p.input_objects();
2162 }
2163 };
2164 let mut used = HashSet::new();
2172 if !input_objects.iter().all(|o| used.insert(o.object_id())) {
2173 return Err(UserInputError::DuplicateObjectRefInput);
2174 }
2175 Ok(input_objects)
2176 }
2177
2178 pub fn get_funds_withdrawals<'a>(
2179 &'a self,
2180 ) -> impl Iterator<Item = &'a FundsWithdrawalArg> + 'a {
2181 let TransactionKind::ProgrammableTransaction(pt) = &self else {
2182 return Either::Left(iter::empty());
2183 };
2184 Either::Right(pt.inputs.iter().filter_map(|input| {
2185 if let CallArg::FundsWithdrawal(withdraw) = input {
2186 Some(withdraw)
2187 } else {
2188 None
2189 }
2190 }))
2191 }
2192
2193 pub fn get_coin_reservation_obj_refs(&self) -> impl Iterator<Item = ObjectRef> + '_ {
2194 let TransactionKind::ProgrammableTransaction(pt) = &self else {
2195 return Either::Left(iter::empty());
2196 };
2197 Either::Right(pt.coin_reservation_obj_refs())
2198 }
2199
2200 pub fn has_coin_reservations(&self) -> bool {
2201 self.get_coin_reservation_obj_refs().next().is_some()
2202 }
2203
2204 pub fn validity_check(&self, config: &ProtocolConfig) -> UserInputResult {
2205 match self {
2206 TransactionKind::ProgrammableTransaction(p) => p.validity_check(config)?,
2207 TransactionKind::ChangeEpoch(_)
2210 | TransactionKind::Genesis(_)
2211 | TransactionKind::ConsensusCommitPrologue(_) => (),
2212 TransactionKind::ConsensusCommitPrologueV2(_) => {
2213 if !config.include_consensus_digest_in_prologue() {
2214 return Err(UserInputError::Unsupported(
2215 "ConsensusCommitPrologueV2 is not supported".to_string(),
2216 ));
2217 }
2218 }
2219 TransactionKind::ConsensusCommitPrologueV3(_) => {
2220 if !config.record_consensus_determined_version_assignments_in_prologue() {
2221 return Err(UserInputError::Unsupported(
2222 "ConsensusCommitPrologueV3 is not supported".to_string(),
2223 ));
2224 }
2225 }
2226 TransactionKind::ConsensusCommitPrologueV4(_) => {
2227 if !config.record_additional_state_digest_in_prologue() {
2228 return Err(UserInputError::Unsupported(
2229 "ConsensusCommitPrologueV4 is not supported".to_string(),
2230 ));
2231 }
2232 }
2233 TransactionKind::EndOfEpochTransaction(txns) => {
2234 if !config.end_of_epoch_transaction_supported() {
2235 return Err(UserInputError::Unsupported(
2236 "EndOfEpochTransaction is not supported".to_string(),
2237 ));
2238 }
2239
2240 for tx in txns {
2241 tx.validity_check(config)?;
2242 }
2243 }
2244
2245 TransactionKind::AuthenticatorStateUpdate(_) => {
2246 if !config.enable_jwk_consensus_updates() {
2247 return Err(UserInputError::Unsupported(
2248 "authenticator state updates not enabled".to_string(),
2249 ));
2250 }
2251 }
2252 TransactionKind::RandomnessStateUpdate(_) => {
2253 if !config.random_beacon() {
2254 return Err(UserInputError::Unsupported(
2255 "randomness state updates not enabled".to_string(),
2256 ));
2257 }
2258 }
2259 TransactionKind::ProgrammableSystemTransaction(_) => {
2260 if !config.enable_accumulators() {
2261 return Err(UserInputError::Unsupported(
2262 "accumulators not enabled".to_string(),
2263 ));
2264 }
2265 }
2266 };
2267 Ok(())
2268 }
2269
2270 pub fn num_commands(&self) -> usize {
2272 match self {
2273 TransactionKind::ProgrammableTransaction(pt) => pt.commands.len(),
2274 _ => 0,
2275 }
2276 }
2277
2278 pub fn iter_commands(&self) -> impl Iterator<Item = &Command> {
2279 match self {
2280 TransactionKind::ProgrammableTransaction(pt) => pt.commands.iter(),
2281 _ => [].iter(),
2282 }
2283 }
2284
2285 pub fn tx_count(&self) -> usize {
2287 match self {
2288 TransactionKind::ProgrammableTransaction(pt) => pt.commands.len(),
2289 _ => 1,
2290 }
2291 }
2292
2293 pub fn name(&self) -> &'static str {
2294 match self {
2295 Self::ChangeEpoch(_) => "ChangeEpoch",
2296 Self::Genesis(_) => "Genesis",
2297 Self::ConsensusCommitPrologue(_) => "ConsensusCommitPrologue",
2298 Self::ConsensusCommitPrologueV2(_) => "ConsensusCommitPrologueV2",
2299 Self::ConsensusCommitPrologueV3(_) => "ConsensusCommitPrologueV3",
2300 Self::ConsensusCommitPrologueV4(_) => "ConsensusCommitPrologueV4",
2301 Self::ProgrammableTransaction(_) => "ProgrammableTransaction",
2302 Self::ProgrammableSystemTransaction(_) => "ProgrammableSystemTransaction",
2303 Self::AuthenticatorStateUpdate(_) => "AuthenticatorStateUpdate",
2304 Self::RandomnessStateUpdate(_) => "RandomnessStateUpdate",
2305 Self::EndOfEpochTransaction(_) => "EndOfEpochTransaction",
2306 }
2307 }
2308}
2309
2310impl Display for TransactionKind {
2311 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
2312 let mut writer = String::new();
2313 match &self {
2314 Self::ChangeEpoch(e) => {
2315 writeln!(writer, "Transaction Kind : Epoch Change")?;
2316 writeln!(writer, "New epoch ID : {}", e.epoch)?;
2317 writeln!(writer, "Storage gas reward : {}", e.storage_charge)?;
2318 writeln!(writer, "Computation gas reward : {}", e.computation_charge)?;
2319 writeln!(writer, "Storage rebate : {}", e.storage_rebate)?;
2320 writeln!(writer, "Timestamp : {}", e.epoch_start_timestamp_ms)?;
2321 }
2322 Self::Genesis(_) => {
2323 writeln!(writer, "Transaction Kind : Genesis")?;
2324 }
2325 Self::ConsensusCommitPrologue(p) => {
2326 writeln!(writer, "Transaction Kind : Consensus Commit Prologue")?;
2327 writeln!(writer, "Timestamp : {}", p.commit_timestamp_ms)?;
2328 }
2329 Self::ConsensusCommitPrologueV2(p) => {
2330 writeln!(writer, "Transaction Kind : Consensus Commit Prologue V2")?;
2331 writeln!(writer, "Timestamp : {}", p.commit_timestamp_ms)?;
2332 writeln!(writer, "Consensus Digest: {}", p.consensus_commit_digest)?;
2333 }
2334 Self::ConsensusCommitPrologueV3(p) => {
2335 writeln!(writer, "Transaction Kind : Consensus Commit Prologue V3")?;
2336 writeln!(writer, "Timestamp : {}", p.commit_timestamp_ms)?;
2337 writeln!(writer, "Consensus Digest: {}", p.consensus_commit_digest)?;
2338 writeln!(
2339 writer,
2340 "Consensus determined version assignment: {:?}",
2341 p.consensus_determined_version_assignments
2342 )?;
2343 }
2344 Self::ConsensusCommitPrologueV4(p) => {
2345 writeln!(writer, "Transaction Kind : Consensus Commit Prologue V4")?;
2346 writeln!(writer, "Timestamp : {}", p.commit_timestamp_ms)?;
2347 writeln!(writer, "Consensus Digest: {}", p.consensus_commit_digest)?;
2348 writeln!(
2349 writer,
2350 "Consensus determined version assignment: {:?}",
2351 p.consensus_determined_version_assignments
2352 )?;
2353 writeln!(
2354 writer,
2355 "Additional State Digest: {}",
2356 p.additional_state_digest
2357 )?;
2358 }
2359 Self::ProgrammableTransaction(p) => {
2360 writeln!(writer, "Transaction Kind : Programmable")?;
2361 write!(writer, "{p}")?;
2362 }
2363 Self::ProgrammableSystemTransaction(p) => {
2364 writeln!(writer, "Transaction Kind : Programmable System")?;
2365 write!(writer, "{p}")?;
2366 }
2367 Self::AuthenticatorStateUpdate(_) => {
2368 writeln!(writer, "Transaction Kind : Authenticator State Update")?;
2369 }
2370 Self::RandomnessStateUpdate(_) => {
2371 writeln!(writer, "Transaction Kind : Randomness State Update")?;
2372 }
2373 Self::EndOfEpochTransaction(_) => {
2374 writeln!(writer, "Transaction Kind : End of Epoch Transaction")?;
2375 }
2376 }
2377 write!(f, "{}", writer)
2378 }
2379}
2380
2381#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)]
2382pub struct GasData {
2383 pub payment: Vec<ObjectRef>,
2384 pub owner: SuiAddress,
2385 pub price: u64,
2386 pub budget: u64,
2387}
2388
2389impl GasData {
2390 pub fn is_unmetered(&self) -> bool {
2391 self.payment.len() == 1
2392 && self.payment[0].0 == ObjectID::ZERO
2393 && self.payment[0].1 == SequenceNumber::default()
2394 && self.payment[0].2 == ObjectDigest::MIN
2395 }
2396}
2397
2398pub fn is_gas_paid_from_address_balance(
2399 gas_data: &GasData,
2400 transaction_kind: &TransactionKind,
2401) -> bool {
2402 gas_data.payment.is_empty()
2403 && matches!(
2404 transaction_kind,
2405 TransactionKind::ProgrammableTransaction(_)
2406 )
2407}
2408
2409pub fn is_gasless_transaction(gas_data: &GasData, transaction_kind: &TransactionKind) -> bool {
2410 is_gas_paid_from_address_balance(gas_data, transaction_kind) && gas_data.price == 0
2411}
2412
2413#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)]
2414pub enum TransactionExpiration {
2415 None,
2417 Epoch(EpochId),
2420 ValidDuring {
2430 min_epoch: Option<EpochId>,
2432 max_epoch: Option<EpochId>,
2434 min_timestamp: Option<u64>,
2436 max_timestamp: Option<u64>,
2438 chain: ChainIdentifier,
2440 nonce: u32,
2442 },
2443 Validity {
2446 min_epoch: Option<EpochId>,
2448 max_epoch: Option<EpochId>,
2450 min_timestamp: Option<u64>,
2452 max_timestamp: Option<u64>,
2454 chain: ChainIdentifier,
2456 nonce: u32,
2458 allowed_proposers: Option<AllowedProposers>,
2460 },
2461}
2462
2463#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)]
2466pub struct AllowedProposers {
2467 pub epoch: EpochId,
2471 #[serde(with = "nonempty_as_vec")]
2476 pub proposers: NonEmpty<u32>,
2477}
2478
2479pub const MAX_UNPAID_ALLOWED_PROPOSERS: u64 = 3;
2485
2486impl TransactionExpiration {
2487 pub fn is_replay_protected(&self) -> bool {
2492 matches!(
2493 self,
2494 TransactionExpiration::ValidDuring {
2495 min_epoch: Some(min_epoch),
2496 max_epoch: Some(max_epoch),
2497 ..
2498 }
2499 | TransactionExpiration::Validity {
2500 min_epoch: Some(min_epoch),
2501 max_epoch: Some(max_epoch),
2502 ..
2503 } if *max_epoch == *min_epoch || *max_epoch == min_epoch.saturating_add(1)
2504 )
2505 }
2506
2507 pub fn allowed_proposers(&self, epoch: EpochId) -> Option<&AllowedProposers> {
2515 match self {
2516 TransactionExpiration::Validity {
2517 allowed_proposers: Some(allowed),
2518 ..
2519 } if allowed.epoch == epoch => Some(allowed),
2520 _ => None,
2521 }
2522 }
2523
2524 pub fn restricts_proposers(&self, epoch: EpochId) -> bool {
2527 self.allowed_proposers(epoch).is_some()
2528 }
2529
2530 pub fn is_allowed_proposer(&self, proposer: u32, epoch: EpochId) -> bool {
2533 self.allowed_proposers(epoch).is_none_or(|allowed| {
2534 allowed
2538 .proposers
2539 .iter()
2540 .find(|p| **p >= proposer)
2541 .is_some_and(|p| *p == proposer)
2542 })
2543 }
2544}
2545
2546#[enum_dispatch(TransactionDataAPI)]
2547#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)]
2548pub enum TransactionData {
2549 V1(TransactionDataV1),
2550 }
2553
2554#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)]
2555pub struct TransactionDataV1 {
2556 pub kind: TransactionKind,
2557 pub sender: SuiAddress,
2558 pub gas_data: GasData,
2559 pub expiration: TransactionExpiration,
2560}
2561
2562impl TransactionData {
2563 pub fn as_v1(&self) -> &TransactionDataV1 {
2564 match self {
2565 TransactionData::V1(v1) => v1,
2566 }
2567 }
2568
2569 pub fn has_replay_protection(
2574 &self,
2575 input_objects: impl IntoIterator<Item = impl std::borrow::Borrow<ObjectReadResult>>,
2576 ) -> bool {
2577 self.expiration().is_replay_protected()
2578 || !self.gas_data().payment.is_empty()
2579 || input_objects
2580 .into_iter()
2581 .any(|object| object.borrow().is_replay_protected_input())
2582 }
2583 fn new_system_transaction(kind: TransactionKind) -> Self {
2584 assert!(kind.is_system_tx());
2586 let sender = SuiAddress::default();
2587 TransactionData::V1(TransactionDataV1 {
2588 kind,
2589 sender,
2590 gas_data: GasData {
2591 price: GAS_PRICE_FOR_SYSTEM_TX,
2592 owner: sender,
2593 payment: vec![(ObjectID::ZERO, SequenceNumber::default(), ObjectDigest::MIN)],
2594 budget: 0,
2595 },
2596 expiration: TransactionExpiration::None,
2597 })
2598 }
2599
2600 pub fn new(
2601 kind: TransactionKind,
2602 sender: SuiAddress,
2603 gas_payment: ObjectRef,
2604 gas_budget: u64,
2605 gas_price: u64,
2606 ) -> Self {
2607 TransactionData::V1(TransactionDataV1 {
2608 kind,
2609 sender,
2610 gas_data: GasData {
2611 price: gas_price,
2612 owner: sender,
2613 payment: vec![gas_payment],
2614 budget: gas_budget,
2615 },
2616 expiration: TransactionExpiration::None,
2617 })
2618 }
2619
2620 pub fn new_with_gas_coins(
2621 kind: TransactionKind,
2622 sender: SuiAddress,
2623 gas_payment: Vec<ObjectRef>,
2624 gas_budget: u64,
2625 gas_price: u64,
2626 ) -> Self {
2627 Self::new_with_gas_coins_allow_sponsor(
2628 kind,
2629 sender,
2630 gas_payment,
2631 gas_budget,
2632 gas_price,
2633 sender,
2634 )
2635 }
2636
2637 pub fn new_with_gas_coins_allow_sponsor(
2638 kind: TransactionKind,
2639 sender: SuiAddress,
2640 gas_payment: Vec<ObjectRef>,
2641 gas_budget: u64,
2642 gas_price: u64,
2643 gas_sponsor: SuiAddress,
2644 ) -> Self {
2645 TransactionData::V1(TransactionDataV1 {
2646 kind,
2647 sender,
2648 gas_data: GasData {
2649 price: gas_price,
2650 owner: gas_sponsor,
2651 payment: gas_payment,
2652 budget: gas_budget,
2653 },
2654 expiration: TransactionExpiration::None,
2655 })
2656 }
2657
2658 pub fn new_with_gas_data(kind: TransactionKind, sender: SuiAddress, gas_data: GasData) -> Self {
2659 TransactionData::V1(TransactionDataV1 {
2660 kind,
2661 sender,
2662 gas_data,
2663 expiration: TransactionExpiration::None,
2664 })
2665 }
2666
2667 pub fn new_with_gas_data_and_expiration(
2668 kind: TransactionKind,
2669 sender: SuiAddress,
2670 gas_data: GasData,
2671 expiration: TransactionExpiration,
2672 ) -> Self {
2673 TransactionData::V1(TransactionDataV1 {
2674 kind,
2675 sender,
2676 gas_data,
2677 expiration,
2678 })
2679 }
2680
2681 pub fn new_move_call(
2682 sender: SuiAddress,
2683 package: ObjectID,
2684 module: Identifier,
2685 function: Identifier,
2686 type_arguments: Vec<TypeTag>,
2687 gas_payment: ObjectRef,
2688 arguments: Vec<CallArg>,
2689 gas_budget: u64,
2690 gas_price: u64,
2691 ) -> anyhow::Result<Self> {
2692 Self::new_move_call_with_gas_coins(
2693 sender,
2694 package,
2695 module,
2696 function,
2697 type_arguments,
2698 vec![gas_payment],
2699 arguments,
2700 gas_budget,
2701 gas_price,
2702 )
2703 }
2704
2705 pub fn new_move_call_with_gas_coins(
2706 sender: SuiAddress,
2707 package: ObjectID,
2708 module: Identifier,
2709 function: Identifier,
2710 type_arguments: Vec<TypeTag>,
2711 gas_payment: Vec<ObjectRef>,
2712 arguments: Vec<CallArg>,
2713 gas_budget: u64,
2714 gas_price: u64,
2715 ) -> anyhow::Result<Self> {
2716 let pt = {
2717 let mut builder = ProgrammableTransactionBuilder::new();
2718 builder.move_call(package, module, function, type_arguments, arguments)?;
2719 builder.finish()
2720 };
2721 Ok(Self::new_programmable(
2722 sender,
2723 gas_payment,
2724 pt,
2725 gas_budget,
2726 gas_price,
2727 ))
2728 }
2729
2730 pub fn new_transfer(
2731 recipient: SuiAddress,
2732 full_object_ref: FullObjectRef,
2733 sender: SuiAddress,
2734 gas_payment: ObjectRef,
2735 gas_budget: u64,
2736 gas_price: u64,
2737 ) -> Self {
2738 let pt = {
2739 let mut builder = ProgrammableTransactionBuilder::new();
2740 builder.transfer_object(recipient, full_object_ref).unwrap();
2741 builder.finish()
2742 };
2743 Self::new_programmable(sender, vec![gas_payment], pt, gas_budget, gas_price)
2744 }
2745
2746 pub fn new_transfer_sui(
2747 recipient: SuiAddress,
2748 sender: SuiAddress,
2749 amount: Option<u64>,
2750 gas_payment: ObjectRef,
2751 gas_budget: u64,
2752 gas_price: u64,
2753 ) -> Self {
2754 Self::new_transfer_sui_allow_sponsor(
2755 recipient,
2756 sender,
2757 amount,
2758 gas_payment,
2759 gas_budget,
2760 gas_price,
2761 sender,
2762 )
2763 }
2764
2765 pub fn new_transfer_sui_allow_sponsor(
2766 recipient: SuiAddress,
2767 sender: SuiAddress,
2768 amount: Option<u64>,
2769 gas_payment: ObjectRef,
2770 gas_budget: u64,
2771 gas_price: u64,
2772 gas_sponsor: SuiAddress,
2773 ) -> Self {
2774 let pt = {
2775 let mut builder = ProgrammableTransactionBuilder::new();
2776 builder.transfer_sui(recipient, amount);
2777 builder.finish()
2778 };
2779 Self::new_programmable_allow_sponsor(
2780 sender,
2781 vec![gas_payment],
2782 pt,
2783 gas_budget,
2784 gas_price,
2785 gas_sponsor,
2786 )
2787 }
2788
2789 pub fn new_pay(
2790 sender: SuiAddress,
2791 coins: Vec<ObjectRef>,
2792 recipients: Vec<SuiAddress>,
2793 amounts: Vec<u64>,
2794 gas_payment: ObjectRef,
2795 gas_budget: u64,
2796 gas_price: u64,
2797 ) -> anyhow::Result<Self> {
2798 let pt = {
2799 let mut builder = ProgrammableTransactionBuilder::new();
2800 builder.pay(coins, recipients, amounts)?;
2801 builder.finish()
2802 };
2803 Ok(Self::new_programmable(
2804 sender,
2805 vec![gas_payment],
2806 pt,
2807 gas_budget,
2808 gas_price,
2809 ))
2810 }
2811
2812 pub fn new_pay_sui(
2813 sender: SuiAddress,
2814 mut coins: Vec<ObjectRef>,
2815 recipients: Vec<SuiAddress>,
2816 amounts: Vec<u64>,
2817 gas_payment: ObjectRef,
2818 gas_budget: u64,
2819 gas_price: u64,
2820 ) -> anyhow::Result<Self> {
2821 coins.insert(0, gas_payment);
2822 let pt = {
2823 let mut builder = ProgrammableTransactionBuilder::new();
2824 builder.pay_sui(recipients, amounts)?;
2825 builder.finish()
2826 };
2827 Ok(Self::new_programmable(
2828 sender, coins, pt, gas_budget, gas_price,
2829 ))
2830 }
2831
2832 pub fn new_pay_all_sui(
2833 sender: SuiAddress,
2834 mut coins: Vec<ObjectRef>,
2835 recipient: SuiAddress,
2836 gas_payment: ObjectRef,
2837 gas_budget: u64,
2838 gas_price: u64,
2839 ) -> Self {
2840 coins.insert(0, gas_payment);
2841 let pt = {
2842 let mut builder = ProgrammableTransactionBuilder::new();
2843 builder.pay_all_sui(recipient);
2844 builder.finish()
2845 };
2846 Self::new_programmable(sender, coins, pt, gas_budget, gas_price)
2847 }
2848
2849 pub fn new_split_coin(
2850 sender: SuiAddress,
2851 coin: ObjectRef,
2852 amounts: Vec<u64>,
2853 gas_payment: ObjectRef,
2854 gas_budget: u64,
2855 gas_price: u64,
2856 ) -> Self {
2857 let pt = {
2858 let mut builder = ProgrammableTransactionBuilder::new();
2859 builder.split_coin(sender, coin, amounts);
2860 builder.finish()
2861 };
2862 Self::new_programmable(sender, vec![gas_payment], pt, gas_budget, gas_price)
2863 }
2864
2865 pub fn new_module(
2866 sender: SuiAddress,
2867 gas_payment: ObjectRef,
2868 modules: Vec<Vec<u8>>,
2869 dep_ids: Vec<ObjectID>,
2870 gas_budget: u64,
2871 gas_price: u64,
2872 ) -> Self {
2873 let pt = {
2874 let mut builder = ProgrammableTransactionBuilder::new();
2875 let upgrade_cap = builder.publish_upgradeable(modules, dep_ids);
2876 builder.transfer_arg(sender, upgrade_cap);
2877 builder.finish()
2878 };
2879 Self::new_programmable(sender, vec![gas_payment], pt, gas_budget, gas_price)
2880 }
2881
2882 pub fn new_upgrade(
2883 sender: SuiAddress,
2884 gas_payment: ObjectRef,
2885 package_id: ObjectID,
2886 modules: Vec<Vec<u8>>,
2887 dep_ids: Vec<ObjectID>,
2888 (upgrade_capability, capability_owner): (ObjectRef, Owner),
2889 upgrade_policy: u8,
2890 digest: Vec<u8>,
2891 gas_budget: u64,
2892 gas_price: u64,
2893 ) -> anyhow::Result<Self> {
2894 let pt = {
2895 let mut builder = ProgrammableTransactionBuilder::new();
2896 let capability_arg = match capability_owner {
2897 Owner::AddressOwner(_) => ObjectArg::ImmOrOwnedObject(upgrade_capability),
2898 Owner::Shared {
2899 initial_shared_version,
2900 }
2901 | Owner::ConsensusAddressOwner {
2902 start_version: initial_shared_version,
2903 ..
2904 }
2905 | Owner::Party {
2906 start_version: initial_shared_version,
2907 ..
2908 } => ObjectArg::SharedObject {
2909 id: upgrade_capability.0,
2910 initial_shared_version,
2911 mutability: SharedObjectMutability::Mutable,
2912 },
2913 Owner::Immutable => {
2914 return Err(anyhow::anyhow!(
2915 "Upgrade capability is stored immutably and cannot be used for upgrades"
2916 ));
2917 }
2918 Owner::ObjectOwner(_) => {
2921 return Err(anyhow::anyhow!("Upgrade capability controlled by object"));
2922 }
2923 };
2924 builder.obj(capability_arg).unwrap();
2925 let upgrade_arg = builder.pure(upgrade_policy).unwrap();
2926 let digest_arg = builder.pure(digest).unwrap();
2927 let upgrade_ticket = builder.programmable_move_call(
2928 SUI_FRAMEWORK_PACKAGE_ID,
2929 ident_str!("package").to_owned(),
2930 ident_str!("authorize_upgrade").to_owned(),
2931 vec![],
2932 vec![Argument::Input(0), upgrade_arg, digest_arg],
2933 );
2934 let upgrade_receipt = builder.upgrade(package_id, upgrade_ticket, dep_ids, modules);
2935
2936 builder.programmable_move_call(
2937 SUI_FRAMEWORK_PACKAGE_ID,
2938 ident_str!("package").to_owned(),
2939 ident_str!("commit_upgrade").to_owned(),
2940 vec![],
2941 vec![Argument::Input(0), upgrade_receipt],
2942 );
2943
2944 builder.finish()
2945 };
2946 Ok(Self::new_programmable(
2947 sender,
2948 vec![gas_payment],
2949 pt,
2950 gas_budget,
2951 gas_price,
2952 ))
2953 }
2954
2955 pub fn new_programmable(
2956 sender: SuiAddress,
2957 gas_payment: Vec<ObjectRef>,
2958 pt: ProgrammableTransaction,
2959 gas_budget: u64,
2960 gas_price: u64,
2961 ) -> Self {
2962 Self::new_programmable_allow_sponsor(sender, gas_payment, pt, gas_budget, gas_price, sender)
2963 }
2964
2965 pub fn new_programmable_allow_sponsor(
2966 sender: SuiAddress,
2967 gas_payment: Vec<ObjectRef>,
2968 pt: ProgrammableTransaction,
2969 gas_budget: u64,
2970 gas_price: u64,
2971 sponsor: SuiAddress,
2972 ) -> Self {
2973 let kind = TransactionKind::ProgrammableTransaction(pt);
2974 Self::new_with_gas_coins_allow_sponsor(
2975 kind,
2976 sender,
2977 gas_payment,
2978 gas_budget,
2979 gas_price,
2980 sponsor,
2981 )
2982 }
2983
2984 pub fn new_programmable_with_address_balance_gas(
2985 sender: SuiAddress,
2986 pt: ProgrammableTransaction,
2987 gas_budget: u64,
2988 gas_price: u64,
2989 chain_identifier: ChainIdentifier,
2990 current_epoch: EpochId,
2991 nonce: u32,
2992 ) -> Self {
2993 TransactionData::V1(TransactionDataV1 {
2994 kind: TransactionKind::ProgrammableTransaction(pt),
2995 sender,
2996 gas_data: GasData {
2997 payment: vec![],
2998 owner: sender,
2999 price: gas_price,
3000 budget: gas_budget,
3001 },
3002 expiration: TransactionExpiration::ValidDuring {
3003 min_epoch: Some(current_epoch),
3004 max_epoch: Some(current_epoch + 1),
3005 min_timestamp: None,
3006 max_timestamp: None,
3007 chain: chain_identifier,
3008 nonce,
3009 },
3010 })
3011 }
3012
3013 pub fn message_version(&self) -> u64 {
3014 match self {
3015 TransactionData::V1(_) => 1,
3016 }
3017 }
3018
3019 pub fn execution_parts(&self) -> (TransactionKind, SuiAddress, GasData) {
3020 (self.kind().clone(), self.sender(), self.gas_data().clone())
3021 }
3022
3023 pub fn uses_randomness(&self) -> bool {
3024 self.kind()
3025 .shared_input_objects()
3026 .any(|obj| obj.id() == SUI_RANDOMNESS_STATE_OBJECT_ID)
3027 }
3028
3029 pub fn digest(&self) -> TransactionDigest {
3030 TransactionDigest::new(default_hash(self))
3031 }
3032}
3033
3034#[enum_dispatch]
3035pub trait TransactionDataAPI {
3036 fn sender(&self) -> SuiAddress;
3037
3038 fn kind(&self) -> &TransactionKind;
3041
3042 fn kind_mut(&mut self) -> &mut TransactionKind;
3044
3045 fn into_kind(self) -> TransactionKind;
3047
3048 fn required_signers(&self) -> NonEmpty<SuiAddress>;
3050
3051 fn gas_data(&self) -> &GasData;
3052
3053 fn gas_owner(&self) -> SuiAddress;
3054
3055 fn gas(&self) -> &[ObjectRef];
3056
3057 fn gas_price(&self) -> u64;
3058
3059 fn gas_budget(&self) -> u64;
3060
3061 fn expiration(&self) -> &TransactionExpiration;
3062
3063 fn expiration_mut(&mut self) -> &mut TransactionExpiration;
3064
3065 fn move_calls(&self) -> Vec<(usize, &ObjectID, &str, &str)>;
3066
3067 fn input_objects(&self) -> UserInputResult<Vec<InputObjectKind>>;
3068
3069 fn shared_input_objects(&self) -> Vec<SharedInputObject>;
3070
3071 fn receiving_objects(&self) -> Vec<ObjectRef>;
3072
3073 fn fastpath_dependency_objects(
3077 &self,
3078 ) -> UserInputResult<(Vec<ObjectRef>, Vec<ObjectID>, Vec<ObjectRef>)>;
3079
3080 fn process_funds_withdrawals_for_signing(
3088 &self,
3089 chain_identifier: ChainIdentifier,
3090 coin_resolver: &dyn CoinReservationResolverTrait,
3091 ) -> UserInputResult<BTreeMap<AccumulatorObjId, (u64, TypeTag, SuiAddress)>>;
3092
3093 fn process_funds_withdrawals_for_estimation(
3097 &self,
3098 chain_identifier: ChainIdentifier,
3099 coin_resolver: &dyn CoinReservationResolverTrait,
3100 ) -> UserInputResult<BTreeMap<AccumulatorObjId, (u64, TypeTag, SuiAddress)>>;
3101
3102 fn process_funds_withdrawals_for_execution(
3105 &self,
3106 chain_identifier: ChainIdentifier,
3107 ) -> BTreeMap<AccumulatorObjId, u64>;
3108
3109 fn check_allowance_inputs(&self, input_objects: &InputObjects) -> UserInputResult<()>;
3113
3114 fn has_funds_withdrawals(&self) -> bool;
3116
3117 fn coin_reservation_obj_refs(
3118 &self,
3119 chain_identifier: ChainIdentifier,
3120 ) -> Vec<ParsedObjectRefWithdrawal>;
3121
3122 fn validity_check(&self, context: &TxValidityCheckContext<'_>) -> SuiResult;
3123
3124 fn check_sponsorship(&self) -> UserInputResult;
3126
3127 fn is_system_tx(&self) -> bool;
3128 fn is_genesis_tx(&self) -> bool;
3129
3130 fn is_end_of_epoch_tx(&self) -> bool;
3133
3134 fn is_consensus_commit_prologue(&self) -> bool;
3135
3136 fn is_sponsored_tx(&self) -> bool;
3138
3139 fn is_gas_paid_from_address_balance(&self) -> bool;
3140
3141 fn is_gasless_transaction(&self) -> bool;
3142
3143 fn sender_mut_for_testing(&mut self) -> &mut SuiAddress;
3144
3145 fn gas_data_mut(&mut self) -> &mut GasData;
3146
3147 fn expiration_mut_for_testing(&mut self) -> &mut TransactionExpiration;
3149}
3150
3151impl TransactionDataAPI for TransactionDataV1 {
3152 fn sender(&self) -> SuiAddress {
3153 self.sender
3154 }
3155
3156 fn kind(&self) -> &TransactionKind {
3157 &self.kind
3158 }
3159
3160 fn kind_mut(&mut self) -> &mut TransactionKind {
3161 &mut self.kind
3162 }
3163
3164 fn into_kind(self) -> TransactionKind {
3165 self.kind
3166 }
3167
3168 fn required_signers(&self) -> NonEmpty<SuiAddress> {
3170 let mut signers = nonempty![self.sender];
3171 if self.gas_owner() != self.sender {
3172 signers.push(self.gas_owner());
3173 }
3174 signers
3175 }
3176
3177 fn gas_data(&self) -> &GasData {
3178 &self.gas_data
3179 }
3180
3181 fn gas_owner(&self) -> SuiAddress {
3182 self.gas_data.owner
3183 }
3184
3185 fn gas(&self) -> &[ObjectRef] {
3186 &self.gas_data.payment
3187 }
3188
3189 fn gas_price(&self) -> u64 {
3190 self.gas_data.price
3191 }
3192
3193 fn gas_budget(&self) -> u64 {
3194 self.gas_data.budget
3195 }
3196
3197 fn expiration(&self) -> &TransactionExpiration {
3198 &self.expiration
3199 }
3200
3201 fn expiration_mut(&mut self) -> &mut TransactionExpiration {
3202 &mut self.expiration
3203 }
3204
3205 fn move_calls(&self) -> Vec<(usize, &ObjectID, &str, &str)> {
3206 self.kind.move_calls()
3207 }
3208
3209 fn input_objects(&self) -> UserInputResult<Vec<InputObjectKind>> {
3210 let mut inputs = self.kind.input_objects()?;
3211
3212 if !self.kind.is_system_tx() {
3213 inputs.extend(
3214 self.gas()
3215 .iter()
3216 .filter(|obj_ref| !ParsedDigest::is_coin_reservation_digest(&obj_ref.2))
3217 .map(|obj_ref| InputObjectKind::ImmOrOwnedMoveObject(*obj_ref)),
3218 );
3219 }
3220 Ok(inputs)
3221 }
3222
3223 fn shared_input_objects(&self) -> Vec<SharedInputObject> {
3224 self.kind.shared_input_objects().collect()
3225 }
3226
3227 fn receiving_objects(&self) -> Vec<ObjectRef> {
3228 self.kind.receiving_objects()
3229 }
3230
3231 fn fastpath_dependency_objects(
3232 &self,
3233 ) -> UserInputResult<(Vec<ObjectRef>, Vec<ObjectID>, Vec<ObjectRef>)> {
3234 let mut move_objects = vec![];
3235 let mut packages = vec![];
3236 let mut receiving_objects = vec![];
3237 self.input_objects()?.iter().for_each(|o| match o {
3238 InputObjectKind::ImmOrOwnedMoveObject(object_ref) => {
3239 move_objects.push(*object_ref);
3240 }
3241 InputObjectKind::MovePackage(package_id) => {
3242 packages.push(*package_id);
3243 }
3244 InputObjectKind::SharedMoveObject { .. } => {}
3245 });
3246 self.receiving_objects().iter().for_each(|object_ref| {
3247 receiving_objects.push(*object_ref);
3248 });
3249 Ok((move_objects, packages, receiving_objects))
3250 }
3251
3252 fn process_funds_withdrawals_for_signing(
3253 &self,
3254 chain_identifier: ChainIdentifier,
3255 coin_resolver: &dyn CoinReservationResolverTrait,
3256 ) -> UserInputResult<BTreeMap<AccumulatorObjId, (u64, TypeTag, SuiAddress)>> {
3257 self.accumulate_funds_withdrawals(chain_identifier, coin_resolver, true)
3258 }
3259
3260 fn process_funds_withdrawals_for_estimation(
3261 &self,
3262 chain_identifier: ChainIdentifier,
3263 coin_resolver: &dyn CoinReservationResolverTrait,
3264 ) -> UserInputResult<BTreeMap<AccumulatorObjId, (u64, TypeTag, SuiAddress)>> {
3265 self.accumulate_funds_withdrawals(chain_identifier, coin_resolver, false)
3266 }
3267
3268 fn process_funds_withdrawals_for_execution(
3269 &self,
3270 chain_identifier: ChainIdentifier,
3271 ) -> BTreeMap<AccumulatorObjId, u64> {
3272 let mut withdraws: Vec<_> = self.get_funds_withdrawals().collect();
3273 withdraws.extend(self.get_funds_withdrawal_for_gas_payment());
3274
3275 let mut withdraw_map: BTreeMap<AccumulatorObjId, u64> = BTreeMap::new();
3277 for withdraw in withdraws {
3278 let reserved_amount = match &withdraw.reservation {
3279 Reservation::MaxAmountU64(amount) => {
3280 assert!(*amount > 0, "verified in validity check");
3281 *amount
3282 }
3283 };
3284
3285 let withdrawal_owner = withdraw.owner_for_withdrawal(self);
3286
3287 let account_id =
3289 AccumulatorValue::get_field_id(withdrawal_owner, &withdraw.type_arg.to_type_tag())
3290 .unwrap();
3291
3292 let value = withdraw_map.entry(account_id).or_default();
3293 *value = value.checked_add(reserved_amount).unwrap();
3295 }
3296
3297 for obj in self.coin_reservation_obj_refs() {
3301 assert_reachable!("processing coin reservation withdrawal");
3302 let parsed = ParsedObjectRefWithdrawal::parse(&obj, chain_identifier).unwrap();
3304 let value = withdraw_map
3305 .entry(AccumulatorObjId::new_unchecked(parsed.unmasked_object_id))
3310 .or_default();
3311 *value = value.checked_add(parsed.reservation_amount()).unwrap();
3313 }
3314
3315 withdraw_map
3316 }
3317
3318 fn check_allowance_inputs(&self, input_objects: &InputObjects) -> UserInputResult<()> {
3319 let allowance_withdrawals: Vec<_> = self
3320 .get_funds_withdrawals()
3321 .filter_map(|w| match w.withdraw_from {
3322 WithdrawFrom::SenderAllowance { funder, allowance } => {
3323 Some((funder, allowance, w.type_arg.to_type_tag()))
3324 }
3325 _ => None,
3326 })
3327 .collect();
3328 if allowance_withdrawals.is_empty() {
3329 return Ok(());
3330 }
3331 let objects_by_id: BTreeMap<ObjectID, &Object> = input_objects
3333 .iter()
3334 .filter_map(|input| Some((input.id(), input.as_object()?)))
3335 .collect();
3336 let mut resolved_allowances: BTreeMap<ObjectID, ResolvedAllowance> = BTreeMap::new();
3339 for (specified_funder, allowance, requested_funds_type) in allowance_withdrawals {
3340 let resolved: &ResolvedAllowance = match resolved_allowances.entry(allowance) {
3341 Entry::Occupied(entry) => entry.into_mut(),
3342 Entry::Vacant(entry) => {
3343 let object = objects_by_id.get(&allowance).ok_or_else(|| {
3344 UserInputError::InvalidWithdrawReservation {
3345 error: format!(
3346 "Specified allowance {allowance} not found among the tx inputs"
3347 ),
3348 }
3349 })?;
3350 entry.insert(parse_allowance_object(object)?)
3351 }
3352 };
3353 if resolved.funder != specified_funder {
3354 return Err(UserInputError::InvalidWithdrawReservation {
3355 error: format!(
3356 "Specified funder {specified_funder} does not match the funder of \
3357 allowance {allowance}"
3358 ),
3359 });
3360 }
3361 if resolved.spender != Some(self.sender()) {
3362 return Err(UserInputError::InvalidWithdrawReservation {
3363 error: format!(
3364 "Transaction sender is not the spender of allowance {allowance}"
3365 ),
3366 });
3367 }
3368 if resolved.funds_type != requested_funds_type {
3369 return Err(UserInputError::InvalidWithdrawReservation {
3370 error: format!(
3371 "Allowance {allowance} is for {}, not {requested_funds_type}",
3372 resolved.funds_type
3373 ),
3374 });
3375 }
3376 }
3377 Ok(())
3378 }
3379
3380 fn has_funds_withdrawals(&self) -> bool {
3381 if self.is_gas_paid_from_address_balance() && self.gas_data().budget > 0 {
3382 return true;
3383 }
3384 if let TransactionKind::ProgrammableTransaction(pt) = &self.kind {
3385 for input in &pt.inputs {
3386 if matches!(input, CallArg::FundsWithdrawal(_)) {
3387 return true;
3388 }
3389 }
3390 }
3391 if self.coin_reservation_obj_refs().next().is_some() {
3392 return true;
3393 }
3394 false
3395 }
3396
3397 fn coin_reservation_obj_refs(
3398 &self,
3399 chain_identifier: ChainIdentifier,
3400 ) -> Vec<ParsedObjectRefWithdrawal> {
3401 self.coin_reservation_obj_refs()
3402 .filter_map(|obj_ref| ParsedObjectRefWithdrawal::parse(&obj_ref, chain_identifier))
3403 .collect()
3404 }
3405
3406 fn validity_check(&self, context: &TxValidityCheckContext<'_>) -> SuiResult {
3407 let config = context.config;
3408
3409 match self.expiration() {
3411 TransactionExpiration::None => (), TransactionExpiration::Epoch(max_epoch) => {
3413 if context.epoch > *max_epoch {
3414 return Err(SuiErrorKind::TransactionExpired.into());
3415 }
3416 }
3417 TransactionExpiration::ValidDuring {
3418 min_epoch,
3419 max_epoch,
3420 min_timestamp,
3421 max_timestamp,
3422 chain,
3423 ..
3424 }
3425 | TransactionExpiration::Validity {
3426 min_epoch,
3427 max_epoch,
3428 min_timestamp,
3429 max_timestamp,
3430 chain,
3431 ..
3432 } => {
3433 if matches!(self.expiration(), TransactionExpiration::Validity { .. }) {
3436 fp_ensure!(
3437 config.allowed_proposers(),
3438 UserInputError::Unsupported(
3439 "Restricting the proposers of a transaction is not supported"
3440 .to_string(),
3441 )
3442 .into()
3443 );
3444 }
3445
3446 if let Some(allowed_proposers) = self.expiration().allowed_proposers(context.epoch)
3449 {
3450 let proposers = &allowed_proposers.proposers;
3451
3452 let max_proposers = MAX_UNPAID_ALLOWED_PROPOSERS
3459 .max(self.gas_data.price / context.reference_gas_price.max(1))
3460 .min(context.committee_size as u64);
3461 fp_ensure!(
3462 proposers.len() as u64 <= max_proposers,
3463 UserInputError::InvalidExpiration {
3464 error: format!(
3465 "allowed_proposers has {} entries, but at most {max_proposers} \
3466 are permitted with gas price {}, reference gas price {}, and a \
3467 committee of {}",
3468 proposers.len(),
3469 self.gas_data.price,
3470 context.reference_gas_price,
3471 context.committee_size,
3472 ),
3473 }
3474 .into()
3475 );
3476
3477 fp_ensure!(
3480 proposers.iter().is_sorted_by(|a, b| a < b),
3481 UserInputError::InvalidExpiration {
3482 error: "allowed_proposers must be strictly increasing".to_string(),
3483 }
3484 .into()
3485 );
3486
3487 if let Some(out_of_range) =
3490 proposers.iter().find(|i| **i >= context.committee_size)
3491 {
3492 return Err(UserInputError::InvalidExpiration {
3493 error: format!(
3494 "allowed_proposers contains index {out_of_range}, but the \
3495 committee has {} members",
3496 context.committee_size,
3497 ),
3498 }
3499 .into());
3500 }
3501 }
3502
3503 if min_timestamp.is_some() || max_timestamp.is_some() {
3504 return Err(UserInputError::Unsupported(
3505 "Timestamp-based transaction expiration is not yet supported".to_string(),
3506 )
3507 .into());
3508 }
3509
3510 match (min_epoch, max_epoch) {
3515 _ if config.relax_valid_during_for_owned_inputs() => (),
3516 (Some(min), Some(max)) => {
3517 if config.enable_multi_epoch_transaction_expiration() {
3518 if !(*max == *min || *max == min.saturating_add(1)) {
3519 return Err(UserInputError::Unsupported(
3520 "max_epoch must be at most min_epoch + 1".to_string(),
3521 )
3522 .into());
3523 }
3524 } else if min != max {
3525 return Err(UserInputError::Unsupported(
3526 "min_epoch must equal max_epoch".to_string(),
3527 )
3528 .into());
3529 }
3530 }
3531 _ => {
3532 return Err(UserInputError::Unsupported(
3533 "Both min_epoch and max_epoch must be specified".to_string(),
3534 )
3535 .into());
3536 }
3537 }
3538
3539 if *chain != context.chain_identifier {
3540 return Err(UserInputError::InvalidChainId {
3541 provided: format!("{:?}", chain),
3542 expected: format!("{:?}", context.chain_identifier),
3543 }
3544 .into());
3545 }
3546
3547 if let Some(min) = min_epoch
3548 && context.epoch < *min
3549 {
3550 return Err(SuiErrorKind::TransactionExpired.into());
3551 }
3552 if let Some(max) = max_epoch
3553 && context.epoch > *max
3554 {
3555 return Err(SuiErrorKind::TransactionExpired.into());
3556 }
3557 }
3558 }
3559
3560 if self.has_funds_withdrawals() {
3561 fp_ensure!(
3564 !self.gas().is_empty() || config.enable_address_balance_gas_payments(),
3565 UserInputError::MissingGasPayment.into()
3566 );
3567
3568 fp_ensure!(
3569 config.enable_accumulators(),
3570 UserInputError::Unsupported("Address balance withdraw is not enabled".to_string())
3571 .into()
3572 );
3573
3574 let max_withdraws = 10;
3576 let mut num_reservations = 0;
3577
3578 for withdraw in self.kind.get_funds_withdrawals() {
3579 num_reservations += 1;
3580 match withdraw.withdraw_from {
3581 WithdrawFrom::Sender => (),
3582 WithdrawFrom::Sponsor => {
3583 return Err(UserInputError::InvalidWithdrawReservation {
3584 error: "Explicit sponsor withdrawals are not yet supported".to_string(),
3585 }
3586 .into());
3587 }
3588 WithdrawFrom::SenderAllowance { .. } => {
3591 fp_ensure!(
3592 config.enable_allowances(),
3593 UserInputError::Unsupported(
3594 "Allowance withdrawals are not enabled".to_string()
3595 )
3596 .into()
3597 );
3598 }
3599 }
3600
3601 match withdraw.reservation {
3602 Reservation::MaxAmountU64(amount) => {
3603 fp_ensure!(
3604 amount > 0,
3605 UserInputError::InvalidWithdrawReservation {
3606 error: "Balance withdraw reservation amount must be non-zero"
3607 .to_string(),
3608 }
3609 .into()
3610 );
3611 }
3612 };
3613 }
3614
3615 for parsed in self.parsed_coin_reservations(context.chain_identifier) {
3616 num_reservations += 1;
3617 if parsed.epoch_id() != context.epoch && parsed.epoch_id() + 1 != context.epoch {
3621 return Err(SuiErrorKind::TransactionExpired.into());
3622 }
3623 if parsed.reservation_amount() == 0 {
3624 return Err(UserInputError::InvalidWithdrawReservation {
3625 error: "Balance withdraw reservation amount must be non-zero".to_string(),
3626 }
3627 .into());
3628 }
3629 }
3630
3631 if config.enable_address_balance_gas_payments()
3633 && self.is_gas_paid_from_address_balance()
3634 {
3635 num_reservations += 1;
3636 }
3637
3638 fp_ensure!(
3639 num_reservations <= max_withdraws,
3640 UserInputError::InvalidWithdrawReservation {
3641 error: format!(
3642 "Maximum number of balance withdraw reservations is {max_withdraws}"
3643 ),
3644 }
3645 .into()
3646 );
3647 }
3648
3649 if config.enable_accumulators()
3650 && config.enable_address_balance_gas_payments()
3651 && self.is_gas_paid_from_address_balance()
3652 {
3653 if config.address_balance_gas_reject_gas_coin_arg()
3654 && let TransactionKind::ProgrammableTransaction(pt) = &self.kind
3655 {
3656 fp_ensure!(
3657 !pt.commands.iter().any(|cmd| cmd.is_gas_coin_used()),
3658 UserInputError::Unsupported(
3659 "Argument::GasCoin is not supported with address balance gas payments"
3660 .to_string(),
3661 )
3662 .into()
3663 );
3664 }
3665
3666 let is_gasless = config.enable_gasless() && self.is_gasless_transaction();
3667 if config.address_balance_gas_check_rgp_at_signing() && !is_gasless {
3668 fp_ensure!(
3669 self.gas_data.price >= context.reference_gas_price,
3670 UserInputError::GasPriceUnderRGP {
3671 gas_price: self.gas_data.price,
3672 reference_gas_price: context.reference_gas_price,
3673 }
3674 .into()
3675 );
3676 }
3677
3678 if !config.relax_valid_during_for_owned_inputs() {
3683 if matches!(self.expiration(), TransactionExpiration::None) {
3684 return Err(UserInputError::MissingGasPayment.into());
3687 }
3688
3689 if !self.expiration().is_replay_protected() {
3690 return Err(UserInputError::InvalidExpiration {
3691 error: "Address balance gas payments require ValidDuring expiration"
3692 .to_string(),
3693 }
3694 .into());
3695 }
3696 }
3697 } else {
3698 fp_ensure!(
3699 !self.gas().is_empty(),
3700 UserInputError::MissingGasPayment.into()
3701 );
3702 }
3703
3704 let gas_len = self.gas().len();
3705 let max_gas_objects = config.max_gas_payment_objects() as usize;
3706
3707 let within_limit = if config.correct_gas_payment_limit_check() {
3708 gas_len <= max_gas_objects
3709 } else {
3710 gas_len < max_gas_objects
3711 };
3712
3713 fp_ensure!(
3714 within_limit,
3715 UserInputError::SizeLimitExceeded {
3716 limit: "maximum number of gas payment objects".to_string(),
3717 value: config.max_gas_payment_objects().to_string()
3718 }
3719 .into()
3720 );
3721
3722 if !config.enable_coin_reservation_obj_refs() {
3723 for (_, _, gas_digest) in self.gas() {
3724 fp_ensure!(
3725 !ParsedDigest::is_coin_reservation_digest(gas_digest),
3726 UserInputError::GasObjectNotOwnedObject {
3727 owner: Owner::AddressOwner(self.sender)
3728 }
3729 .into()
3730 );
3731 }
3732 } else {
3733 let sui_accumulator_id =
3736 *AccumulatorValue::get_field_id(self.sender, &Balance::type_tag(GAS::type_tag()))?
3737 .inner();
3738
3739 for gas_ref in self.gas() {
3740 if let Some(parsed) =
3741 ParsedObjectRefWithdrawal::parse(gas_ref, context.chain_identifier)
3742 {
3743 fp_ensure!(
3746 self.gas_owner() == self.sender,
3747 UserInputError::GasObjectNotOwnedObject {
3748 owner: Owner::AddressOwner(self.sender)
3749 }
3750 .into()
3751 );
3752 fp_ensure!(
3753 parsed.unmasked_object_id == sui_accumulator_id,
3754 UserInputError::GasObjectNotOwnedObject {
3755 owner: Owner::AddressOwner(self.sender)
3756 }
3757 .into()
3758 );
3759 }
3760 }
3761 }
3762
3763 if !self.is_system_tx() {
3764 fp_ensure!(
3765 !check_for_gas_price_too_high(config.gas_model_version())
3766 || self.gas_data.price < config.max_gas_price(),
3767 UserInputError::GasPriceTooHigh {
3768 max_gas_price: config.max_gas_price(),
3769 }
3770 .into()
3771 );
3772 let cost_table = SuiCostTable::new(config, self.gas_data.price);
3773
3774 fp_ensure!(
3775 self.gas_data.budget <= cost_table.max_gas_budget,
3776 UserInputError::GasBudgetTooHigh {
3777 gas_budget: self.gas_data().budget,
3778 max_budget: cost_table.max_gas_budget,
3779 }
3780 .into()
3781 );
3782 let is_gasless = config.enable_gasless() && self.is_gasless_transaction();
3783 if is_gasless {
3784 fp_ensure!(
3785 self.gas_data.budget == 0,
3786 UserInputError::Unsupported(
3787 "gas_budget must be 0 for gasless transactions".to_string()
3788 )
3789 .into()
3790 );
3791 } else {
3792 fp_ensure!(
3793 self.gas_data.budget >= cost_table.min_transaction_cost,
3794 UserInputError::GasBudgetTooLow {
3795 gas_budget: self.gas_data.budget,
3796 min_budget: cost_table.min_transaction_cost,
3797 }
3798 .into()
3799 );
3800 }
3801 }
3802
3803 self.kind().validity_check(config)?;
3804
3805 if config.enable_gasless() && self.is_gasless_transaction() {
3806 let TransactionKind::ProgrammableTransaction(pt) = &self.kind else {
3807 debug_fatal!("gasless transaction is not a ProgrammableTransaction");
3808 return Err(UserInputError::Unsupported(
3809 "Gasless transactions must be programmable transactions".to_string(),
3810 )
3811 .into());
3812 };
3813 pt.validate_gasless_transaction(config)?;
3814 }
3815
3816 self.check_sponsorship()?;
3817 Ok(())
3818 }
3819
3820 fn is_sponsored_tx(&self) -> bool {
3822 self.gas_owner() != self.sender
3823 }
3824
3825 fn is_gas_paid_from_address_balance(&self) -> bool {
3829 is_gas_paid_from_address_balance(&self.gas_data, &self.kind)
3830 }
3831
3832 fn is_gasless_transaction(&self) -> bool {
3833 is_gasless_transaction(&self.gas_data, &self.kind)
3834 }
3835
3836 fn check_sponsorship(&self) -> UserInputResult {
3838 if self.gas_owner() == self.sender() {
3840 return Ok(());
3841 }
3842 if matches!(&self.kind, TransactionKind::ProgrammableTransaction(_)) {
3843 return Ok(());
3844 }
3845 Err(UserInputError::UnsupportedSponsoredTransactionKind)
3846 }
3847
3848 fn is_end_of_epoch_tx(&self) -> bool {
3849 matches!(
3850 self.kind,
3851 TransactionKind::ChangeEpoch(_) | TransactionKind::EndOfEpochTransaction(_)
3852 )
3853 }
3854
3855 fn is_consensus_commit_prologue(&self) -> bool {
3856 match &self.kind {
3857 TransactionKind::ConsensusCommitPrologue(_)
3858 | TransactionKind::ConsensusCommitPrologueV2(_)
3859 | TransactionKind::ConsensusCommitPrologueV3(_)
3860 | TransactionKind::ConsensusCommitPrologueV4(_) => true,
3861
3862 TransactionKind::ProgrammableTransaction(_)
3863 | TransactionKind::ProgrammableSystemTransaction(_)
3864 | TransactionKind::ChangeEpoch(_)
3865 | TransactionKind::Genesis(_)
3866 | TransactionKind::AuthenticatorStateUpdate(_)
3867 | TransactionKind::EndOfEpochTransaction(_)
3868 | TransactionKind::RandomnessStateUpdate(_) => false,
3869 }
3870 }
3871
3872 fn is_system_tx(&self) -> bool {
3873 self.kind.is_system_tx()
3874 }
3875
3876 fn is_genesis_tx(&self) -> bool {
3877 matches!(self.kind, TransactionKind::Genesis(_))
3878 }
3879
3880 fn sender_mut_for_testing(&mut self) -> &mut SuiAddress {
3881 &mut self.sender
3882 }
3883
3884 fn gas_data_mut(&mut self) -> &mut GasData {
3885 &mut self.gas_data
3886 }
3887
3888 fn expiration_mut_for_testing(&mut self) -> &mut TransactionExpiration {
3889 &mut self.expiration
3890 }
3891}
3892
3893impl TransactionDataV1 {
3894 fn accumulate_funds_withdrawals(
3895 &self,
3896 chain_identifier: ChainIdentifier,
3897 coin_resolver: &dyn CoinReservationResolverTrait,
3898 include_gas_payment: bool,
3899 ) -> UserInputResult<BTreeMap<AccumulatorObjId, (u64, TypeTag, SuiAddress)>> {
3900 let mut withdraws: Vec<_> = self.get_funds_withdrawals().collect();
3901
3902 for withdraw in self.parsed_coin_reservations(chain_identifier) {
3903 let withdrawal_arg =
3904 coin_resolver.resolve_funds_withdrawal(self.sender(), withdraw, None)?;
3905 withdraws.push(withdrawal_arg);
3906 }
3907
3908 if include_gas_payment {
3909 withdraws.extend(self.get_funds_withdrawal_for_gas_payment());
3910 }
3911
3912 let mut withdraw_map: BTreeMap<AccumulatorObjId, (u64, TypeTag, SuiAddress)> =
3913 BTreeMap::new();
3914 for withdraw in withdraws {
3915 let reserved_amount = match &withdraw.reservation {
3916 Reservation::MaxAmountU64(amount) => {
3917 if *amount == 0 {
3918 return Err(UserInputError::InvalidWithdrawReservation {
3919 error: "Balance withdraw reservation amount must be non-zero"
3920 .to_string(),
3921 });
3922 }
3923 *amount
3924 }
3925 };
3926
3927 let account_address = withdraw.owner_for_withdrawal(self);
3928 let type_tag = withdraw.type_arg.to_type_tag();
3929 let account_id =
3930 AccumulatorValue::get_field_id(account_address, &type_tag).map_err(|e| {
3931 UserInputError::InvalidWithdrawReservation {
3932 error: e.to_string(),
3933 }
3934 })?;
3935
3936 let (current_amount, _, _) = withdraw_map
3937 .entry(account_id)
3938 .or_insert_with(|| (0, type_tag, account_address));
3939 *current_amount = current_amount.checked_add(reserved_amount).ok_or(
3940 UserInputError::InvalidWithdrawReservation {
3941 error: "Balance withdraw reservation overflow".to_string(),
3942 },
3943 )?;
3944 }
3945
3946 Ok(withdraw_map)
3947 }
3948
3949 fn get_funds_withdrawal_for_gas_payment(&self) -> Option<FundsWithdrawalArg> {
3950 if self.is_gas_paid_from_address_balance() && self.gas_data().budget > 0 {
3951 Some(if self.sender() != self.gas_owner() {
3952 FundsWithdrawalArg::balance_from_sponsor(self.gas_data().budget, GAS::type_tag())
3953 } else {
3954 FundsWithdrawalArg::balance_from_sender(self.gas_data().budget, GAS::type_tag())
3955 })
3956 } else {
3957 None
3958 }
3959 }
3960
3961 fn get_funds_withdrawals(&self) -> impl Iterator<Item = FundsWithdrawalArg> + '_ {
3962 self.kind.get_funds_withdrawals().cloned()
3963 }
3964
3965 fn coin_reservation_obj_refs(&self) -> impl Iterator<Item = ObjectRef> {
3966 self.kind
3967 .get_coin_reservation_obj_refs()
3968 .chain(self.gas().iter().filter_map(|gas_ref| {
3969 if ParsedDigest::is_coin_reservation_digest(&gas_ref.2) {
3970 Some(*gas_ref)
3971 } else {
3972 None
3973 }
3974 }))
3975 }
3976
3977 fn parsed_coin_reservations(
3978 &self,
3979 chain_identifier: ChainIdentifier,
3980 ) -> impl Iterator<Item = ParsedObjectRefWithdrawal> {
3981 self.coin_reservation_obj_refs().map(move |obj_ref| {
3982 ParsedObjectRefWithdrawal::parse(&obj_ref, chain_identifier).unwrap()
3983 })
3984 }
3985}
3986
3987pub struct TxValidityCheckContext<'a> {
3988 pub config: &'a ProtocolConfig,
3989 pub epoch: EpochId,
3990 pub chain_identifier: ChainIdentifier,
3991 pub reference_gas_price: u64,
3992 pub committee_size: u32,
3994}
3995
3996impl<'a> TxValidityCheckContext<'a> {
3997 pub fn from_cfg_for_testing(config: &'a ProtocolConfig) -> Self {
3998 Self {
3999 config,
4000 epoch: 0,
4001 chain_identifier: ChainIdentifier::default(),
4002 reference_gas_price: 1000,
4003 committee_size: 4,
4004 }
4005 }
4006}
4007
4008#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
4009pub struct SenderSignedData(SizeOneVec<SenderSignedTransaction>);
4010
4011#[derive(Debug, Clone, PartialEq, Eq, Hash)]
4012pub struct SenderSignedTransaction {
4013 pub intent_message: IntentMessage<TransactionData>,
4014 pub tx_signatures: Vec<GenericSignature>,
4018}
4019
4020impl Serialize for SenderSignedTransaction {
4021 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
4022 where
4023 S: serde::Serializer,
4024 {
4025 #[derive(Serialize)]
4026 #[serde(rename = "SenderSignedTransaction")]
4027 struct SignedTxn<'a> {
4028 intent_message: &'a IntentMessage<TransactionData>,
4029 tx_signatures: &'a Vec<GenericSignature>,
4030 }
4031
4032 if self.intent_message().intent != Intent::sui_transaction() {
4033 return Err(serde::ser::Error::custom("invalid Intent for Transaction"));
4034 }
4035
4036 let txn = SignedTxn {
4037 intent_message: self.intent_message(),
4038 tx_signatures: &self.tx_signatures,
4039 };
4040 txn.serialize(serializer)
4041 }
4042}
4043
4044impl<'de> Deserialize<'de> for SenderSignedTransaction {
4045 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
4046 where
4047 D: serde::Deserializer<'de>,
4048 {
4049 #[derive(Deserialize)]
4050 #[serde(rename = "SenderSignedTransaction")]
4051 struct SignedTxn {
4052 intent_message: IntentMessage<TransactionData>,
4053 tx_signatures: Vec<GenericSignature>,
4054 }
4055
4056 let SignedTxn {
4057 intent_message,
4058 tx_signatures,
4059 } = Deserialize::deserialize(deserializer)?;
4060
4061 if intent_message.intent != Intent::sui_transaction() {
4062 return Err(serde::de::Error::custom("invalid Intent for Transaction"));
4063 }
4064
4065 Ok(Self {
4066 intent_message,
4067 tx_signatures,
4068 })
4069 }
4070}
4071
4072impl SenderSignedTransaction {
4073 pub(crate) fn get_signer_sig_mapping(
4075 &self,
4076 verify_legacy_zklogin_address: bool,
4077 ) -> SuiResult<BTreeMap<SuiAddress, (u8, &GenericSignature)>> {
4078 let mut mapping = BTreeMap::new();
4079 for (idx, sig) in self.tx_signatures.iter().enumerate() {
4080 if verify_legacy_zklogin_address && let GenericSignature::ZkLoginAuthenticator(z) = sig
4081 {
4082 mapping.insert(SuiAddress::try_from_padded(&z.inputs)?, (idx as u8, sig));
4084 }
4085 let address = sig.try_into()?;
4086 mapping.insert(address, (idx as u8, sig));
4087 }
4088 Ok(mapping)
4089 }
4090
4091 pub fn intent_message(&self) -> &IntentMessage<TransactionData> {
4092 &self.intent_message
4093 }
4094}
4095
4096impl SenderSignedData {
4097 pub fn new(tx_data: TransactionData, tx_signatures: Vec<GenericSignature>) -> Self {
4098 Self(SizeOneVec::new(SenderSignedTransaction {
4099 intent_message: IntentMessage::new(Intent::sui_transaction(), tx_data),
4100 tx_signatures,
4101 }))
4102 }
4103
4104 pub fn new_from_sender_signature(tx_data: TransactionData, tx_signature: Signature) -> Self {
4105 Self(SizeOneVec::new(SenderSignedTransaction {
4106 intent_message: IntentMessage::new(Intent::sui_transaction(), tx_data),
4107 tx_signatures: vec![tx_signature.into()],
4108 }))
4109 }
4110
4111 pub fn inner(&self) -> &SenderSignedTransaction {
4112 self.0.element()
4113 }
4114
4115 pub fn into_inner(self) -> SenderSignedTransaction {
4116 self.0.into_inner()
4117 }
4118
4119 pub fn inner_mut(&mut self) -> &mut SenderSignedTransaction {
4120 self.0.element_mut()
4121 }
4122
4123 pub fn add_signature(&mut self, new_signature: Signature) {
4126 self.inner_mut().tx_signatures.push(new_signature.into());
4127 }
4128
4129 pub(crate) fn get_signer_sig_mapping(
4130 &self,
4131 verify_legacy_zklogin_address: bool,
4132 ) -> SuiResult<BTreeMap<SuiAddress, (u8, &GenericSignature)>> {
4133 self.inner()
4134 .get_signer_sig_mapping(verify_legacy_zklogin_address)
4135 }
4136
4137 pub fn transaction_data(&self) -> &TransactionData {
4138 &self.intent_message().value
4139 }
4140
4141 pub fn intent_message(&self) -> &IntentMessage<TransactionData> {
4142 self.inner().intent_message()
4143 }
4144
4145 pub fn tx_signatures(&self) -> &[GenericSignature] {
4146 &self.inner().tx_signatures
4147 }
4148
4149 pub fn has_zklogin_sig(&self) -> bool {
4150 self.tx_signatures().iter().any(|sig| sig.is_zklogin())
4151 }
4152
4153 pub fn has_upgraded_multisig(&self) -> bool {
4154 self.tx_signatures()
4155 .iter()
4156 .any(|sig| sig.is_upgraded_multisig())
4157 }
4158
4159 #[cfg(test)]
4160 pub fn intent_message_mut_for_testing(&mut self) -> &mut IntentMessage<TransactionData> {
4161 &mut self.inner_mut().intent_message
4162 }
4163
4164 pub fn tx_signatures_mut_for_testing(&mut self) -> &mut Vec<GenericSignature> {
4166 &mut self.inner_mut().tx_signatures
4167 }
4168
4169 pub fn full_message_digest_with_alias_versions(
4171 &self,
4172 alias_versions: &Vec<(SuiAddress, Option<SequenceNumber>)>,
4173 ) -> SenderSignedDataDigest {
4174 let mut digest = DefaultHash::default();
4175 bcs::serialize_into(&mut digest, self).expect("serialization should not fail");
4176 bcs::serialize_into(&mut digest, alias_versions).expect("serialization should not fail");
4177 let hash = digest.finalize();
4178 SenderSignedDataDigest::new(hash.into())
4179 }
4180
4181 pub fn serialized_size(&self) -> SuiResult<usize> {
4182 bcs::serialized_size(self).map_err(|e| {
4183 SuiErrorKind::TransactionSerializationError {
4184 error: e.to_string(),
4185 }
4186 .into()
4187 })
4188 }
4189
4190 fn check_user_signature_protocol_compatibility(&self, config: &ProtocolConfig) -> SuiResult {
4191 for sig in &self.inner().tx_signatures {
4192 match sig {
4193 GenericSignature::MultiSig(_) => {
4194 if !config.upgraded_multisig_supported() {
4195 return Err(SuiErrorKind::UserInputError {
4196 error: UserInputError::Unsupported(
4197 "upgraded multisig format not enabled on this network".to_string(),
4198 ),
4199 }
4200 .into());
4201 }
4202 }
4203 GenericSignature::ZkLoginAuthenticator(_) => {
4204 if !config.zklogin_auth() {
4205 return Err(SuiErrorKind::UserInputError {
4206 error: UserInputError::Unsupported(
4207 "zklogin is not enabled on this network".to_string(),
4208 ),
4209 }
4210 .into());
4211 }
4212 }
4213 GenericSignature::PasskeyAuthenticator(_) => {
4214 if !config.passkey_auth() {
4215 return Err(SuiErrorKind::UserInputError {
4216 error: UserInputError::Unsupported(
4217 "passkey is not enabled on this network".to_string(),
4218 ),
4219 }
4220 .into());
4221 }
4222 }
4223 GenericSignature::Signature(_) | GenericSignature::MultiSigLegacy(_) => (),
4224 }
4225 }
4226
4227 Ok(())
4228 }
4229
4230 pub fn validity_check(&self, context: &TxValidityCheckContext<'_>) -> Result<usize, SuiError> {
4233 self.check_user_signature_protocol_compatibility(context.config)?;
4235
4236 let tx_data = &self.transaction_data();
4241 fp_ensure!(
4242 !tx_data.is_system_tx(),
4243 SuiErrorKind::UserInputError {
4244 error: UserInputError::Unsupported(
4245 "SenderSignedData must not contain system transaction".to_string()
4246 )
4247 }
4248 .into()
4249 );
4250
4251 let tx_size = self.serialized_size()?;
4253 let max_tx_size_bytes = context.config.max_tx_size_bytes();
4254 fp_ensure!(
4255 tx_size as u64 <= max_tx_size_bytes,
4256 SuiErrorKind::UserInputError {
4257 error: UserInputError::SizeLimitExceeded {
4258 limit: format!(
4259 "serialized transaction size exceeded maximum of {max_tx_size_bytes}"
4260 ),
4261 value: tx_size.to_string(),
4262 }
4263 }
4264 .into()
4265 );
4266
4267 if context.config.enable_gasless() && tx_data.is_gasless_transaction() {
4268 let gasless_max = context.config.get_gasless_max_tx_size_bytes();
4269 fp_ensure!(
4270 tx_size as u64 <= gasless_max,
4271 SuiErrorKind::UserInputError {
4272 error: UserInputError::SizeLimitExceeded {
4273 limit: format!(
4274 "serialized gasless transaction size exceeded maximum of {gasless_max}"
4275 ),
4276 value: tx_size.to_string(),
4277 }
4278 }
4279 .into()
4280 );
4281 }
4282
4283 tx_data.validity_check(context)?;
4284
4285 Ok(tx_size)
4286 }
4287}
4288
4289impl Message for SenderSignedData {
4290 type DigestType = TransactionDigest;
4291 const SCOPE: IntentScope = IntentScope::SenderSignedTransaction;
4292
4293 fn digest(&self) -> Self::DigestType {
4295 self.intent_message().value.digest()
4296 }
4297}
4298
4299impl<S> Envelope<SenderSignedData, S> {
4300 pub fn sender_address(&self) -> SuiAddress {
4301 self.data().intent_message().value.sender()
4302 }
4303
4304 pub fn gas_owner(&self) -> SuiAddress {
4305 self.data().intent_message().value.gas_owner()
4306 }
4307
4308 pub fn gas(&self) -> &[ObjectRef] {
4309 self.data().intent_message().value.gas()
4310 }
4311
4312 pub fn is_consensus_tx(&self) -> bool {
4313 self.transaction_data().has_funds_withdrawals()
4314 || self.shared_input_objects().next().is_some()
4315 }
4316
4317 pub fn shared_input_objects(&self) -> impl Iterator<Item = SharedInputObject> + '_ {
4318 self.data()
4319 .inner()
4320 .intent_message
4321 .value
4322 .shared_input_objects()
4323 .into_iter()
4324 }
4325
4326 pub fn key(&self) -> TransactionKey {
4328 match &self.data().intent_message().value.kind() {
4329 TransactionKind::RandomnessStateUpdate(rsu) => {
4330 TransactionKey::RandomnessRound(rsu.epoch, rsu.randomness_round)
4331 }
4332 _ => TransactionKey::Digest(*self.digest()),
4333 }
4334 }
4335
4336 pub fn non_digest_key(&self) -> Option<TransactionKey> {
4341 match &self.data().intent_message().value.kind() {
4342 TransactionKind::RandomnessStateUpdate(rsu) => Some(TransactionKey::RandomnessRound(
4343 rsu.epoch,
4344 rsu.randomness_round,
4345 )),
4346 _ => None,
4347 }
4348 }
4349
4350 pub fn is_system_tx(&self) -> bool {
4351 self.data().intent_message().value.is_system_tx()
4352 }
4353
4354 pub fn is_sponsored_tx(&self) -> bool {
4355 self.data().intent_message().value.is_sponsored_tx()
4356 }
4357}
4358
4359impl Transaction {
4360 pub fn from_data_and_signer(
4361 data: TransactionData,
4362 signers: Vec<&dyn Signer<Signature>>,
4363 ) -> Self {
4364 let signatures = {
4365 let intent_msg = IntentMessage::new(Intent::sui_transaction(), &data);
4366 signers
4367 .into_iter()
4368 .map(|s| Signature::new_secure(&intent_msg, s))
4369 .collect()
4370 };
4371 Self::from_data(data, signatures)
4372 }
4373
4374 pub fn from_data(data: TransactionData, signatures: Vec<Signature>) -> Self {
4376 Self::from_generic_sig_data(data, signatures.into_iter().map(|s| s.into()).collect())
4377 }
4378
4379 pub fn signature_from_signer(
4380 data: TransactionData,
4381 intent: Intent,
4382 signer: &dyn Signer<Signature>,
4383 ) -> Signature {
4384 let intent_msg = IntentMessage::new(intent, data);
4385 Signature::new_secure(&intent_msg, signer)
4386 }
4387
4388 pub fn from_generic_sig_data(data: TransactionData, signatures: Vec<GenericSignature>) -> Self {
4389 Self::new(SenderSignedData::new(data, signatures))
4390 }
4391
4392 pub fn to_tx_bytes_and_signatures(&self) -> (Base64, Vec<Base64>) {
4395 (
4396 Base64::from_bytes(&bcs::to_bytes(&self.data().intent_message().value).unwrap()),
4397 self.data()
4398 .inner()
4399 .tx_signatures
4400 .iter()
4401 .map(|s| Base64::from_bytes(s.as_ref()))
4402 .collect(),
4403 )
4404 }
4405}
4406
4407impl VerifiedTransaction {
4408 pub fn new_change_epoch(
4409 next_epoch: EpochId,
4410 protocol_version: ProtocolVersion,
4411 storage_charge: u64,
4412 computation_charge: u64,
4413 storage_rebate: u64,
4414 non_refundable_storage_fee: u64,
4415 epoch_start_timestamp_ms: u64,
4416 system_packages: Vec<(SequenceNumber, Vec<Vec<u8>>, Vec<ObjectID>)>,
4417 ) -> Self {
4418 ChangeEpoch {
4419 epoch: next_epoch,
4420 protocol_version,
4421 storage_charge,
4422 computation_charge,
4423 storage_rebate,
4424 non_refundable_storage_fee,
4425 epoch_start_timestamp_ms,
4426 system_packages,
4427 }
4428 .pipe(TransactionKind::ChangeEpoch)
4429 .pipe(Self::new_system_transaction)
4430 }
4431
4432 pub fn new_genesis_transaction(objects: Vec<GenesisObject>) -> Self {
4433 GenesisTransaction { objects }
4434 .pipe(TransactionKind::Genesis)
4435 .pipe(Self::new_system_transaction)
4436 }
4437
4438 pub fn new_consensus_commit_prologue(
4439 epoch: u64,
4440 round: u64,
4441 commit_timestamp_ms: CheckpointTimestamp,
4442 ) -> Self {
4443 ConsensusCommitPrologue {
4444 epoch,
4445 round,
4446 commit_timestamp_ms,
4447 }
4448 .pipe(TransactionKind::ConsensusCommitPrologue)
4449 .pipe(Self::new_system_transaction)
4450 }
4451
4452 pub fn new_consensus_commit_prologue_v2(
4453 epoch: u64,
4454 round: u64,
4455 commit_timestamp_ms: CheckpointTimestamp,
4456 consensus_commit_digest: ConsensusCommitDigest,
4457 ) -> Self {
4458 ConsensusCommitPrologueV2 {
4459 epoch,
4460 round,
4461 commit_timestamp_ms,
4462 consensus_commit_digest,
4463 }
4464 .pipe(TransactionKind::ConsensusCommitPrologueV2)
4465 .pipe(Self::new_system_transaction)
4466 }
4467
4468 pub fn new_consensus_commit_prologue_v3(
4469 epoch: u64,
4470 round: u64,
4471 commit_timestamp_ms: CheckpointTimestamp,
4472 consensus_commit_digest: ConsensusCommitDigest,
4473 consensus_determined_version_assignments: ConsensusDeterminedVersionAssignments,
4474 ) -> Self {
4475 ConsensusCommitPrologueV3 {
4476 epoch,
4477 round,
4478 sub_dag_index: None,
4480 commit_timestamp_ms,
4481 consensus_commit_digest,
4482 consensus_determined_version_assignments,
4483 }
4484 .pipe(TransactionKind::ConsensusCommitPrologueV3)
4485 .pipe(Self::new_system_transaction)
4486 }
4487
4488 pub fn new_consensus_commit_prologue_v4(
4489 epoch: u64,
4490 round: u64,
4491 commit_timestamp_ms: CheckpointTimestamp,
4492 consensus_commit_digest: ConsensusCommitDigest,
4493 consensus_determined_version_assignments: ConsensusDeterminedVersionAssignments,
4494 additional_state_digest: AdditionalConsensusStateDigest,
4495 ) -> Self {
4496 ConsensusCommitPrologueV4 {
4497 epoch,
4498 round,
4499 sub_dag_index: None,
4501 commit_timestamp_ms,
4502 consensus_commit_digest,
4503 consensus_determined_version_assignments,
4504 additional_state_digest,
4505 }
4506 .pipe(TransactionKind::ConsensusCommitPrologueV4)
4507 .pipe(Self::new_system_transaction)
4508 }
4509
4510 pub fn new_authenticator_state_update(
4511 epoch: u64,
4512 round: u64,
4513 new_active_jwks: Vec<ActiveJwk>,
4514 authenticator_obj_initial_shared_version: SequenceNumber,
4515 ) -> Self {
4516 AuthenticatorStateUpdate {
4517 epoch,
4518 round,
4519 new_active_jwks,
4520 authenticator_obj_initial_shared_version,
4521 }
4522 .pipe(TransactionKind::AuthenticatorStateUpdate)
4523 .pipe(Self::new_system_transaction)
4524 }
4525
4526 pub fn new_randomness_state_update(
4527 epoch: u64,
4528 randomness_round: RandomnessRound,
4529 random_bytes: Vec<u8>,
4530 randomness_obj_initial_shared_version: SequenceNumber,
4531 ) -> Self {
4532 RandomnessStateUpdate {
4533 epoch,
4534 randomness_round,
4535 random_bytes,
4536 randomness_obj_initial_shared_version,
4537 }
4538 .pipe(TransactionKind::RandomnessStateUpdate)
4539 .pipe(Self::new_system_transaction)
4540 }
4541
4542 pub fn new_end_of_epoch_transaction(txns: Vec<EndOfEpochTransactionKind>) -> Self {
4543 TransactionKind::EndOfEpochTransaction(txns).pipe(Self::new_system_transaction)
4544 }
4545
4546 pub fn new_system_transaction(system_transaction: TransactionKind) -> Self {
4547 system_transaction
4548 .pipe(TransactionData::new_system_transaction)
4549 .pipe(|data| {
4550 SenderSignedData::new_from_sender_signature(
4551 data,
4552 Ed25519SuiSignature::from_bytes(&[0; Ed25519SuiSignature::LENGTH])
4553 .unwrap()
4554 .into(),
4555 )
4556 })
4557 .pipe(Transaction::new)
4558 .pipe(Self::new_from_verified)
4559 }
4560}
4561
4562impl VerifiedSignedTransaction {
4563 pub fn new(
4565 epoch: EpochId,
4566 transaction: VerifiedTransaction,
4567 authority: AuthorityName,
4568 secret: &dyn Signer<AuthoritySignature>,
4569 ) -> Self {
4570 Self::new_from_verified(SignedTransaction::new(
4571 epoch,
4572 transaction.into_inner().into_data(),
4573 secret,
4574 authority,
4575 ))
4576 }
4577}
4578
4579pub type Transaction = Envelope<SenderSignedData, EmptySignInfo>;
4581pub type VerifiedTransaction = VerifiedEnvelope<SenderSignedData, EmptySignInfo>;
4582pub type TrustedTransaction = TrustedEnvelope<SenderSignedData, EmptySignInfo>;
4583
4584pub type SignedTransaction = Envelope<SenderSignedData, AuthoritySignInfo>;
4586pub type VerifiedSignedTransaction = VerifiedEnvelope<SenderSignedData, AuthoritySignInfo>;
4587
4588impl Transaction {
4589 pub fn verify_signature_for_testing(
4590 &self,
4591 current_epoch: EpochId,
4592 verify_params: &VerifyParams,
4593 ) -> SuiResult {
4594 verify_sender_signed_data_message_signatures(
4595 self.data(),
4596 current_epoch,
4597 verify_params,
4598 Arc::new(VerifiedDigestCache::new_empty()),
4599 vec![],
4600 )?;
4601 Ok(())
4602 }
4603
4604 pub fn try_into_verified_for_testing(
4605 self,
4606 current_epoch: EpochId,
4607 verify_params: &VerifyParams,
4608 ) -> SuiResult<VerifiedTransaction> {
4609 self.verify_signature_for_testing(current_epoch, verify_params)?;
4610 Ok(VerifiedTransaction::new_from_verified(self))
4611 }
4612}
4613
4614impl SignedTransaction {
4615 pub fn verify_signatures_authenticated_for_testing(
4616 &self,
4617 committee: &Committee,
4618 verify_params: &VerifyParams,
4619 ) -> SuiResult {
4620 verify_sender_signed_data_message_signatures(
4621 self.data(),
4622 committee.epoch(),
4623 verify_params,
4624 Arc::new(VerifiedDigestCache::new_empty()),
4625 vec![],
4626 )?;
4627
4628 self.auth_sig().verify_secure(
4629 self.data(),
4630 Intent::sui_app(IntentScope::SenderSignedTransaction),
4631 committee,
4632 )
4633 }
4634
4635 pub fn try_into_verified_for_testing(
4636 self,
4637 committee: &Committee,
4638 verify_params: &VerifyParams,
4639 ) -> SuiResult<VerifiedSignedTransaction> {
4640 self.verify_signatures_authenticated_for_testing(committee, verify_params)?;
4641 Ok(VerifiedSignedTransaction::new_from_verified(self))
4642 }
4643}
4644
4645pub type CertifiedTransaction = Envelope<SenderSignedData, AuthorityStrongQuorumSignInfo>;
4646
4647impl CertifiedTransaction {
4648 pub fn gas_price(&self) -> u64 {
4649 self.data().transaction_data().gas_price()
4650 }
4651}
4652
4653pub type VerifiedCertificate = VerifiedEnvelope<SenderSignedData, AuthorityStrongQuorumSignInfo>;
4654pub type TrustedCertificate = TrustedEnvelope<SenderSignedData, AuthorityStrongQuorumSignInfo>;
4655
4656#[derive(Clone, Debug, Serialize, Deserialize)]
4657pub struct WithAliases<T>(
4658 T,
4659 #[serde(with = "nonempty_as_vec")] NonEmpty<(u8, Option<SequenceNumber>)>,
4660);
4661
4662impl<T> WithAliases<T> {
4663 pub fn new(tx: T, aliases: NonEmpty<(u8, Option<SequenceNumber>)>) -> Self {
4664 Self(tx, aliases)
4665 }
4666
4667 pub fn tx(&self) -> &T {
4668 &self.0
4669 }
4670
4671 pub fn aliases(&self) -> &NonEmpty<(u8, Option<SequenceNumber>)> {
4672 &self.1
4673 }
4674
4675 pub fn into_tx(self) -> T {
4676 self.0
4677 }
4678
4679 pub fn into_aliases(self) -> NonEmpty<(u8, Option<SequenceNumber>)> {
4680 self.1
4681 }
4682
4683 pub fn into_inner(self) -> (T, NonEmpty<(u8, Option<SequenceNumber>)>) {
4684 (self.0, self.1)
4685 }
4686}
4687
4688impl<T: Message, S> WithAliases<VerifiedEnvelope<T, S>> {
4689 pub fn serializable(self) -> WithAliases<TrustedEnvelope<T, S>> {
4691 WithAliases(self.0.serializable(), self.1)
4692 }
4693}
4694
4695impl<S> WithAliases<Envelope<SenderSignedData, S>> {
4696 pub fn no_aliases(tx: Envelope<SenderSignedData, S>) -> Self {
4699 let required_signers = tx.intent_message().value.required_signers();
4700 assert_eq!(required_signers.len(), tx.tx_signatures().len());
4701 let no_aliases = required_signers
4702 .iter()
4703 .enumerate()
4704 .map(|(idx, _)| (idx as u8, None))
4705 .collect::<Vec<_>>();
4706 Self::new(
4707 tx,
4708 NonEmpty::from_vec(no_aliases).expect("must have at least one required_signer"),
4709 )
4710 }
4711}
4712
4713impl<S> WithAliases<VerifiedEnvelope<SenderSignedData, S>> {
4714 pub fn no_aliases(tx: VerifiedEnvelope<SenderSignedData, S>) -> Self {
4717 let required_signers = tx.intent_message().value.required_signers();
4718 assert_eq!(required_signers.len(), tx.tx_signatures().len());
4719 let no_aliases = required_signers
4720 .iter()
4721 .enumerate()
4722 .map(|(idx, _)| (idx as u8, None))
4723 .collect::<Vec<_>>();
4724 Self::new(
4725 tx,
4726 NonEmpty::from_vec(no_aliases).expect("must have at least one required_signer"),
4727 )
4728 }
4729}
4730
4731pub type TransactionWithAliases = WithAliases<Transaction>;
4732pub type VerifiedTransactionWithAliases = WithAliases<VerifiedTransaction>;
4733pub type TrustedTransactionWithAliases = WithAliases<TrustedTransaction>;
4734
4735#[derive(Clone, Debug, Serialize, Deserialize)]
4740pub struct DeprecatedWithAliases<T>(
4741 T,
4742 #[serde(with = "nonempty_as_vec")] NonEmpty<(SuiAddress, Option<SequenceNumber>)>,
4743);
4744
4745impl<T> DeprecatedWithAliases<T> {
4746 pub fn into_inner(self) -> (T, NonEmpty<(SuiAddress, Option<SequenceNumber>)>) {
4747 (self.0, self.1)
4748 }
4749}
4750
4751impl<T: Message, S> From<WithAliases<VerifiedEnvelope<T, S>>> for WithAliases<Envelope<T, S>> {
4752 fn from(value: WithAliases<VerifiedEnvelope<T, S>>) -> Self {
4753 Self(value.0.into(), value.1)
4754 }
4755}
4756
4757impl<T: Message, S> From<WithAliases<TrustedEnvelope<T, S>>>
4758 for WithAliases<VerifiedEnvelope<T, S>>
4759{
4760 fn from(value: WithAliases<TrustedEnvelope<T, S>>) -> Self {
4761 Self(value.0.into(), value.1)
4762 }
4763}
4764
4765mod nonempty_as_vec {
4766 use super::*;
4767 use serde::{Deserialize, Deserializer, Serialize, Serializer};
4768
4769 pub fn serialize<S, T>(value: &NonEmpty<T>, serializer: S) -> Result<S::Ok, S::Error>
4770 where
4771 S: Serializer,
4772 T: Serialize,
4773 {
4774 let vec: Vec<&T> = value.iter().collect();
4775 vec.serialize(serializer)
4776 }
4777
4778 pub fn deserialize<'de, D, T>(deserializer: D) -> Result<NonEmpty<T>, D::Error>
4779 where
4780 D: Deserializer<'de>,
4781 T: Deserialize<'de> + Clone,
4782 {
4783 use serde::de::{SeqAccess, Visitor};
4784 use std::fmt;
4785 use std::marker::PhantomData;
4786
4787 struct NonEmptyVisitor<T>(PhantomData<T>);
4788
4789 impl<'de, T> Visitor<'de> for NonEmptyVisitor<T>
4790 where
4791 T: Deserialize<'de> + Clone,
4792 {
4793 type Value = NonEmpty<T>;
4794
4795 fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
4796 formatter.write_str("a non-empty sequence")
4797 }
4798
4799 fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
4800 where
4801 A: SeqAccess<'de>,
4802 {
4803 let head = seq
4804 .next_element()?
4805 .ok_or_else(|| serde::de::Error::custom("empty vector"))?;
4806
4807 let mut tail = Vec::new();
4808 while let Some(elem) = seq.next_element()? {
4809 tail.push(elem);
4810 }
4811
4812 Ok(NonEmpty { head, tail })
4813 }
4814 }
4815
4816 deserializer.deserialize_seq(NonEmptyVisitor(PhantomData))
4817 }
4818}
4819
4820#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
4830pub enum TransactionClaim {
4831 #[deprecated(note = "Use AddressAliasesV2")]
4833 AddressAliases(
4834 #[serde(with = "nonempty_as_vec")] NonEmpty<(SuiAddress, Option<SequenceNumber>)>,
4835 ),
4836
4837 ImmutableInputObjects(Vec<ObjectID>),
4840
4841 AddressAliasesV2(#[serde(with = "nonempty_as_vec")] NonEmpty<(u8, Option<SequenceNumber>)>),
4845}
4846
4847#[derive(Clone, Debug, Serialize, Deserialize)]
4849pub struct TransactionWithClaims<T> {
4850 tx: T,
4851 claims: Vec<TransactionClaim>,
4852}
4853
4854impl<T> TransactionWithClaims<T> {
4855 pub fn new(tx: T, claims: Vec<TransactionClaim>) -> Self {
4856 Self { tx, claims }
4857 }
4858
4859 pub fn from_aliases(tx: T, aliases: NonEmpty<(u8, Option<SequenceNumber>)>) -> Self {
4861 Self {
4862 tx,
4863 claims: vec![TransactionClaim::AddressAliasesV2(aliases)],
4864 }
4865 }
4866
4867 pub fn no_aliases(tx: T) -> Self {
4869 Self { tx, claims: vec![] }
4870 }
4871
4872 pub fn tx(&self) -> &T {
4873 &self.tx
4874 }
4875
4876 pub fn into_tx(self) -> T {
4877 self.tx
4878 }
4879
4880 pub fn aliases(&self) -> Option<NonEmpty<(u8, Option<SequenceNumber>)>> {
4882 self.claims
4883 .iter()
4884 .find_map(|c| match c {
4885 TransactionClaim::AddressAliasesV2(aliases) => Some(aliases),
4886 _ => None,
4887 })
4888 .cloned()
4889 }
4890
4891 #[allow(deprecated)]
4893 pub fn aliases_v1(&self) -> Option<NonEmpty<(SuiAddress, Option<SequenceNumber>)>> {
4894 self.claims
4895 .iter()
4896 .find_map(|c| match c {
4897 TransactionClaim::AddressAliases(aliases) => Some(aliases),
4898 _ => None,
4899 })
4900 .cloned()
4901 }
4902
4903 pub fn get_immutable_objects(&self) -> Vec<ObjectID> {
4905 self.claims
4906 .iter()
4907 .find_map(|c| match c {
4908 TransactionClaim::ImmutableInputObjects(objs) => Some(objs.clone()),
4909 _ => None,
4910 })
4911 .unwrap_or_default()
4912 }
4913}
4914
4915pub type PlainTransactionWithClaims = TransactionWithClaims<Transaction>;
4916
4917impl<T: Message, S> From<WithAliases<VerifiedEnvelope<T, S>>>
4920 for TransactionWithClaims<Envelope<T, S>>
4921{
4922 fn from(value: WithAliases<VerifiedEnvelope<T, S>>) -> Self {
4923 let (tx, aliases) = value.into_inner();
4924 Self::from_aliases(tx.into(), aliases)
4925 }
4926}
4927
4928#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize, PartialOrd, Ord, Hash)]
4929pub enum InputObjectKind {
4930 MovePackage(ObjectID),
4932 ImmOrOwnedMoveObject(ObjectRef),
4934 SharedMoveObject {
4936 id: ObjectID,
4937 initial_shared_version: SequenceNumber,
4938 mutability: SharedObjectMutability,
4939 },
4940}
4941
4942#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize, PartialOrd, Ord, Hash)]
4943pub enum SharedObjectMutability {
4944 Immutable,
4946 Mutable,
4947 NonExclusiveWrite,
4951}
4952
4953impl SharedObjectMutability {
4954 pub fn is_exclusive(&self) -> bool {
4955 match self {
4956 SharedObjectMutability::Mutable => true,
4957 SharedObjectMutability::Immutable => false,
4958 SharedObjectMutability::NonExclusiveWrite => false,
4959 }
4960 }
4961}
4962
4963impl InputObjectKind {
4964 pub fn object_id(&self) -> ObjectID {
4965 self.full_object_id().id()
4966 }
4967
4968 pub fn full_object_id(&self) -> FullObjectID {
4969 match self {
4970 Self::MovePackage(id) => FullObjectID::Fastpath(*id),
4971 Self::ImmOrOwnedMoveObject((id, _, _)) => FullObjectID::Fastpath(*id),
4972 Self::SharedMoveObject {
4973 id,
4974 initial_shared_version,
4975 ..
4976 } => FullObjectID::Consensus((*id, *initial_shared_version)),
4977 }
4978 }
4979
4980 pub fn version(&self) -> Option<SequenceNumber> {
4981 match self {
4982 Self::MovePackage(..) => None,
4983 Self::ImmOrOwnedMoveObject((_, version, _)) => Some(*version),
4984 Self::SharedMoveObject { .. } => None,
4985 }
4986 }
4987
4988 pub fn object_not_found_error(&self) -> UserInputError {
4989 match *self {
4990 Self::MovePackage(package_id) => {
4991 UserInputError::DependentPackageNotFound { package_id }
4992 }
4993 Self::ImmOrOwnedMoveObject((object_id, version, _)) => UserInputError::ObjectNotFound {
4994 object_id,
4995 version: Some(version),
4996 },
4997 Self::SharedMoveObject { id, .. } => UserInputError::ObjectNotFound {
4998 object_id: id,
4999 version: None,
5000 },
5001 }
5002 }
5003
5004 pub fn is_shared_object(&self) -> bool {
5005 matches!(self, Self::SharedMoveObject { .. })
5006 }
5007}
5008
5009#[derive(Clone, Debug)]
5012pub struct ObjectReadResult {
5013 pub input_object_kind: InputObjectKind,
5014 pub object: ObjectReadResultKind,
5015}
5016
5017#[derive(Clone)]
5018pub enum ObjectReadResultKind {
5019 Object(Object),
5020 ObjectConsensusStreamEnded(SequenceNumber, TransactionDigest),
5023 CancelledTransactionSharedObject(SequenceNumber),
5025}
5026
5027impl ObjectReadResultKind {
5028 pub fn is_cancelled(&self) -> bool {
5029 matches!(
5030 self,
5031 ObjectReadResultKind::CancelledTransactionSharedObject(_)
5032 )
5033 }
5034
5035 pub fn version(&self) -> SequenceNumber {
5036 match self {
5037 ObjectReadResultKind::Object(object) => object.version(),
5038 ObjectReadResultKind::ObjectConsensusStreamEnded(seq, _) => *seq,
5039 ObjectReadResultKind::CancelledTransactionSharedObject(seq) => *seq,
5040 }
5041 }
5042}
5043
5044impl std::fmt::Debug for ObjectReadResultKind {
5045 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
5046 match self {
5047 ObjectReadResultKind::Object(obj) => {
5048 write!(f, "Object({:?})", obj.compute_object_reference())
5049 }
5050 ObjectReadResultKind::ObjectConsensusStreamEnded(seq, digest) => {
5051 write!(f, "ObjectConsensusStreamEnded({}, {:?})", seq, digest)
5052 }
5053 ObjectReadResultKind::CancelledTransactionSharedObject(seq) => {
5054 write!(f, "CancelledTransactionSharedObject({})", seq)
5055 }
5056 }
5057 }
5058}
5059
5060impl From<Object> for ObjectReadResultKind {
5061 fn from(object: Object) -> Self {
5062 Self::Object(object)
5063 }
5064}
5065
5066impl ObjectReadResult {
5067 pub fn new(input_object_kind: InputObjectKind, object: ObjectReadResultKind) -> Self {
5068 if let (
5069 InputObjectKind::ImmOrOwnedMoveObject(_),
5070 ObjectReadResultKind::ObjectConsensusStreamEnded(_, _),
5071 ) = (&input_object_kind, &object)
5072 {
5073 panic!("only consensus objects can be ObjectConsensusStreamEnded");
5074 }
5075
5076 if let (
5077 InputObjectKind::ImmOrOwnedMoveObject(_),
5078 ObjectReadResultKind::CancelledTransactionSharedObject(_),
5079 ) = (&input_object_kind, &object)
5080 {
5081 panic!("only consensus objects can be CancelledTransactionSharedObject");
5082 }
5083
5084 Self {
5085 input_object_kind,
5086 object,
5087 }
5088 }
5089
5090 pub fn id(&self) -> ObjectID {
5091 self.input_object_kind.object_id()
5092 }
5093
5094 pub fn as_object(&self) -> Option<&Object> {
5095 match &self.object {
5096 ObjectReadResultKind::Object(object) => Some(object),
5097 ObjectReadResultKind::ObjectConsensusStreamEnded(_, _) => None,
5098 ObjectReadResultKind::CancelledTransactionSharedObject(_) => None,
5099 }
5100 }
5101
5102 pub fn new_from_gas_object(gas: &Object) -> Self {
5103 let objref = gas.compute_object_reference();
5104 Self {
5105 input_object_kind: InputObjectKind::ImmOrOwnedMoveObject(objref),
5106 object: ObjectReadResultKind::Object(gas.clone()),
5107 }
5108 }
5109
5110 pub fn is_mutable(&self) -> bool {
5111 match (&self.input_object_kind, &self.object) {
5112 (InputObjectKind::MovePackage(_), _) => false,
5113 (InputObjectKind::ImmOrOwnedMoveObject(_), ObjectReadResultKind::Object(object)) => {
5114 !object.is_immutable()
5115 }
5116 (
5117 InputObjectKind::ImmOrOwnedMoveObject(_),
5118 ObjectReadResultKind::ObjectConsensusStreamEnded(_, _),
5119 ) => unreachable!(),
5120 (
5121 InputObjectKind::ImmOrOwnedMoveObject(_),
5122 ObjectReadResultKind::CancelledTransactionSharedObject(_),
5123 ) => unreachable!(),
5124 (InputObjectKind::SharedMoveObject { mutability, .. }, _) => match mutability {
5125 SharedObjectMutability::Mutable => true,
5126 SharedObjectMutability::Immutable => false,
5127 SharedObjectMutability::NonExclusiveWrite => false,
5128 },
5129 }
5130 }
5131
5132 pub fn is_shared_object(&self) -> bool {
5133 self.input_object_kind.is_shared_object()
5134 }
5135
5136 pub fn is_consensus_stream_ended(&self) -> bool {
5137 self.consensus_stream_end_info().is_some()
5138 }
5139
5140 pub fn consensus_stream_end_info(&self) -> Option<(SequenceNumber, TransactionDigest)> {
5141 match &self.object {
5142 ObjectReadResultKind::ObjectConsensusStreamEnded(v, tx) => Some((*v, *tx)),
5143 _ => None,
5144 }
5145 }
5146
5147 pub fn get_address_owned_objref(&self) -> Option<ObjectRef> {
5149 match (&self.input_object_kind, &self.object) {
5150 (InputObjectKind::MovePackage(_), _) => None,
5151 (
5152 InputObjectKind::ImmOrOwnedMoveObject(objref),
5153 ObjectReadResultKind::Object(object),
5154 ) => {
5155 if object.is_immutable() {
5156 None
5157 } else {
5158 Some(*objref)
5159 }
5160 }
5161 (
5162 InputObjectKind::ImmOrOwnedMoveObject(_),
5163 ObjectReadResultKind::ObjectConsensusStreamEnded(_, _),
5164 ) => unreachable!(),
5165 (
5166 InputObjectKind::ImmOrOwnedMoveObject(_),
5167 ObjectReadResultKind::CancelledTransactionSharedObject(_),
5168 ) => unreachable!(),
5169 (InputObjectKind::SharedMoveObject { .. }, _) => None,
5170 }
5171 }
5172
5173 pub fn is_address_owned(&self) -> bool {
5174 self.get_address_owned_objref().is_some()
5175 }
5176
5177 pub fn is_replay_protected_input(&self) -> bool {
5178 if let InputObjectKind::ImmOrOwnedMoveObject(obj_ref) = &self.input_object_kind
5179 && ParsedDigest::is_coin_reservation_digest(&obj_ref.2)
5180 {
5181 true
5182 } else {
5183 self.is_address_owned()
5184 }
5185 }
5186
5187 pub fn to_shared_input(&self) -> Option<SharedInput> {
5188 match self.input_object_kind {
5189 InputObjectKind::MovePackage(_) => None,
5190 InputObjectKind::ImmOrOwnedMoveObject(_) => None,
5191 InputObjectKind::SharedMoveObject { id, mutability, .. } => Some(match &self.object {
5192 ObjectReadResultKind::Object(obj) => {
5193 SharedInput::Existing(obj.compute_object_reference())
5194 }
5195 ObjectReadResultKind::ObjectConsensusStreamEnded(seq, digest) => {
5196 SharedInput::ConsensusStreamEnded((id, *seq, mutability, *digest))
5197 }
5198 ObjectReadResultKind::CancelledTransactionSharedObject(seq) => {
5199 SharedInput::Cancelled((id, *seq))
5200 }
5201 }),
5202 }
5203 }
5204
5205 pub fn get_previous_transaction(&self) -> Option<TransactionDigest> {
5206 match &self.object {
5207 ObjectReadResultKind::Object(obj) => Some(obj.previous_transaction),
5208 ObjectReadResultKind::ObjectConsensusStreamEnded(_, digest) => Some(*digest),
5209 ObjectReadResultKind::CancelledTransactionSharedObject(_) => None,
5210 }
5211 }
5212}
5213
5214#[derive(Clone)]
5215pub struct InputObjects {
5216 objects: Vec<ObjectReadResult>,
5217}
5218
5219impl std::fmt::Debug for InputObjects {
5220 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
5221 f.debug_list().entries(self.objects.iter()).finish()
5222 }
5223}
5224
5225#[derive(Clone)]
5228pub struct CheckedInputObjects(InputObjects);
5229
5230impl CheckedInputObjects {
5236 pub fn new_with_checked_transaction_inputs(inputs: InputObjects) -> Self {
5238 Self(inputs)
5239 }
5240
5241 pub fn new_for_genesis(input_objects: Vec<ObjectReadResult>) -> Self {
5243 Self(InputObjects::new(input_objects))
5244 }
5245
5246 pub fn new_for_replay(input_objects: InputObjects) -> Self {
5248 Self(input_objects)
5249 }
5250
5251 pub fn inner(&self) -> &InputObjects {
5252 &self.0
5253 }
5254
5255 pub fn into_inner(self) -> InputObjects {
5256 self.0
5257 }
5258}
5259
5260impl From<Vec<ObjectReadResult>> for InputObjects {
5261 fn from(objects: Vec<ObjectReadResult>) -> Self {
5262 Self::new(objects)
5263 }
5264}
5265
5266impl InputObjects {
5267 pub fn new(objects: Vec<ObjectReadResult>) -> Self {
5268 Self { objects }
5269 }
5270
5271 pub fn len(&self) -> usize {
5272 self.objects.len()
5273 }
5274
5275 pub fn is_empty(&self) -> bool {
5276 self.objects.is_empty()
5277 }
5278
5279 pub fn contains_consensus_stream_ended_objects(&self) -> bool {
5280 self.objects
5281 .iter()
5282 .any(|obj| obj.is_consensus_stream_ended())
5283 }
5284
5285 pub fn get_cancelled_objects(&self) -> Option<(Vec<ObjectID>, SequenceNumber)> {
5288 let mut contains_cancelled = false;
5289 let mut cancel_reason = None;
5290 let mut cancelled_objects = Vec::new();
5291 for obj in &self.objects {
5292 if let ObjectReadResultKind::CancelledTransactionSharedObject(version) = obj.object {
5293 contains_cancelled = true;
5294 if version == SequenceNumber::CONGESTED
5295 || version == SequenceNumber::RANDOMNESS_UNAVAILABLE
5296 {
5297 assert!(cancel_reason.is_none() || cancel_reason == Some(version));
5299 cancel_reason = Some(version);
5300 cancelled_objects.push(obj.id());
5301 }
5302 }
5303 }
5304
5305 if !cancelled_objects.is_empty() {
5306 Some((
5307 cancelled_objects,
5308 cancel_reason
5309 .expect("there should be a cancel reason if there are cancelled objects"),
5310 ))
5311 } else {
5312 assert!(!contains_cancelled);
5313 None
5314 }
5315 }
5316
5317 pub fn filter_owned_objects(&self) -> Vec<ObjectRef> {
5318 let owned_objects: Vec<_> = self
5319 .objects
5320 .iter()
5321 .filter_map(|obj| obj.get_address_owned_objref())
5322 .collect();
5323
5324 trace!(
5325 num_mutable_objects = owned_objects.len(),
5326 "Checked locks and found mutable objects"
5327 );
5328
5329 owned_objects
5330 }
5331
5332 pub fn filter_shared_objects(&self) -> Vec<SharedInput> {
5333 self.objects
5334 .iter()
5335 .filter(|obj| obj.is_shared_object())
5336 .map(|obj| {
5337 obj.to_shared_input()
5338 .expect("already filtered for shared objects")
5339 })
5340 .collect()
5341 }
5342
5343 pub fn transaction_dependencies(&self) -> BTreeSet<TransactionDigest> {
5344 self.objects
5345 .iter()
5346 .filter_map(|obj| obj.get_previous_transaction())
5347 .collect()
5348 }
5349
5350 pub fn exclusive_mutable_inputs(&self) -> BTreeMap<ObjectID, (VersionDigest, Owner)> {
5353 self.mutables_with_input_kinds()
5354 .filter_map(|(id, (version, owner, kind))| match kind {
5355 InputObjectKind::SharedMoveObject { mutability, .. } => match mutability {
5356 SharedObjectMutability::Mutable => Some((id, (version, owner))),
5357 SharedObjectMutability::Immutable => None,
5358 SharedObjectMutability::NonExclusiveWrite => None,
5359 },
5360 _ => Some((id, (version, owner))),
5361 })
5362 .collect()
5363 }
5364
5365 pub fn non_exclusive_input_objects(&self) -> BTreeMap<ObjectID, Object> {
5366 self.objects
5367 .iter()
5368 .filter_map(|read_result| {
5369 match (read_result.as_object(), read_result.input_object_kind) {
5370 (
5371 Some(object),
5372 InputObjectKind::SharedMoveObject {
5373 mutability: SharedObjectMutability::NonExclusiveWrite,
5374 ..
5375 },
5376 ) => Some((read_result.id(), object.clone())),
5377 _ => None,
5378 }
5379 })
5380 .collect()
5381 }
5382
5383 pub fn all_mutable_inputs(&self) -> BTreeMap<ObjectID, (VersionDigest, Owner)> {
5386 self.mutables_with_input_kinds()
5387 .filter_map(|(id, (version, owner, kind))| match kind {
5388 InputObjectKind::SharedMoveObject { mutability, .. } => match mutability {
5389 SharedObjectMutability::Mutable => Some((id, (version, owner))),
5390 SharedObjectMutability::Immutable => None,
5391 SharedObjectMutability::NonExclusiveWrite => Some((id, (version, owner))),
5392 },
5393 _ => Some((id, (version, owner))),
5394 })
5395 .collect()
5396 }
5397
5398 fn mutables_with_input_kinds(
5399 &self,
5400 ) -> impl Iterator<Item = (ObjectID, (VersionDigest, Owner, InputObjectKind))> + '_ {
5401 self.objects.iter().filter_map(
5402 |ObjectReadResult {
5403 input_object_kind,
5404 object,
5405 }| match (input_object_kind, object) {
5406 (InputObjectKind::MovePackage(_), _) => None,
5407 (
5408 InputObjectKind::ImmOrOwnedMoveObject(object_ref),
5409 ObjectReadResultKind::Object(object),
5410 ) => {
5411 if object.is_immutable() {
5412 None
5413 } else {
5414 Some((
5415 object_ref.0,
5416 (
5417 (object_ref.1, object_ref.2),
5418 object.owner.clone(),
5419 *input_object_kind,
5420 ),
5421 ))
5422 }
5423 }
5424 (
5425 InputObjectKind::ImmOrOwnedMoveObject(_),
5426 ObjectReadResultKind::ObjectConsensusStreamEnded(_, _),
5427 ) => {
5428 unreachable!()
5429 }
5430 (
5431 InputObjectKind::SharedMoveObject { .. },
5432 ObjectReadResultKind::ObjectConsensusStreamEnded(_, _),
5433 ) => None,
5434 (
5435 InputObjectKind::SharedMoveObject { mutability, .. },
5436 ObjectReadResultKind::Object(object),
5437 ) => match *mutability {
5438 SharedObjectMutability::Mutable => {
5439 let oref = object.compute_object_reference();
5440 Some((
5441 oref.0,
5442 ((oref.1, oref.2), object.owner.clone(), *input_object_kind),
5443 ))
5444 }
5445 SharedObjectMutability::Immutable => None,
5446 SharedObjectMutability::NonExclusiveWrite => {
5447 let oref = object.compute_object_reference();
5448 Some((
5449 oref.0,
5450 ((oref.1, oref.2), object.owner.clone(), *input_object_kind),
5451 ))
5452 }
5453 },
5454 (
5455 InputObjectKind::ImmOrOwnedMoveObject(_),
5456 ObjectReadResultKind::CancelledTransactionSharedObject(_),
5457 ) => {
5458 unreachable!()
5459 }
5460 (
5461 InputObjectKind::SharedMoveObject { .. },
5462 ObjectReadResultKind::CancelledTransactionSharedObject(_),
5463 ) => None,
5464 },
5465 )
5466 }
5467
5468 pub fn lamport_timestamp(&self, receiving_objects: &[ObjectRef]) -> SequenceNumber {
5472 let input_versions = self
5473 .objects
5474 .iter()
5475 .filter_map(|object| match &object.object {
5476 ObjectReadResultKind::Object(object) => {
5477 object.data.try_as_move().map(MoveObject::version)
5478 }
5479 ObjectReadResultKind::ObjectConsensusStreamEnded(v, _) => Some(*v),
5480 ObjectReadResultKind::CancelledTransactionSharedObject(_) => None,
5481 })
5482 .chain(receiving_objects.iter().map(|object_ref| object_ref.1));
5483
5484 SequenceNumber::lamport_increment(input_versions)
5485 }
5486
5487 pub fn object_kinds(&self) -> impl Iterator<Item = &InputObjectKind> {
5488 self.objects.iter().map(
5489 |ObjectReadResult {
5490 input_object_kind, ..
5491 }| input_object_kind,
5492 )
5493 }
5494
5495 pub fn consensus_stream_ended_objects(&self) -> BTreeMap<ObjectID, SequenceNumber> {
5496 self.objects
5497 .iter()
5498 .filter_map(|obj| {
5499 if let InputObjectKind::SharedMoveObject {
5500 id,
5501 initial_shared_version,
5502 ..
5503 } = obj.input_object_kind
5504 {
5505 obj.is_consensus_stream_ended()
5506 .then_some((id, initial_shared_version))
5507 } else {
5508 None
5509 }
5510 })
5511 .collect()
5512 }
5513
5514 pub fn into_object_map(self) -> BTreeMap<ObjectID, Object> {
5515 self.objects
5516 .into_iter()
5517 .filter_map(|o| o.as_object().map(|object| (o.id(), object.clone())))
5518 .collect()
5519 }
5520
5521 pub fn push(&mut self, object: ObjectReadResult) {
5522 self.objects.push(object);
5523 }
5524
5525 pub fn iter(&self) -> impl Iterator<Item = &ObjectReadResult> {
5526 self.objects.iter()
5527 }
5528
5529 pub fn iter_objects(&self) -> impl Iterator<Item = &Object> {
5530 self.objects.iter().filter_map(|o| o.as_object())
5531 }
5532
5533 pub fn non_exclusive_mutable_inputs(
5534 &self,
5535 ) -> impl Iterator<Item = (ObjectID, SequenceNumber)> + '_ {
5536 self.objects.iter().filter_map(
5537 |ObjectReadResult {
5538 input_object_kind,
5539 object,
5540 }| match input_object_kind {
5541 InputObjectKind::SharedMoveObject {
5545 id,
5546 mutability: SharedObjectMutability::NonExclusiveWrite,
5547 ..
5548 } if !object.is_cancelled() => Some((*id, object.version())),
5549 _ => None,
5550 },
5551 )
5552 }
5553}
5554
5555#[derive(Clone, Debug)]
5559pub enum ReceivingObjectReadResultKind {
5560 Object(Object),
5561 PreviouslyReceivedObject,
5563}
5564
5565impl ReceivingObjectReadResultKind {
5566 pub fn as_object(&self) -> Option<&Object> {
5567 match &self {
5568 Self::Object(object) => Some(object),
5569 Self::PreviouslyReceivedObject => None,
5570 }
5571 }
5572}
5573
5574pub struct ReceivingObjectReadResult {
5575 pub object_ref: ObjectRef,
5576 pub object: ReceivingObjectReadResultKind,
5577}
5578
5579impl ReceivingObjectReadResult {
5580 pub fn new(object_ref: ObjectRef, object: ReceivingObjectReadResultKind) -> Self {
5581 Self { object_ref, object }
5582 }
5583
5584 pub fn is_previously_received(&self) -> bool {
5585 matches!(
5586 self.object,
5587 ReceivingObjectReadResultKind::PreviouslyReceivedObject
5588 )
5589 }
5590}
5591
5592impl From<Object> for ReceivingObjectReadResultKind {
5593 fn from(object: Object) -> Self {
5594 Self::Object(object)
5595 }
5596}
5597
5598pub struct ReceivingObjects {
5599 pub objects: Vec<ReceivingObjectReadResult>,
5600}
5601
5602impl ReceivingObjects {
5603 pub fn iter(&self) -> impl Iterator<Item = &ReceivingObjectReadResult> {
5604 self.objects.iter()
5605 }
5606
5607 pub fn iter_objects(&self) -> impl Iterator<Item = &Object> {
5608 self.objects.iter().filter_map(|o| o.object.as_object())
5609 }
5610}
5611
5612impl From<Vec<ReceivingObjectReadResult>> for ReceivingObjects {
5613 fn from(objects: Vec<ReceivingObjectReadResult>) -> Self {
5614 Self { objects }
5615 }
5616}
5617
5618impl Display for CertifiedTransaction {
5619 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
5620 let mut writer = String::new();
5621 writeln!(writer, "Transaction Hash: {:?}", self.digest())?;
5622 writeln!(
5623 writer,
5624 "Signed Authorities Bitmap : {:?}",
5625 self.auth_sig().signers_map
5626 )?;
5627 write!(writer, "{}", &self.data().intent_message().value.kind())?;
5628 write!(f, "{}", writer)
5629 }
5630}
5631
5632#[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
5636pub enum TransactionKey {
5637 Digest(TransactionDigest),
5638 RandomnessRound(EpochId, RandomnessRound),
5639 AccumulatorSettlement(EpochId, u64 ),
5640 ConsensusCommitPrologue(EpochId, u64 , u32 ),
5641}
5642
5643impl TransactionKey {
5644 pub fn unwrap_digest(&self) -> &TransactionDigest {
5645 match self {
5646 TransactionKey::Digest(d) => d,
5647 _ => panic!("called unwrap_digest on a non-Digest TransactionKey: {self:?}"),
5648 }
5649 }
5650
5651 pub fn as_digest(&self) -> Option<&TransactionDigest> {
5652 match self {
5653 TransactionKey::Digest(d) => Some(d),
5654 _ => None,
5655 }
5656 }
5657}