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 has_shared_inputs(&self) -> bool {
1041 self.inputs
1042 .iter()
1043 .any(|input| matches!(input, CallArg::Object(ObjectArg::SharedObject { .. })))
1044 }
1045
1046 pub fn validate_gasless_transaction(&self, config: &ProtocolConfig) -> UserInputResult {
1047 fp_ensure!(
1048 !self.commands.is_empty(),
1049 UserInputError::Unsupported(
1050 "Gasless transactions must have at least one command".to_string()
1051 )
1052 );
1053
1054 for input in &self.inputs {
1055 match input {
1056 CallArg::Pure(_) | CallArg::FundsWithdrawal(_) => {}
1057 CallArg::Object(
1058 ObjectArg::ImmOrOwnedObject(_) | ObjectArg::SharedObject { .. },
1059 ) => {}
1060 CallArg::Object(ObjectArg::Receiving(_)) => {
1061 return Err(UserInputError::Unsupported(
1062 "Gasless transactions do not support Receiving object inputs".to_string(),
1063 ));
1064 }
1065 }
1066 }
1067
1068 let allowed_token_types = get_gasless_allowed_token_types(config);
1069
1070 for command in &self.commands {
1071 command.validate_gasless_transaction(&allowed_token_types)?;
1072 }
1073
1074 self.validate_gasless_inputs(config)?;
1075
1076 Ok(())
1077 }
1078
1079 fn validate_gasless_inputs(&self, config: &ProtocolConfig) -> UserInputResult {
1080 let mut used_inputs = vec![false; self.inputs.len()];
1081 for idx in self.commands.iter().flat_map(|cmd| cmd.input_arguments()) {
1082 if let Some(slot) = used_inputs.get_mut(idx as usize) {
1083 *slot = true;
1084 }
1085 }
1086
1087 let max_unused_pure = config.get_gasless_max_unused_inputs();
1088 let max_pure_bytes = config.get_gasless_max_pure_input_bytes();
1089 let mut unused_pure_count = 0u64;
1090
1091 for (i, input) in self.inputs.iter().enumerate() {
1092 let is_used = used_inputs[i];
1093 match input {
1094 CallArg::Pure(bytes) => {
1095 fp_ensure!(
1096 bytes.len() as u64 <= max_pure_bytes,
1097 UserInputError::Unsupported(format!(
1098 "Input {} has size {} bytes, but gasless transactions \
1099 allow at most {} bytes per Pure input",
1100 i,
1101 bytes.len(),
1102 max_pure_bytes
1103 ))
1104 );
1105 if !is_used {
1106 unused_pure_count += 1;
1107 }
1108 }
1109 CallArg::Object(_) if !is_used => {
1110 return Err(UserInputError::Unsupported(format!(
1111 "Gasless transactions do not allow unused Object inputs (input {})",
1112 i
1113 )));
1114 }
1115 CallArg::FundsWithdrawal(_) if !is_used => {
1116 return Err(UserInputError::Unsupported(format!(
1117 "Gasless transactions do not allow unused FundsWithdrawal inputs (input {})",
1118 i
1119 )));
1120 }
1121 CallArg::Object(_) | CallArg::FundsWithdrawal(_) => {}
1122 }
1123 }
1124
1125 fp_ensure!(
1126 unused_pure_count <= max_unused_pure,
1127 UserInputError::Unsupported(format!(
1128 "Gasless transactions allow at most {} unused Pure inputs, but found {}",
1129 max_unused_pure, unused_pure_count
1130 ))
1131 );
1132
1133 Ok(())
1134 }
1135}
1136
1137pub fn get_gasless_allowed_token_types(config: &ProtocolConfig) -> Arc<BTreeMap<TypeTag, u64>> {
1139 #[allow(clippy::type_complexity)]
1140 static CACHE: RwLock<Option<(u64, Arc<BTreeMap<TypeTag, u64>>)>> = RwLock::new(None);
1141
1142 let version = config.version.as_u64();
1143
1144 if let Some((v, map)) = CACHE.read().unwrap().as_ref()
1146 && *v == version
1147 {
1148 return apply_test_token_overrides(Arc::clone(map));
1149 }
1150
1151 let mut cache = CACHE.write().unwrap();
1153 if let Some((v, map)) = cache.as_ref()
1154 && *v == version
1155 {
1156 return apply_test_token_overrides(Arc::clone(map));
1157 }
1158 let map: BTreeMap<TypeTag, u64> = config
1159 .gasless_allowed_token_types()
1160 .iter()
1161 .map(|(s, min_amount)| {
1162 let tag: TypeTag = s
1163 .parse()
1164 .unwrap_or_else(|e| panic!("invalid gasless token type {s:?}: {e}"));
1165 (tag, *min_amount)
1166 })
1167 .collect();
1168 let arc = Arc::new(map);
1169 *cache = Some((version, Arc::clone(&arc)));
1170 apply_test_token_overrides(arc)
1171}
1172
1173fn apply_test_token_overrides(base: Arc<BTreeMap<TypeTag, u64>>) -> Arc<BTreeMap<TypeTag, u64>> {
1174 #[cfg(feature = "testing")]
1175 {
1176 let overrides = GASLESS_TOKENS_FOR_TESTING.read().unwrap();
1177 if !overrides.is_empty() {
1178 let mut types = (*base).clone();
1179 for (s, min_transfer) in overrides.iter() {
1180 match s.parse() {
1181 Ok(tag) => {
1182 types.insert(tag, *min_transfer);
1183 }
1184 Err(e) => {
1185 debug_fatal!("invalid gasless token override {s:?}: {e}");
1186 }
1187 }
1188 }
1189 return Arc::new(types);
1190 }
1191 }
1192 base
1193}
1194
1195#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)]
1197pub enum Command {
1198 MoveCall(Box<ProgrammableMoveCall>),
1200 TransferObjects(Vec<Argument>, Argument),
1205 SplitCoins(Argument, Vec<Argument>),
1208 MergeCoins(Argument, Vec<Argument>),
1211 Publish(Vec<Vec<u8>>, Vec<ObjectID>),
1214 MakeMoveVec(Option<TypeInput>, Vec<Argument>),
1218 Upgrade(Vec<Vec<u8>>, Vec<ObjectID>, ObjectID, Argument),
1226}
1227
1228#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy, Serialize, Deserialize)]
1230pub enum Argument {
1231 GasCoin,
1234 Input(u16),
1237 Result(u16),
1239 NestedResult(u16, u16),
1242}
1243
1244#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)]
1247pub struct ProgrammableMoveCall {
1248 pub package: ObjectID,
1250 pub module: String,
1252 pub function: String,
1254 pub type_arguments: Vec<TypeInput>,
1256 pub arguments: Vec<Argument>,
1258}
1259
1260impl ProgrammableMoveCall {
1261 fn input_objects(&self) -> Vec<InputObjectKind> {
1262 let ProgrammableMoveCall {
1263 package,
1264 type_arguments,
1265 ..
1266 } = self;
1267 let mut packages = BTreeSet::from([*package]);
1268 for type_argument in type_arguments {
1269 add_type_input_packages(&mut packages, type_argument)
1270 }
1271 packages
1272 .into_iter()
1273 .map(InputObjectKind::MovePackage)
1274 .collect()
1275 }
1276
1277 pub fn validity_check(&self, config: &ProtocolConfig) -> UserInputResult {
1278 let is_blocked = BLOCKED_MOVE_FUNCTIONS.contains(&(
1279 self.package,
1280 self.module.as_str(),
1281 self.function.as_str(),
1282 ));
1283 fp_ensure!(!is_blocked, UserInputError::BlockedMoveFunction);
1284 let mut type_arguments_count = 0;
1285 for tag in &self.type_arguments {
1286 type_input_validity_check(tag, config, &mut type_arguments_count)?;
1287 }
1288 fp_ensure!(
1289 self.arguments.len() < config.max_arguments() as usize,
1290 UserInputError::SizeLimitExceeded {
1291 limit: "maximum arguments in a move call".to_string(),
1292 value: config.max_arguments().to_string()
1293 }
1294 );
1295 if config.validate_identifier_inputs() {
1296 fp_ensure!(
1297 identifier::is_valid(&self.module),
1298 UserInputError::InvalidIdentifier {
1299 error: self.module.clone()
1300 }
1301 );
1302 fp_ensure!(
1303 identifier::is_valid(&self.function),
1304 UserInputError::InvalidIdentifier {
1305 error: self.module.clone()
1306 }
1307 );
1308 }
1309 Ok(())
1310 }
1311
1312 fn validate_gasless_transaction(
1313 &self,
1314 allowed_token_types: &BTreeMap<TypeTag, u64>,
1315 ) -> UserInputResult {
1316 type FunctionIdent = (AccountAddress, &'static IdentStr, &'static IdentStr);
1317
1318 enum TypeArgConstraint {
1319 FundType,
1321 BalanceType,
1323 }
1324 use TypeArgConstraint::*;
1325
1326 const SUI_BALANCE_SEND_FUNDS: FunctionIdent = (
1327 SUI_FRAMEWORK_ADDRESS,
1328 BALANCE_MODULE_NAME,
1329 BALANCE_SEND_FUNDS_FUNCTION_NAME,
1330 );
1331 const SUI_BALANCE_REDEEM_FUNDS: FunctionIdent = (
1332 SUI_FRAMEWORK_ADDRESS,
1333 BALANCE_MODULE_NAME,
1334 BALANCE_REDEEM_FUNDS_FUNCTION_NAME,
1335 );
1336 const SUI_BALANCE_SPLIT: FunctionIdent = (
1337 SUI_FRAMEWORK_ADDRESS,
1338 BALANCE_MODULE_NAME,
1339 BALANCE_SPLIT_FUNCTION_NAME,
1340 );
1341 const SUI_BALANCE_ZERO: FunctionIdent = (
1342 SUI_FRAMEWORK_ADDRESS,
1343 BALANCE_MODULE_NAME,
1344 BALANCE_ZERO_FUNCTION_NAME,
1345 );
1346 const SUI_FUNDS_ACCUMULATOR_WITHDRAWAL_SPLIT: FunctionIdent = (
1347 SUI_FRAMEWORK_ADDRESS,
1348 FUNDS_ACCUMULATOR_MODULE_NAME,
1349 WITHDRAWAL_SPLIT_FUNC_NAME,
1350 );
1351 const SUI_COIN_INTO_BALANCE: FunctionIdent = (
1352 SUI_FRAMEWORK_ADDRESS,
1353 COIN_MODULE_NAME,
1354 INTO_BALANCE_FUNC_NAME,
1355 );
1356 const SUI_COIN_REDEEM_FUNDS: FunctionIdent = (
1357 SUI_FRAMEWORK_ADDRESS,
1358 COIN_MODULE_NAME,
1359 REDEEM_FUNDS_FUNC_NAME,
1360 );
1361 const SUI_COIN_SEND_FUNDS: FunctionIdent = (
1362 SUI_FRAMEWORK_ADDRESS,
1363 COIN_MODULE_NAME,
1364 SEND_FUNDS_FUNC_NAME,
1365 );
1366 const SUI_COIN_PUT: FunctionIdent =
1367 (SUI_FRAMEWORK_ADDRESS, COIN_MODULE_NAME, PUT_FUNC_NAME);
1368
1369 const GASLESS_FUNCTIONS: &[(FunctionIdent, &[Option<TypeArgConstraint>])] = &[
1370 (SUI_BALANCE_SEND_FUNDS, &[Some(FundType)]),
1371 (SUI_BALANCE_REDEEM_FUNDS, &[Some(FundType)]),
1372 (SUI_BALANCE_SPLIT, &[Some(FundType)]),
1373 (SUI_BALANCE_ZERO, &[Some(FundType)]),
1374 (SUI_FUNDS_ACCUMULATOR_WITHDRAWAL_SPLIT, &[Some(BalanceType)]),
1375 (SUI_COIN_INTO_BALANCE, &[Some(FundType)]),
1376 (SUI_COIN_REDEEM_FUNDS, &[Some(FundType)]),
1377 (SUI_COIN_SEND_FUNDS, &[Some(FundType)]),
1378 (SUI_COIN_PUT, &[Some(FundType)]),
1379 ];
1380
1381 let Some((_, type_arg_constraints)) =
1382 GASLESS_FUNCTIONS
1383 .iter()
1384 .find(|((addr, module, function), _)| {
1385 *addr == AccountAddress::from(self.package)
1386 && module.as_str() == self.module
1387 && function.as_str() == self.function
1388 })
1389 else {
1390 return Err(UserInputError::Unsupported(format!(
1391 "Function {}::{}::{} is not supported in gasless transactions",
1392 self.package, self.module, self.function
1393 )));
1394 };
1395
1396 fp_ensure!(
1397 type_arg_constraints.len() == self.type_arguments.len(),
1398 UserInputError::Unsupported(format!(
1399 "Function {}::{}::{} requires {} type arguments, but {} were provided",
1400 self.package,
1401 self.module,
1402 self.function,
1403 type_arg_constraints.len(),
1404 self.type_arguments.len()
1405 ))
1406 );
1407
1408 for (type_arg_constraint, type_input) in type_arg_constraints
1409 .iter()
1410 .zip_debug_eq(&self.type_arguments)
1411 {
1412 let Some(type_arg_constraint) = type_arg_constraint else {
1413 continue;
1414 };
1415 let type_arg = type_input.to_type_tag().map_err(|e| {
1416 UserInputError::Unsupported(format!(
1417 "Failed to parse type argument {type_input} as a type tag: {e}"
1418 ))
1419 })?;
1420 let fund_type = match type_arg_constraint {
1421 TypeArgConstraint::FundType => type_arg,
1422 TypeArgConstraint::BalanceType => Balance::maybe_get_balance_type_param(&type_arg)
1423 .ok_or_else(|| {
1424 UserInputError::Unsupported(format!(
1425 "Expected a type Balance<_> but got {type_input}",
1426 ))
1427 })?,
1428 };
1429 fp_ensure!(
1430 allowed_token_types.contains_key(&fund_type),
1431 UserInputError::Unsupported(format!(
1432 "Fund type {fund_type} is not currently allowed in gasless transactions"
1433 ))
1434 );
1435 }
1436 Ok(())
1437 }
1438}
1439
1440impl Command {
1441 pub fn move_call(
1442 package: ObjectID,
1443 module: Identifier,
1444 function: Identifier,
1445 type_arguments: Vec<TypeTag>,
1446 arguments: Vec<Argument>,
1447 ) -> Self {
1448 let module = module.to_string();
1449 let function = function.to_string();
1450 let type_arguments = type_arguments.into_iter().map(TypeInput::from).collect();
1451 Command::MoveCall(Box::new(ProgrammableMoveCall {
1452 package,
1453 module,
1454 function,
1455 type_arguments,
1456 arguments,
1457 }))
1458 }
1459
1460 pub fn make_move_vec(ty: Option<TypeTag>, args: Vec<Argument>) -> Self {
1461 Command::MakeMoveVec(ty.map(TypeInput::from), args)
1462 }
1463
1464 fn input_objects(&self) -> Vec<InputObjectKind> {
1465 match self {
1466 Command::Upgrade(_, deps, package_id, _) => deps
1467 .iter()
1468 .map(|id| InputObjectKind::MovePackage(*id))
1469 .chain(Some(InputObjectKind::MovePackage(*package_id)))
1470 .collect(),
1471 Command::Publish(_, deps) => deps
1472 .iter()
1473 .map(|id| InputObjectKind::MovePackage(*id))
1474 .collect(),
1475 Command::MoveCall(c) => c.input_objects(),
1476 Command::MakeMoveVec(Some(t), _) => {
1477 let mut packages = BTreeSet::new();
1478 add_type_input_packages(&mut packages, t);
1479 packages
1480 .into_iter()
1481 .map(InputObjectKind::MovePackage)
1482 .collect()
1483 }
1484 Command::MakeMoveVec(None, _)
1485 | Command::TransferObjects(_, _)
1486 | Command::SplitCoins(_, _)
1487 | Command::MergeCoins(_, _) => vec![],
1488 }
1489 }
1490
1491 fn non_system_packages_to_be_published(&self) -> Option<&Vec<Vec<u8>>> {
1492 match self {
1493 Command::Upgrade(v, _, _, _) => Some(v),
1494 Command::Publish(v, _) => Some(v),
1495 Command::MoveCall(_)
1496 | Command::TransferObjects(_, _)
1497 | Command::SplitCoins(_, _)
1498 | Command::MergeCoins(_, _)
1499 | Command::MakeMoveVec(_, _) => None,
1500 }
1501 }
1502
1503 fn validity_check(&self, config: &ProtocolConfig) -> UserInputResult {
1504 match self {
1505 Command::MoveCall(call) => call.validity_check(config)?,
1506 Command::TransferObjects(args, _)
1507 | Command::MergeCoins(_, args)
1508 | Command::SplitCoins(_, args) => {
1509 fp_ensure!(!args.is_empty(), UserInputError::EmptyCommandInput);
1510 fp_ensure!(
1511 args.len() < config.max_arguments() as usize,
1512 UserInputError::SizeLimitExceeded {
1513 limit: "maximum arguments in a programmable transaction command"
1514 .to_string(),
1515 value: config.max_arguments().to_string()
1516 }
1517 );
1518 }
1519 Command::MakeMoveVec(ty_opt, args) => {
1520 fp_ensure!(
1522 ty_opt.is_some() || !args.is_empty(),
1523 UserInputError::EmptyCommandInput
1524 );
1525 if let Some(ty) = ty_opt {
1526 let mut type_arguments_count = 0;
1527 type_input_validity_check(ty, config, &mut type_arguments_count)?;
1528 }
1529 fp_ensure!(
1530 args.len() < config.max_arguments() as usize,
1531 UserInputError::SizeLimitExceeded {
1532 limit: "maximum arguments in a programmable transaction command"
1533 .to_string(),
1534 value: config.max_arguments().to_string()
1535 }
1536 );
1537 }
1538 Command::Publish(modules, deps) | Command::Upgrade(modules, deps, _, _) => {
1539 fp_ensure!(!modules.is_empty(), UserInputError::EmptyCommandInput);
1540 fp_ensure!(
1541 modules.len() < config.max_modules_in_publish() as usize,
1542 UserInputError::SizeLimitExceeded {
1543 limit: "maximum modules in a programmable transaction upgrade command"
1544 .to_string(),
1545 value: config.max_modules_in_publish().to_string()
1546 }
1547 );
1548 if let Some(max_package_dependencies) = config.max_package_dependencies_as_option()
1549 {
1550 fp_ensure!(
1551 deps.len() < max_package_dependencies as usize,
1552 UserInputError::SizeLimitExceeded {
1553 limit: "maximum package dependencies".to_string(),
1554 value: max_package_dependencies.to_string()
1555 }
1556 );
1557 };
1558 }
1559 };
1560 Ok(())
1561 }
1562
1563 fn validate_gasless_transaction(
1564 &self,
1565 allowed_token_types: &BTreeMap<TypeTag, u64>,
1566 ) -> UserInputResult {
1567 match self {
1568 Command::MoveCall(call) => call.validate_gasless_transaction(allowed_token_types),
1569 Command::MergeCoins(_, _) | Command::SplitCoins(_, _) => Ok(()),
1570 _ => Err(UserInputError::Unsupported(
1571 "Gasless transactions only support MoveCall, MergeCoins, and SplitCoins commands"
1572 .to_string(),
1573 )),
1574 }
1575 }
1576
1577 fn is_input_arg_used(&self, input_arg: u16) -> bool {
1578 self.is_argument_used(Argument::Input(input_arg))
1579 }
1580
1581 pub fn is_gas_coin_used(&self) -> bool {
1582 self.is_argument_used(Argument::GasCoin)
1583 }
1584
1585 pub fn is_argument_used(&self, argument: Argument) -> bool {
1586 self.arguments().any(|a| a == &argument)
1587 }
1588
1589 fn input_arguments(&self) -> impl Iterator<Item = u16> + '_ {
1590 self.arguments().filter_map(|arg| match arg {
1591 Argument::Input(i) => Some(*i),
1592 _ => None,
1593 })
1594 }
1595
1596 fn arguments(&self) -> impl Iterator<Item = &Argument> + '_ {
1597 let (args, single): (&[Argument], Option<&Argument>) = match self {
1598 Command::MoveCall(c) => (&c.arguments, None),
1599 Command::TransferObjects(args, arg)
1600 | Command::MergeCoins(arg, args)
1601 | Command::SplitCoins(arg, args) => (args, Some(arg)),
1602 Command::MakeMoveVec(_, args) => (args, None),
1603 Command::Upgrade(_, _, _, arg) => (&[], Some(arg)),
1604 Command::Publish(_, _) => (&[], None),
1605 };
1606 args.iter().chain(single)
1607 }
1608}
1609
1610pub fn write_sep<T: Display>(
1611 f: &mut Formatter<'_>,
1612 items: impl IntoIterator<Item = T>,
1613 sep: &str,
1614) -> std::fmt::Result {
1615 let mut xs = items.into_iter();
1616 let Some(x) = xs.next() else {
1617 return Ok(());
1618 };
1619 write!(f, "{x}")?;
1620 for x in xs {
1621 write!(f, "{sep}{x}")?;
1622 }
1623 Ok(())
1624}
1625
1626impl ProgrammableTransaction {
1627 pub fn input_objects(&self) -> UserInputResult<Vec<InputObjectKind>> {
1628 let ProgrammableTransaction { inputs, commands } = self;
1629 let input_arg_objects = inputs
1630 .iter()
1631 .flat_map(|arg| arg.input_objects())
1632 .collect::<Vec<_>>();
1633 let mut used = HashSet::new();
1635 if !input_arg_objects.iter().all(|o| used.insert(o.object_id())) {
1636 return Err(UserInputError::DuplicateObjectRefInput);
1637 }
1638 let command_input_objects: BTreeSet<InputObjectKind> = commands
1640 .iter()
1641 .flat_map(|command| command.input_objects())
1642 .collect();
1643 Ok(input_arg_objects
1644 .into_iter()
1645 .chain(command_input_objects)
1646 .collect())
1647 }
1648
1649 fn receiving_objects(&self) -> Vec<ObjectRef> {
1650 let ProgrammableTransaction { inputs, .. } = self;
1651 inputs
1652 .iter()
1653 .flat_map(|arg| arg.receiving_objects())
1654 .collect()
1655 }
1656
1657 fn validity_check(&self, config: &ProtocolConfig) -> UserInputResult {
1658 let ProgrammableTransaction { inputs, commands } = self;
1659 fp_ensure!(
1660 commands.len() < config.max_programmable_tx_commands() as usize,
1661 UserInputError::SizeLimitExceeded {
1662 limit: "maximum commands in a programmable transaction".to_string(),
1663 value: config.max_programmable_tx_commands().to_string()
1664 }
1665 );
1666 let total_inputs = self.input_objects()?.len() + self.receiving_objects().len();
1667 fp_ensure!(
1668 total_inputs <= config.max_input_objects() as usize,
1669 UserInputError::SizeLimitExceeded {
1670 limit: "maximum input + receiving objects in a transaction".to_string(),
1671 value: config.max_input_objects().to_string()
1672 }
1673 );
1674 for input in inputs {
1675 input.validity_check(config)?
1676 }
1677 if let Some(max_publish_commands) = config.max_publish_or_upgrade_per_ptb_as_option() {
1678 let publish_count = commands
1679 .iter()
1680 .filter(|c| matches!(c, Command::Publish(_, _) | Command::Upgrade(_, _, _, _)))
1681 .count() as u64;
1682 fp_ensure!(
1683 publish_count <= max_publish_commands,
1684 UserInputError::MaxPublishCountExceeded {
1685 max_publish_commands,
1686 publish_count,
1687 }
1688 );
1689 }
1690 for command in commands {
1691 command.validity_check(config)?;
1692 }
1693
1694 if let Some(random_index) = inputs.iter().position(|obj| {
1697 matches!(
1698 obj,
1699 CallArg::Object(ObjectArg::SharedObject { id, .. }) if *id == SUI_RANDOMNESS_STATE_OBJECT_ID
1700 )
1701 }) {
1702 fp_ensure!(
1703 config.random_beacon(),
1704 UserInputError::Unsupported(
1705 "randomness is not enabled on this network".to_string(),
1706 )
1707 );
1708 let mut used_random_object = false;
1709 let random_index = random_index.try_into().unwrap();
1710 for command in commands {
1711 if !used_random_object {
1712 used_random_object = command.is_input_arg_used(random_index);
1713 } else {
1714 fp_ensure!(
1715 matches!(
1716 command,
1717 Command::TransferObjects(_, _) | Command::MergeCoins(_, _)
1718 ),
1719 UserInputError::PostRandomCommandRestrictions
1720 );
1721 }
1722 }
1723 }
1724
1725 Ok(())
1726 }
1727
1728 pub fn coin_reservation_obj_refs(&self) -> impl Iterator<Item = ObjectRef> + '_ {
1730 self.inputs.iter().filter_map(|arg| match arg {
1731 CallArg::Object(ObjectArg::ImmOrOwnedObject(obj_ref))
1732 if ParsedDigest::is_coin_reservation_digest(&obj_ref.2) =>
1733 {
1734 Some(*obj_ref)
1735 }
1736 _ => None,
1737 })
1738 }
1739
1740 pub fn shared_input_objects(&self) -> impl Iterator<Item = SharedInputObject> + '_ {
1741 self.inputs.iter().filter_map(|arg| match arg {
1742 CallArg::Pure(_)
1743 | CallArg::Object(ObjectArg::Receiving(_))
1744 | CallArg::Object(ObjectArg::ImmOrOwnedObject(_))
1745 | CallArg::FundsWithdrawal(_) => None,
1746 CallArg::Object(ObjectArg::SharedObject {
1747 id,
1748 initial_shared_version,
1749 mutability,
1750 }) => Some(SharedInputObject {
1751 id: *id,
1752 initial_shared_version: *initial_shared_version,
1753 mutability: *mutability,
1754 }),
1755 })
1756 }
1757
1758 fn move_calls(&self) -> Vec<(usize, &ObjectID, &str, &str)> {
1759 self.commands
1760 .iter()
1761 .enumerate()
1762 .filter_map(|(idx, command)| match command {
1763 Command::MoveCall(m) => {
1764 Some((idx, &m.package, m.module.as_str(), m.function.as_str()))
1765 }
1766 _ => None,
1767 })
1768 .collect()
1769 }
1770
1771 pub fn non_system_packages_to_be_published(&self) -> impl Iterator<Item = &Vec<Vec<u8>>> + '_ {
1772 self.commands
1773 .iter()
1774 .filter_map(|q| q.non_system_packages_to_be_published())
1775 }
1776}
1777
1778impl Display for Argument {
1779 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
1780 match self {
1781 Argument::GasCoin => write!(f, "GasCoin"),
1782 Argument::Input(i) => write!(f, "Input({i})"),
1783 Argument::Result(i) => write!(f, "Result({i})"),
1784 Argument::NestedResult(i, j) => write!(f, "NestedResult({i},{j})"),
1785 }
1786 }
1787}
1788
1789impl Display for ProgrammableMoveCall {
1790 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
1791 let ProgrammableMoveCall {
1792 package,
1793 module,
1794 function,
1795 type_arguments,
1796 arguments,
1797 } = self;
1798 write!(f, "{package}::{module}::{function}")?;
1799 if !type_arguments.is_empty() {
1800 write!(f, "<")?;
1801 write_sep(f, type_arguments, ",")?;
1802 write!(f, ">")?;
1803 }
1804 write!(f, "(")?;
1805 write_sep(f, arguments, ",")?;
1806 write!(f, ")")
1807 }
1808}
1809
1810impl Display for Command {
1811 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
1812 match self {
1813 Command::MoveCall(p) => {
1814 write!(f, "MoveCall({p})")
1815 }
1816 Command::MakeMoveVec(ty_opt, elems) => {
1817 write!(f, "MakeMoveVec(")?;
1818 if let Some(ty) = ty_opt {
1819 write!(f, "Some{ty}")?;
1820 } else {
1821 write!(f, "None")?;
1822 }
1823 write!(f, ",[")?;
1824 write_sep(f, elems, ",")?;
1825 write!(f, "])")
1826 }
1827 Command::TransferObjects(objs, addr) => {
1828 write!(f, "TransferObjects([")?;
1829 write_sep(f, objs, ",")?;
1830 write!(f, "],{addr})")
1831 }
1832 Command::SplitCoins(coin, amounts) => {
1833 write!(f, "SplitCoins({coin}")?;
1834 write_sep(f, amounts, ",")?;
1835 write!(f, ")")
1836 }
1837 Command::MergeCoins(target, coins) => {
1838 write!(f, "MergeCoins({target},")?;
1839 write_sep(f, coins, ",")?;
1840 write!(f, ")")
1841 }
1842 Command::Publish(_bytes, deps) => {
1843 write!(f, "Publish(_,")?;
1844 write_sep(f, deps, ",")?;
1845 write!(f, ")")
1846 }
1847 Command::Upgrade(_bytes, deps, current_package_id, ticket) => {
1848 write!(f, "Upgrade(_,")?;
1849 write_sep(f, deps, ",")?;
1850 write!(f, ", {current_package_id}")?;
1851 write!(f, ", {ticket}")?;
1852 write!(f, ")")
1853 }
1854 }
1855 }
1856}
1857
1858impl Display for ProgrammableTransaction {
1859 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
1860 let ProgrammableTransaction { inputs, commands } = self;
1861 writeln!(f, "Inputs: {inputs:?}")?;
1862 writeln!(f, "Commands: [")?;
1863 for c in commands {
1864 writeln!(f, " {c},")?;
1865 }
1866 writeln!(f, "]")
1867 }
1868}
1869
1870#[derive(Debug, PartialEq, Eq)]
1871pub struct SharedInputObject {
1872 pub id: ObjectID,
1873 pub initial_shared_version: SequenceNumber,
1874 pub mutability: SharedObjectMutability,
1875}
1876
1877impl SharedInputObject {
1878 pub const SUI_SYSTEM_OBJ: Self = Self {
1879 id: SUI_SYSTEM_STATE_OBJECT_ID,
1880 initial_shared_version: SUI_SYSTEM_STATE_OBJECT_SHARED_VERSION,
1881 mutability: SharedObjectMutability::Mutable,
1882 };
1883
1884 pub fn id(&self) -> ObjectID {
1885 self.id
1886 }
1887
1888 pub fn id_and_version(&self) -> (ObjectID, SequenceNumber) {
1889 (self.id, self.initial_shared_version)
1890 }
1891
1892 pub fn into_id_and_version(self) -> (ObjectID, SequenceNumber) {
1893 (self.id, self.initial_shared_version)
1894 }
1895
1896 pub fn is_accessed_exclusively(&self) -> bool {
1897 self.mutability.is_exclusive()
1898 }
1899
1900 pub fn may_mutate(&self) -> bool {
1902 match self.mutability {
1903 SharedObjectMutability::Immutable => false,
1904 SharedObjectMutability::Mutable | SharedObjectMutability::NonExclusiveWrite => true,
1905 }
1906 }
1907}
1908
1909impl TransactionKind {
1910 pub fn programmable(pt: ProgrammableTransaction) -> Self {
1913 TransactionKind::ProgrammableTransaction(pt)
1914 }
1915
1916 pub fn is_system_tx(&self) -> bool {
1917 match self {
1919 TransactionKind::ChangeEpoch(_)
1920 | TransactionKind::Genesis(_)
1921 | TransactionKind::ConsensusCommitPrologue(_)
1922 | TransactionKind::ConsensusCommitPrologueV2(_)
1923 | TransactionKind::ConsensusCommitPrologueV3(_)
1924 | TransactionKind::ConsensusCommitPrologueV4(_)
1925 | TransactionKind::AuthenticatorStateUpdate(_)
1926 | TransactionKind::RandomnessStateUpdate(_)
1927 | TransactionKind::EndOfEpochTransaction(_)
1928 | TransactionKind::ProgrammableSystemTransaction(_) => true,
1929 TransactionKind::ProgrammableTransaction(_) => false,
1930 }
1931 }
1932
1933 pub fn is_end_of_epoch_tx(&self) -> bool {
1934 matches!(
1935 self,
1936 TransactionKind::EndOfEpochTransaction(_) | TransactionKind::ChangeEpoch(_)
1937 )
1938 }
1939
1940 pub fn mutates_implicitly_read_system_object(&self) -> bool {
1941 self.shared_input_objects()
1942 .any(|obj| obj.may_mutate() && obj.id.is_implicitly_read_system_object())
1943 }
1944
1945 pub fn is_accumulator_barrier_settle_tx(&self) -> bool {
1946 matches!(self, TransactionKind::ProgrammableSystemTransaction(_))
1947 && self.shared_input_objects().any(|obj| {
1948 obj.id == SUI_ACCUMULATOR_ROOT_OBJECT_ID
1949 && obj.mutability == SharedObjectMutability::Mutable
1950 })
1951 }
1952
1953 pub fn accumulator_barrier_settlement_key(&self) -> Option<TransactionKey> {
1957 let TransactionKind::ProgrammableSystemTransaction(pt) = self else {
1958 return None;
1959 };
1960 let has_mutable_acc_root = pt.inputs.iter().any(|input| {
1961 matches!(
1962 input,
1963 CallArg::Object(ObjectArg::SharedObject {
1964 id,
1965 mutability: SharedObjectMutability::Mutable,
1966 ..
1967 }) if *id == SUI_ACCUMULATOR_ROOT_OBJECT_ID
1968 )
1969 });
1970 if !has_mutable_acc_root {
1971 return None;
1972 }
1973 let epoch = pt.inputs.get(1).and_then(|arg| match arg {
1976 CallArg::Pure(bytes) => bcs::from_bytes::<u64>(bytes).ok(),
1977 _ => None,
1978 })?;
1979 let checkpoint_height = pt.inputs.get(2).and_then(|arg| match arg {
1980 CallArg::Pure(bytes) => bcs::from_bytes::<u64>(bytes).ok(),
1981 _ => None,
1982 })?;
1983 Some(TransactionKey::AccumulatorSettlement(
1984 epoch,
1985 checkpoint_height,
1986 ))
1987 }
1988
1989 pub fn get_advance_epoch_tx_gas_summary(&self) -> Option<(u64, u64)> {
1993 let e = match self {
1994 Self::ChangeEpoch(e) => e,
1995 Self::EndOfEpochTransaction(txns) => {
1996 if let EndOfEpochTransactionKind::ChangeEpoch(e) =
1997 txns.last().expect("at least one end-of-epoch txn required")
1998 {
1999 e
2000 } else {
2001 panic!("final end-of-epoch txn must be ChangeEpoch")
2002 }
2003 }
2004 _ => return None,
2005 };
2006
2007 Some((e.computation_charge + e.storage_charge, e.storage_rebate))
2008 }
2009
2010 pub fn shared_input_objects(&self) -> impl Iterator<Item = SharedInputObject> + '_ {
2013 match &self {
2014 Self::ChangeEpoch(_) => {
2015 Either::Left(Either::Left(iter::once(SharedInputObject::SUI_SYSTEM_OBJ)))
2016 }
2017
2018 Self::ConsensusCommitPrologue(_)
2019 | Self::ConsensusCommitPrologueV2(_)
2020 | Self::ConsensusCommitPrologueV3(_)
2021 | Self::ConsensusCommitPrologueV4(_) => {
2022 Either::Left(Either::Left(iter::once(SharedInputObject {
2023 id: SUI_CLOCK_OBJECT_ID,
2024 initial_shared_version: SUI_CLOCK_OBJECT_SHARED_VERSION,
2025 mutability: SharedObjectMutability::Mutable,
2026 })))
2027 }
2028 Self::AuthenticatorStateUpdate(update) => {
2029 Either::Left(Either::Left(iter::once(SharedInputObject {
2030 id: SUI_AUTHENTICATOR_STATE_OBJECT_ID,
2031 initial_shared_version: update.authenticator_obj_initial_shared_version,
2032 mutability: SharedObjectMutability::Mutable,
2033 })))
2034 }
2035 Self::RandomnessStateUpdate(update) => {
2036 Either::Left(Either::Left(iter::once(SharedInputObject {
2037 id: SUI_RANDOMNESS_STATE_OBJECT_ID,
2038 initial_shared_version: update.randomness_obj_initial_shared_version,
2039 mutability: SharedObjectMutability::Mutable,
2040 })))
2041 }
2042 Self::EndOfEpochTransaction(txns) => Either::Left(Either::Right(
2043 txns.iter().flat_map(|txn| txn.shared_input_objects()),
2044 )),
2045 Self::ProgrammableTransaction(pt) | Self::ProgrammableSystemTransaction(pt) => {
2046 Either::Right(Either::Left(pt.shared_input_objects()))
2047 }
2048 Self::Genesis(_) => Either::Right(Either::Right(iter::empty())),
2049 }
2050 }
2051
2052 fn move_calls(&self) -> Vec<(usize, &ObjectID, &str, &str)> {
2053 match &self {
2054 Self::ProgrammableTransaction(pt) => pt.move_calls(),
2055 _ => vec![],
2056 }
2057 }
2058
2059 pub fn receiving_objects(&self) -> Vec<ObjectRef> {
2060 match &self {
2061 TransactionKind::ChangeEpoch(_)
2062 | TransactionKind::Genesis(_)
2063 | TransactionKind::ConsensusCommitPrologue(_)
2064 | TransactionKind::ConsensusCommitPrologueV2(_)
2065 | TransactionKind::ConsensusCommitPrologueV3(_)
2066 | TransactionKind::ConsensusCommitPrologueV4(_)
2067 | TransactionKind::AuthenticatorStateUpdate(_)
2068 | TransactionKind::RandomnessStateUpdate(_)
2069 | TransactionKind::EndOfEpochTransaction(_)
2070 | TransactionKind::ProgrammableSystemTransaction(_) => vec![],
2071 TransactionKind::ProgrammableTransaction(pt) => pt.receiving_objects(),
2072 }
2073 }
2074
2075 pub fn input_objects(&self) -> UserInputResult<Vec<InputObjectKind>> {
2080 let input_objects = match &self {
2081 Self::ChangeEpoch(_) => {
2082 vec![InputObjectKind::SharedMoveObject {
2083 id: SUI_SYSTEM_STATE_OBJECT_ID,
2084 initial_shared_version: SUI_SYSTEM_STATE_OBJECT_SHARED_VERSION,
2085 mutability: SharedObjectMutability::Mutable,
2086 }]
2087 }
2088 Self::Genesis(_) => {
2089 vec![]
2090 }
2091 Self::ConsensusCommitPrologue(_)
2092 | Self::ConsensusCommitPrologueV2(_)
2093 | Self::ConsensusCommitPrologueV3(_)
2094 | Self::ConsensusCommitPrologueV4(_) => {
2095 vec![InputObjectKind::SharedMoveObject {
2096 id: SUI_CLOCK_OBJECT_ID,
2097 initial_shared_version: SUI_CLOCK_OBJECT_SHARED_VERSION,
2098 mutability: SharedObjectMutability::Mutable,
2099 }]
2100 }
2101 Self::AuthenticatorStateUpdate(update) => {
2102 vec![InputObjectKind::SharedMoveObject {
2103 id: SUI_AUTHENTICATOR_STATE_OBJECT_ID,
2104 initial_shared_version: update.authenticator_obj_initial_shared_version(),
2105 mutability: SharedObjectMutability::Mutable,
2106 }]
2107 }
2108 Self::RandomnessStateUpdate(update) => {
2109 vec![InputObjectKind::SharedMoveObject {
2110 id: SUI_RANDOMNESS_STATE_OBJECT_ID,
2111 initial_shared_version: update.randomness_obj_initial_shared_version(),
2112 mutability: SharedObjectMutability::Mutable,
2113 }]
2114 }
2115 Self::EndOfEpochTransaction(txns) => {
2116 let before_dedup: Vec<_> =
2119 txns.iter().flat_map(|txn| txn.input_objects()).collect();
2120 let mut has_seen = HashSet::new();
2121 let mut after_dedup = vec![];
2122 for obj in before_dedup {
2123 if has_seen.insert(obj) {
2124 after_dedup.push(obj);
2125 }
2126 }
2127 after_dedup
2128 }
2129 Self::ProgrammableTransaction(p) | Self::ProgrammableSystemTransaction(p) => {
2130 return p.input_objects();
2131 }
2132 };
2133 let mut used = HashSet::new();
2141 if !input_objects.iter().all(|o| used.insert(o.object_id())) {
2142 return Err(UserInputError::DuplicateObjectRefInput);
2143 }
2144 Ok(input_objects)
2145 }
2146
2147 pub fn get_funds_withdrawals<'a>(
2148 &'a self,
2149 ) -> impl Iterator<Item = &'a FundsWithdrawalArg> + 'a {
2150 let TransactionKind::ProgrammableTransaction(pt) = &self else {
2151 return Either::Left(iter::empty());
2152 };
2153 Either::Right(pt.inputs.iter().filter_map(|input| {
2154 if let CallArg::FundsWithdrawal(withdraw) = input {
2155 Some(withdraw)
2156 } else {
2157 None
2158 }
2159 }))
2160 }
2161
2162 pub fn get_coin_reservation_obj_refs(&self) -> impl Iterator<Item = ObjectRef> + '_ {
2163 let TransactionKind::ProgrammableTransaction(pt) = &self else {
2164 return Either::Left(iter::empty());
2165 };
2166 Either::Right(pt.coin_reservation_obj_refs())
2167 }
2168
2169 pub fn has_coin_reservations(&self) -> bool {
2170 self.get_coin_reservation_obj_refs().next().is_some()
2171 }
2172
2173 pub fn validity_check(&self, config: &ProtocolConfig) -> UserInputResult {
2174 match self {
2175 TransactionKind::ProgrammableTransaction(p) => p.validity_check(config)?,
2176 TransactionKind::ChangeEpoch(_)
2179 | TransactionKind::Genesis(_)
2180 | TransactionKind::ConsensusCommitPrologue(_) => (),
2181 TransactionKind::ConsensusCommitPrologueV2(_) => {
2182 if !config.include_consensus_digest_in_prologue() {
2183 return Err(UserInputError::Unsupported(
2184 "ConsensusCommitPrologueV2 is not supported".to_string(),
2185 ));
2186 }
2187 }
2188 TransactionKind::ConsensusCommitPrologueV3(_) => {
2189 if !config.record_consensus_determined_version_assignments_in_prologue() {
2190 return Err(UserInputError::Unsupported(
2191 "ConsensusCommitPrologueV3 is not supported".to_string(),
2192 ));
2193 }
2194 }
2195 TransactionKind::ConsensusCommitPrologueV4(_) => {
2196 if !config.record_additional_state_digest_in_prologue() {
2197 return Err(UserInputError::Unsupported(
2198 "ConsensusCommitPrologueV4 is not supported".to_string(),
2199 ));
2200 }
2201 }
2202 TransactionKind::EndOfEpochTransaction(txns) => {
2203 if !config.end_of_epoch_transaction_supported() {
2204 return Err(UserInputError::Unsupported(
2205 "EndOfEpochTransaction is not supported".to_string(),
2206 ));
2207 }
2208
2209 for tx in txns {
2210 tx.validity_check(config)?;
2211 }
2212 }
2213
2214 TransactionKind::AuthenticatorStateUpdate(_) => {
2215 if !config.enable_jwk_consensus_updates() {
2216 return Err(UserInputError::Unsupported(
2217 "authenticator state updates not enabled".to_string(),
2218 ));
2219 }
2220 }
2221 TransactionKind::RandomnessStateUpdate(_) => {
2222 if !config.random_beacon() {
2223 return Err(UserInputError::Unsupported(
2224 "randomness state updates not enabled".to_string(),
2225 ));
2226 }
2227 }
2228 TransactionKind::ProgrammableSystemTransaction(_) => {
2229 if !config.enable_accumulators() {
2230 return Err(UserInputError::Unsupported(
2231 "accumulators not enabled".to_string(),
2232 ));
2233 }
2234 }
2235 };
2236 Ok(())
2237 }
2238
2239 pub fn num_commands(&self) -> usize {
2241 match self {
2242 TransactionKind::ProgrammableTransaction(pt) => pt.commands.len(),
2243 _ => 0,
2244 }
2245 }
2246
2247 pub fn iter_commands(&self) -> impl Iterator<Item = &Command> {
2248 match self {
2249 TransactionKind::ProgrammableTransaction(pt) => pt.commands.iter(),
2250 _ => [].iter(),
2251 }
2252 }
2253
2254 pub fn tx_count(&self) -> usize {
2256 match self {
2257 TransactionKind::ProgrammableTransaction(pt) => pt.commands.len(),
2258 _ => 1,
2259 }
2260 }
2261
2262 pub fn name(&self) -> &'static str {
2263 match self {
2264 Self::ChangeEpoch(_) => "ChangeEpoch",
2265 Self::Genesis(_) => "Genesis",
2266 Self::ConsensusCommitPrologue(_) => "ConsensusCommitPrologue",
2267 Self::ConsensusCommitPrologueV2(_) => "ConsensusCommitPrologueV2",
2268 Self::ConsensusCommitPrologueV3(_) => "ConsensusCommitPrologueV3",
2269 Self::ConsensusCommitPrologueV4(_) => "ConsensusCommitPrologueV4",
2270 Self::ProgrammableTransaction(_) => "ProgrammableTransaction",
2271 Self::ProgrammableSystemTransaction(_) => "ProgrammableSystemTransaction",
2272 Self::AuthenticatorStateUpdate(_) => "AuthenticatorStateUpdate",
2273 Self::RandomnessStateUpdate(_) => "RandomnessStateUpdate",
2274 Self::EndOfEpochTransaction(_) => "EndOfEpochTransaction",
2275 }
2276 }
2277}
2278
2279impl Display for TransactionKind {
2280 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
2281 let mut writer = String::new();
2282 match &self {
2283 Self::ChangeEpoch(e) => {
2284 writeln!(writer, "Transaction Kind : Epoch Change")?;
2285 writeln!(writer, "New epoch ID : {}", e.epoch)?;
2286 writeln!(writer, "Storage gas reward : {}", e.storage_charge)?;
2287 writeln!(writer, "Computation gas reward : {}", e.computation_charge)?;
2288 writeln!(writer, "Storage rebate : {}", e.storage_rebate)?;
2289 writeln!(writer, "Timestamp : {}", e.epoch_start_timestamp_ms)?;
2290 }
2291 Self::Genesis(_) => {
2292 writeln!(writer, "Transaction Kind : Genesis")?;
2293 }
2294 Self::ConsensusCommitPrologue(p) => {
2295 writeln!(writer, "Transaction Kind : Consensus Commit Prologue")?;
2296 writeln!(writer, "Timestamp : {}", p.commit_timestamp_ms)?;
2297 }
2298 Self::ConsensusCommitPrologueV2(p) => {
2299 writeln!(writer, "Transaction Kind : Consensus Commit Prologue V2")?;
2300 writeln!(writer, "Timestamp : {}", p.commit_timestamp_ms)?;
2301 writeln!(writer, "Consensus Digest: {}", p.consensus_commit_digest)?;
2302 }
2303 Self::ConsensusCommitPrologueV3(p) => {
2304 writeln!(writer, "Transaction Kind : Consensus Commit Prologue V3")?;
2305 writeln!(writer, "Timestamp : {}", p.commit_timestamp_ms)?;
2306 writeln!(writer, "Consensus Digest: {}", p.consensus_commit_digest)?;
2307 writeln!(
2308 writer,
2309 "Consensus determined version assignment: {:?}",
2310 p.consensus_determined_version_assignments
2311 )?;
2312 }
2313 Self::ConsensusCommitPrologueV4(p) => {
2314 writeln!(writer, "Transaction Kind : Consensus Commit Prologue V4")?;
2315 writeln!(writer, "Timestamp : {}", p.commit_timestamp_ms)?;
2316 writeln!(writer, "Consensus Digest: {}", p.consensus_commit_digest)?;
2317 writeln!(
2318 writer,
2319 "Consensus determined version assignment: {:?}",
2320 p.consensus_determined_version_assignments
2321 )?;
2322 writeln!(
2323 writer,
2324 "Additional State Digest: {}",
2325 p.additional_state_digest
2326 )?;
2327 }
2328 Self::ProgrammableTransaction(p) => {
2329 writeln!(writer, "Transaction Kind : Programmable")?;
2330 write!(writer, "{p}")?;
2331 }
2332 Self::ProgrammableSystemTransaction(p) => {
2333 writeln!(writer, "Transaction Kind : Programmable System")?;
2334 write!(writer, "{p}")?;
2335 }
2336 Self::AuthenticatorStateUpdate(_) => {
2337 writeln!(writer, "Transaction Kind : Authenticator State Update")?;
2338 }
2339 Self::RandomnessStateUpdate(_) => {
2340 writeln!(writer, "Transaction Kind : Randomness State Update")?;
2341 }
2342 Self::EndOfEpochTransaction(_) => {
2343 writeln!(writer, "Transaction Kind : End of Epoch Transaction")?;
2344 }
2345 }
2346 write!(f, "{}", writer)
2347 }
2348}
2349
2350#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)]
2351pub struct GasData {
2352 pub payment: Vec<ObjectRef>,
2353 pub owner: SuiAddress,
2354 pub price: u64,
2355 pub budget: u64,
2356}
2357
2358impl GasData {
2359 pub fn is_unmetered(&self) -> bool {
2360 self.payment.len() == 1
2361 && self.payment[0].0 == ObjectID::ZERO
2362 && self.payment[0].1 == SequenceNumber::default()
2363 && self.payment[0].2 == ObjectDigest::MIN
2364 }
2365}
2366
2367pub fn is_gas_paid_from_address_balance(
2368 gas_data: &GasData,
2369 transaction_kind: &TransactionKind,
2370) -> bool {
2371 gas_data.payment.is_empty()
2372 && matches!(
2373 transaction_kind,
2374 TransactionKind::ProgrammableTransaction(_)
2375 )
2376}
2377
2378pub fn is_gasless_transaction(gas_data: &GasData, transaction_kind: &TransactionKind) -> bool {
2379 is_gas_paid_from_address_balance(gas_data, transaction_kind) && gas_data.price == 0
2380}
2381
2382#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)]
2383pub enum TransactionExpiration {
2384 None,
2386 Epoch(EpochId),
2389 ValidDuring {
2399 min_epoch: Option<EpochId>,
2401 max_epoch: Option<EpochId>,
2403 min_timestamp: Option<u64>,
2405 max_timestamp: Option<u64>,
2407 chain: ChainIdentifier,
2409 nonce: u32,
2411 },
2412 Validity {
2415 min_epoch: Option<EpochId>,
2417 max_epoch: Option<EpochId>,
2419 min_timestamp: Option<u64>,
2421 max_timestamp: Option<u64>,
2423 chain: ChainIdentifier,
2425 nonce: u32,
2427 allowed_proposers: Option<AllowedProposers>,
2429 },
2430}
2431
2432#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)]
2435pub struct AllowedProposers {
2436 pub epoch: EpochId,
2440 #[serde(with = "nonempty_as_vec")]
2445 pub proposers: NonEmpty<u32>,
2446}
2447
2448pub const MAX_UNPAID_ALLOWED_PROPOSERS: u64 = 3;
2454
2455impl TransactionExpiration {
2456 pub fn is_replay_protected(&self) -> bool {
2461 matches!(
2462 self,
2463 TransactionExpiration::ValidDuring {
2464 min_epoch: Some(min_epoch),
2465 max_epoch: Some(max_epoch),
2466 ..
2467 }
2468 | TransactionExpiration::Validity {
2469 min_epoch: Some(min_epoch),
2470 max_epoch: Some(max_epoch),
2471 ..
2472 } if *max_epoch == *min_epoch || *max_epoch == min_epoch.saturating_add(1)
2473 )
2474 }
2475
2476 pub fn allowed_proposers(&self, epoch: EpochId) -> Option<&AllowedProposers> {
2484 match self {
2485 TransactionExpiration::Validity {
2486 allowed_proposers: Some(allowed),
2487 ..
2488 } if allowed.epoch == epoch => Some(allowed),
2489 _ => None,
2490 }
2491 }
2492
2493 pub fn restricts_proposers(&self, epoch: EpochId) -> bool {
2496 self.allowed_proposers(epoch).is_some()
2497 }
2498
2499 pub fn is_allowed_proposer(&self, proposer: u32, epoch: EpochId) -> bool {
2502 self.allowed_proposers(epoch).is_none_or(|allowed| {
2503 allowed
2507 .proposers
2508 .iter()
2509 .find(|p| **p >= proposer)
2510 .is_some_and(|p| *p == proposer)
2511 })
2512 }
2513}
2514
2515#[enum_dispatch(TransactionDataAPI)]
2516#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)]
2517pub enum TransactionData {
2518 V1(TransactionDataV1),
2519 }
2522
2523#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)]
2524pub struct TransactionDataV1 {
2525 pub kind: TransactionKind,
2526 pub sender: SuiAddress,
2527 pub gas_data: GasData,
2528 pub expiration: TransactionExpiration,
2529}
2530
2531impl TransactionData {
2532 pub fn as_v1(&self) -> &TransactionDataV1 {
2533 match self {
2534 TransactionData::V1(v1) => v1,
2535 }
2536 }
2537 fn new_system_transaction(kind: TransactionKind) -> Self {
2538 assert!(kind.is_system_tx());
2540 let sender = SuiAddress::default();
2541 TransactionData::V1(TransactionDataV1 {
2542 kind,
2543 sender,
2544 gas_data: GasData {
2545 price: GAS_PRICE_FOR_SYSTEM_TX,
2546 owner: sender,
2547 payment: vec![(ObjectID::ZERO, SequenceNumber::default(), ObjectDigest::MIN)],
2548 budget: 0,
2549 },
2550 expiration: TransactionExpiration::None,
2551 })
2552 }
2553
2554 pub fn new(
2555 kind: TransactionKind,
2556 sender: SuiAddress,
2557 gas_payment: ObjectRef,
2558 gas_budget: u64,
2559 gas_price: u64,
2560 ) -> Self {
2561 TransactionData::V1(TransactionDataV1 {
2562 kind,
2563 sender,
2564 gas_data: GasData {
2565 price: gas_price,
2566 owner: sender,
2567 payment: vec![gas_payment],
2568 budget: gas_budget,
2569 },
2570 expiration: TransactionExpiration::None,
2571 })
2572 }
2573
2574 pub fn new_with_gas_coins(
2575 kind: TransactionKind,
2576 sender: SuiAddress,
2577 gas_payment: Vec<ObjectRef>,
2578 gas_budget: u64,
2579 gas_price: u64,
2580 ) -> Self {
2581 Self::new_with_gas_coins_allow_sponsor(
2582 kind,
2583 sender,
2584 gas_payment,
2585 gas_budget,
2586 gas_price,
2587 sender,
2588 )
2589 }
2590
2591 pub fn new_with_gas_coins_allow_sponsor(
2592 kind: TransactionKind,
2593 sender: SuiAddress,
2594 gas_payment: Vec<ObjectRef>,
2595 gas_budget: u64,
2596 gas_price: u64,
2597 gas_sponsor: SuiAddress,
2598 ) -> Self {
2599 TransactionData::V1(TransactionDataV1 {
2600 kind,
2601 sender,
2602 gas_data: GasData {
2603 price: gas_price,
2604 owner: gas_sponsor,
2605 payment: gas_payment,
2606 budget: gas_budget,
2607 },
2608 expiration: TransactionExpiration::None,
2609 })
2610 }
2611
2612 pub fn new_with_gas_data(kind: TransactionKind, sender: SuiAddress, gas_data: GasData) -> Self {
2613 TransactionData::V1(TransactionDataV1 {
2614 kind,
2615 sender,
2616 gas_data,
2617 expiration: TransactionExpiration::None,
2618 })
2619 }
2620
2621 pub fn new_with_gas_data_and_expiration(
2622 kind: TransactionKind,
2623 sender: SuiAddress,
2624 gas_data: GasData,
2625 expiration: TransactionExpiration,
2626 ) -> Self {
2627 TransactionData::V1(TransactionDataV1 {
2628 kind,
2629 sender,
2630 gas_data,
2631 expiration,
2632 })
2633 }
2634
2635 pub fn new_move_call(
2636 sender: SuiAddress,
2637 package: ObjectID,
2638 module: Identifier,
2639 function: Identifier,
2640 type_arguments: Vec<TypeTag>,
2641 gas_payment: ObjectRef,
2642 arguments: Vec<CallArg>,
2643 gas_budget: u64,
2644 gas_price: u64,
2645 ) -> anyhow::Result<Self> {
2646 Self::new_move_call_with_gas_coins(
2647 sender,
2648 package,
2649 module,
2650 function,
2651 type_arguments,
2652 vec![gas_payment],
2653 arguments,
2654 gas_budget,
2655 gas_price,
2656 )
2657 }
2658
2659 pub fn new_move_call_with_gas_coins(
2660 sender: SuiAddress,
2661 package: ObjectID,
2662 module: Identifier,
2663 function: Identifier,
2664 type_arguments: Vec<TypeTag>,
2665 gas_payment: Vec<ObjectRef>,
2666 arguments: Vec<CallArg>,
2667 gas_budget: u64,
2668 gas_price: u64,
2669 ) -> anyhow::Result<Self> {
2670 let pt = {
2671 let mut builder = ProgrammableTransactionBuilder::new();
2672 builder.move_call(package, module, function, type_arguments, arguments)?;
2673 builder.finish()
2674 };
2675 Ok(Self::new_programmable(
2676 sender,
2677 gas_payment,
2678 pt,
2679 gas_budget,
2680 gas_price,
2681 ))
2682 }
2683
2684 pub fn new_transfer(
2685 recipient: SuiAddress,
2686 full_object_ref: FullObjectRef,
2687 sender: SuiAddress,
2688 gas_payment: ObjectRef,
2689 gas_budget: u64,
2690 gas_price: u64,
2691 ) -> Self {
2692 let pt = {
2693 let mut builder = ProgrammableTransactionBuilder::new();
2694 builder.transfer_object(recipient, full_object_ref).unwrap();
2695 builder.finish()
2696 };
2697 Self::new_programmable(sender, vec![gas_payment], pt, gas_budget, gas_price)
2698 }
2699
2700 pub fn new_transfer_sui(
2701 recipient: SuiAddress,
2702 sender: SuiAddress,
2703 amount: Option<u64>,
2704 gas_payment: ObjectRef,
2705 gas_budget: u64,
2706 gas_price: u64,
2707 ) -> Self {
2708 Self::new_transfer_sui_allow_sponsor(
2709 recipient,
2710 sender,
2711 amount,
2712 gas_payment,
2713 gas_budget,
2714 gas_price,
2715 sender,
2716 )
2717 }
2718
2719 pub fn new_transfer_sui_allow_sponsor(
2720 recipient: SuiAddress,
2721 sender: SuiAddress,
2722 amount: Option<u64>,
2723 gas_payment: ObjectRef,
2724 gas_budget: u64,
2725 gas_price: u64,
2726 gas_sponsor: SuiAddress,
2727 ) -> Self {
2728 let pt = {
2729 let mut builder = ProgrammableTransactionBuilder::new();
2730 builder.transfer_sui(recipient, amount);
2731 builder.finish()
2732 };
2733 Self::new_programmable_allow_sponsor(
2734 sender,
2735 vec![gas_payment],
2736 pt,
2737 gas_budget,
2738 gas_price,
2739 gas_sponsor,
2740 )
2741 }
2742
2743 pub fn new_pay(
2744 sender: SuiAddress,
2745 coins: Vec<ObjectRef>,
2746 recipients: Vec<SuiAddress>,
2747 amounts: Vec<u64>,
2748 gas_payment: ObjectRef,
2749 gas_budget: u64,
2750 gas_price: u64,
2751 ) -> anyhow::Result<Self> {
2752 let pt = {
2753 let mut builder = ProgrammableTransactionBuilder::new();
2754 builder.pay(coins, recipients, amounts)?;
2755 builder.finish()
2756 };
2757 Ok(Self::new_programmable(
2758 sender,
2759 vec![gas_payment],
2760 pt,
2761 gas_budget,
2762 gas_price,
2763 ))
2764 }
2765
2766 pub fn new_pay_sui(
2767 sender: SuiAddress,
2768 mut coins: Vec<ObjectRef>,
2769 recipients: Vec<SuiAddress>,
2770 amounts: Vec<u64>,
2771 gas_payment: ObjectRef,
2772 gas_budget: u64,
2773 gas_price: u64,
2774 ) -> anyhow::Result<Self> {
2775 coins.insert(0, gas_payment);
2776 let pt = {
2777 let mut builder = ProgrammableTransactionBuilder::new();
2778 builder.pay_sui(recipients, amounts)?;
2779 builder.finish()
2780 };
2781 Ok(Self::new_programmable(
2782 sender, coins, pt, gas_budget, gas_price,
2783 ))
2784 }
2785
2786 pub fn new_pay_all_sui(
2787 sender: SuiAddress,
2788 mut coins: Vec<ObjectRef>,
2789 recipient: SuiAddress,
2790 gas_payment: ObjectRef,
2791 gas_budget: u64,
2792 gas_price: u64,
2793 ) -> Self {
2794 coins.insert(0, gas_payment);
2795 let pt = {
2796 let mut builder = ProgrammableTransactionBuilder::new();
2797 builder.pay_all_sui(recipient);
2798 builder.finish()
2799 };
2800 Self::new_programmable(sender, coins, pt, gas_budget, gas_price)
2801 }
2802
2803 pub fn new_split_coin(
2804 sender: SuiAddress,
2805 coin: ObjectRef,
2806 amounts: Vec<u64>,
2807 gas_payment: ObjectRef,
2808 gas_budget: u64,
2809 gas_price: u64,
2810 ) -> Self {
2811 let pt = {
2812 let mut builder = ProgrammableTransactionBuilder::new();
2813 builder.split_coin(sender, coin, amounts);
2814 builder.finish()
2815 };
2816 Self::new_programmable(sender, vec![gas_payment], pt, gas_budget, gas_price)
2817 }
2818
2819 pub fn new_module(
2820 sender: SuiAddress,
2821 gas_payment: ObjectRef,
2822 modules: Vec<Vec<u8>>,
2823 dep_ids: Vec<ObjectID>,
2824 gas_budget: u64,
2825 gas_price: u64,
2826 ) -> Self {
2827 let pt = {
2828 let mut builder = ProgrammableTransactionBuilder::new();
2829 let upgrade_cap = builder.publish_upgradeable(modules, dep_ids);
2830 builder.transfer_arg(sender, upgrade_cap);
2831 builder.finish()
2832 };
2833 Self::new_programmable(sender, vec![gas_payment], pt, gas_budget, gas_price)
2834 }
2835
2836 pub fn new_upgrade(
2837 sender: SuiAddress,
2838 gas_payment: ObjectRef,
2839 package_id: ObjectID,
2840 modules: Vec<Vec<u8>>,
2841 dep_ids: Vec<ObjectID>,
2842 (upgrade_capability, capability_owner): (ObjectRef, Owner),
2843 upgrade_policy: u8,
2844 digest: Vec<u8>,
2845 gas_budget: u64,
2846 gas_price: u64,
2847 ) -> anyhow::Result<Self> {
2848 let pt = {
2849 let mut builder = ProgrammableTransactionBuilder::new();
2850 let capability_arg = match capability_owner {
2851 Owner::AddressOwner(_) => ObjectArg::ImmOrOwnedObject(upgrade_capability),
2852 Owner::Shared {
2853 initial_shared_version,
2854 }
2855 | Owner::ConsensusAddressOwner {
2856 start_version: initial_shared_version,
2857 ..
2858 }
2859 | Owner::Party {
2860 start_version: initial_shared_version,
2861 ..
2862 } => ObjectArg::SharedObject {
2863 id: upgrade_capability.0,
2864 initial_shared_version,
2865 mutability: SharedObjectMutability::Mutable,
2866 },
2867 Owner::Immutable => {
2868 return Err(anyhow::anyhow!(
2869 "Upgrade capability is stored immutably and cannot be used for upgrades"
2870 ));
2871 }
2872 Owner::ObjectOwner(_) => {
2875 return Err(anyhow::anyhow!("Upgrade capability controlled by object"));
2876 }
2877 };
2878 builder.obj(capability_arg).unwrap();
2879 let upgrade_arg = builder.pure(upgrade_policy).unwrap();
2880 let digest_arg = builder.pure(digest).unwrap();
2881 let upgrade_ticket = builder.programmable_move_call(
2882 SUI_FRAMEWORK_PACKAGE_ID,
2883 ident_str!("package").to_owned(),
2884 ident_str!("authorize_upgrade").to_owned(),
2885 vec![],
2886 vec![Argument::Input(0), upgrade_arg, digest_arg],
2887 );
2888 let upgrade_receipt = builder.upgrade(package_id, upgrade_ticket, dep_ids, modules);
2889
2890 builder.programmable_move_call(
2891 SUI_FRAMEWORK_PACKAGE_ID,
2892 ident_str!("package").to_owned(),
2893 ident_str!("commit_upgrade").to_owned(),
2894 vec![],
2895 vec![Argument::Input(0), upgrade_receipt],
2896 );
2897
2898 builder.finish()
2899 };
2900 Ok(Self::new_programmable(
2901 sender,
2902 vec![gas_payment],
2903 pt,
2904 gas_budget,
2905 gas_price,
2906 ))
2907 }
2908
2909 pub fn new_programmable(
2910 sender: SuiAddress,
2911 gas_payment: Vec<ObjectRef>,
2912 pt: ProgrammableTransaction,
2913 gas_budget: u64,
2914 gas_price: u64,
2915 ) -> Self {
2916 Self::new_programmable_allow_sponsor(sender, gas_payment, pt, gas_budget, gas_price, sender)
2917 }
2918
2919 pub fn new_programmable_allow_sponsor(
2920 sender: SuiAddress,
2921 gas_payment: Vec<ObjectRef>,
2922 pt: ProgrammableTransaction,
2923 gas_budget: u64,
2924 gas_price: u64,
2925 sponsor: SuiAddress,
2926 ) -> Self {
2927 let kind = TransactionKind::ProgrammableTransaction(pt);
2928 Self::new_with_gas_coins_allow_sponsor(
2929 kind,
2930 sender,
2931 gas_payment,
2932 gas_budget,
2933 gas_price,
2934 sponsor,
2935 )
2936 }
2937
2938 pub fn new_programmable_with_address_balance_gas(
2939 sender: SuiAddress,
2940 pt: ProgrammableTransaction,
2941 gas_budget: u64,
2942 gas_price: u64,
2943 chain_identifier: ChainIdentifier,
2944 current_epoch: EpochId,
2945 nonce: u32,
2946 ) -> Self {
2947 TransactionData::V1(TransactionDataV1 {
2948 kind: TransactionKind::ProgrammableTransaction(pt),
2949 sender,
2950 gas_data: GasData {
2951 payment: vec![],
2952 owner: sender,
2953 price: gas_price,
2954 budget: gas_budget,
2955 },
2956 expiration: TransactionExpiration::ValidDuring {
2957 min_epoch: Some(current_epoch),
2958 max_epoch: Some(current_epoch + 1),
2959 min_timestamp: None,
2960 max_timestamp: None,
2961 chain: chain_identifier,
2962 nonce,
2963 },
2964 })
2965 }
2966
2967 pub fn message_version(&self) -> u64 {
2968 match self {
2969 TransactionData::V1(_) => 1,
2970 }
2971 }
2972
2973 pub fn execution_parts(&self) -> (TransactionKind, SuiAddress, GasData) {
2974 (self.kind().clone(), self.sender(), self.gas_data().clone())
2975 }
2976
2977 pub fn uses_randomness(&self) -> bool {
2978 self.kind()
2979 .shared_input_objects()
2980 .any(|obj| obj.id() == SUI_RANDOMNESS_STATE_OBJECT_ID)
2981 }
2982
2983 pub fn digest(&self) -> TransactionDigest {
2984 TransactionDigest::new(default_hash(self))
2985 }
2986}
2987
2988#[enum_dispatch]
2989pub trait TransactionDataAPI {
2990 fn sender(&self) -> SuiAddress;
2991
2992 fn kind(&self) -> &TransactionKind;
2995
2996 fn kind_mut(&mut self) -> &mut TransactionKind;
2998
2999 fn into_kind(self) -> TransactionKind;
3001
3002 fn required_signers(&self) -> NonEmpty<SuiAddress>;
3004
3005 fn gas_data(&self) -> &GasData;
3006
3007 fn gas_owner(&self) -> SuiAddress;
3008
3009 fn gas(&self) -> &[ObjectRef];
3010
3011 fn gas_price(&self) -> u64;
3012
3013 fn gas_budget(&self) -> u64;
3014
3015 fn expiration(&self) -> &TransactionExpiration;
3016
3017 fn expiration_mut(&mut self) -> &mut TransactionExpiration;
3018
3019 fn move_calls(&self) -> Vec<(usize, &ObjectID, &str, &str)>;
3020
3021 fn input_objects(&self) -> UserInputResult<Vec<InputObjectKind>>;
3022
3023 fn shared_input_objects(&self) -> Vec<SharedInputObject>;
3024
3025 fn receiving_objects(&self) -> Vec<ObjectRef>;
3026
3027 fn fastpath_dependency_objects(
3031 &self,
3032 ) -> UserInputResult<(Vec<ObjectRef>, Vec<ObjectID>, Vec<ObjectRef>)>;
3033
3034 fn process_funds_withdrawals_for_signing(
3042 &self,
3043 chain_identifier: ChainIdentifier,
3044 coin_resolver: &dyn CoinReservationResolverTrait,
3045 ) -> UserInputResult<BTreeMap<AccumulatorObjId, (u64, TypeTag, SuiAddress)>>;
3046
3047 fn process_funds_withdrawals_for_estimation(
3051 &self,
3052 chain_identifier: ChainIdentifier,
3053 coin_resolver: &dyn CoinReservationResolverTrait,
3054 ) -> UserInputResult<BTreeMap<AccumulatorObjId, (u64, TypeTag, SuiAddress)>>;
3055
3056 fn process_funds_withdrawals_for_execution(
3059 &self,
3060 chain_identifier: ChainIdentifier,
3061 ) -> BTreeMap<AccumulatorObjId, u64>;
3062
3063 fn check_allowance_inputs(&self, input_objects: &InputObjects) -> UserInputResult<()>;
3067
3068 fn has_funds_withdrawals(&self) -> bool;
3070
3071 fn coin_reservation_obj_refs(
3072 &self,
3073 chain_identifier: ChainIdentifier,
3074 ) -> Vec<ParsedObjectRefWithdrawal>;
3075
3076 fn validity_check(&self, context: &TxValidityCheckContext<'_>) -> SuiResult;
3077
3078 fn check_sponsorship(&self) -> UserInputResult;
3080
3081 fn is_system_tx(&self) -> bool;
3082 fn is_genesis_tx(&self) -> bool;
3083
3084 fn is_end_of_epoch_tx(&self) -> bool;
3087
3088 fn is_consensus_commit_prologue(&self) -> bool;
3089
3090 fn is_sponsored_tx(&self) -> bool;
3092
3093 fn is_gas_paid_from_address_balance(&self) -> bool;
3094
3095 fn is_gasless_transaction(&self) -> bool;
3096
3097 fn sender_mut_for_testing(&mut self) -> &mut SuiAddress;
3098
3099 fn gas_data_mut(&mut self) -> &mut GasData;
3100
3101 fn expiration_mut_for_testing(&mut self) -> &mut TransactionExpiration;
3103}
3104
3105impl TransactionDataAPI for TransactionDataV1 {
3106 fn sender(&self) -> SuiAddress {
3107 self.sender
3108 }
3109
3110 fn kind(&self) -> &TransactionKind {
3111 &self.kind
3112 }
3113
3114 fn kind_mut(&mut self) -> &mut TransactionKind {
3115 &mut self.kind
3116 }
3117
3118 fn into_kind(self) -> TransactionKind {
3119 self.kind
3120 }
3121
3122 fn required_signers(&self) -> NonEmpty<SuiAddress> {
3124 let mut signers = nonempty![self.sender];
3125 if self.gas_owner() != self.sender {
3126 signers.push(self.gas_owner());
3127 }
3128 signers
3129 }
3130
3131 fn gas_data(&self) -> &GasData {
3132 &self.gas_data
3133 }
3134
3135 fn gas_owner(&self) -> SuiAddress {
3136 self.gas_data.owner
3137 }
3138
3139 fn gas(&self) -> &[ObjectRef] {
3140 &self.gas_data.payment
3141 }
3142
3143 fn gas_price(&self) -> u64 {
3144 self.gas_data.price
3145 }
3146
3147 fn gas_budget(&self) -> u64 {
3148 self.gas_data.budget
3149 }
3150
3151 fn expiration(&self) -> &TransactionExpiration {
3152 &self.expiration
3153 }
3154
3155 fn expiration_mut(&mut self) -> &mut TransactionExpiration {
3156 &mut self.expiration
3157 }
3158
3159 fn move_calls(&self) -> Vec<(usize, &ObjectID, &str, &str)> {
3160 self.kind.move_calls()
3161 }
3162
3163 fn input_objects(&self) -> UserInputResult<Vec<InputObjectKind>> {
3164 let mut inputs = self.kind.input_objects()?;
3165
3166 if !self.kind.is_system_tx() {
3167 inputs.extend(
3168 self.gas()
3169 .iter()
3170 .filter(|obj_ref| !ParsedDigest::is_coin_reservation_digest(&obj_ref.2))
3171 .map(|obj_ref| InputObjectKind::ImmOrOwnedMoveObject(*obj_ref)),
3172 );
3173 }
3174 Ok(inputs)
3175 }
3176
3177 fn shared_input_objects(&self) -> Vec<SharedInputObject> {
3178 self.kind.shared_input_objects().collect()
3179 }
3180
3181 fn receiving_objects(&self) -> Vec<ObjectRef> {
3182 self.kind.receiving_objects()
3183 }
3184
3185 fn fastpath_dependency_objects(
3186 &self,
3187 ) -> UserInputResult<(Vec<ObjectRef>, Vec<ObjectID>, Vec<ObjectRef>)> {
3188 let mut move_objects = vec![];
3189 let mut packages = vec![];
3190 let mut receiving_objects = vec![];
3191 self.input_objects()?.iter().for_each(|o| match o {
3192 InputObjectKind::ImmOrOwnedMoveObject(object_ref) => {
3193 move_objects.push(*object_ref);
3194 }
3195 InputObjectKind::MovePackage(package_id) => {
3196 packages.push(*package_id);
3197 }
3198 InputObjectKind::SharedMoveObject { .. } => {}
3199 });
3200 self.receiving_objects().iter().for_each(|object_ref| {
3201 receiving_objects.push(*object_ref);
3202 });
3203 Ok((move_objects, packages, receiving_objects))
3204 }
3205
3206 fn process_funds_withdrawals_for_signing(
3207 &self,
3208 chain_identifier: ChainIdentifier,
3209 coin_resolver: &dyn CoinReservationResolverTrait,
3210 ) -> UserInputResult<BTreeMap<AccumulatorObjId, (u64, TypeTag, SuiAddress)>> {
3211 self.accumulate_funds_withdrawals(chain_identifier, coin_resolver, true)
3212 }
3213
3214 fn process_funds_withdrawals_for_estimation(
3215 &self,
3216 chain_identifier: ChainIdentifier,
3217 coin_resolver: &dyn CoinReservationResolverTrait,
3218 ) -> UserInputResult<BTreeMap<AccumulatorObjId, (u64, TypeTag, SuiAddress)>> {
3219 self.accumulate_funds_withdrawals(chain_identifier, coin_resolver, false)
3220 }
3221
3222 fn process_funds_withdrawals_for_execution(
3223 &self,
3224 chain_identifier: ChainIdentifier,
3225 ) -> BTreeMap<AccumulatorObjId, u64> {
3226 let mut withdraws: Vec<_> = self.get_funds_withdrawals().collect();
3227 withdraws.extend(self.get_funds_withdrawal_for_gas_payment());
3228
3229 let mut withdraw_map: BTreeMap<AccumulatorObjId, u64> = BTreeMap::new();
3231 for withdraw in withdraws {
3232 let reserved_amount = match &withdraw.reservation {
3233 Reservation::MaxAmountU64(amount) => {
3234 assert!(*amount > 0, "verified in validity check");
3235 *amount
3236 }
3237 };
3238
3239 let withdrawal_owner = withdraw.owner_for_withdrawal(self);
3240
3241 let account_id =
3243 AccumulatorValue::get_field_id(withdrawal_owner, &withdraw.type_arg.to_type_tag())
3244 .unwrap();
3245
3246 let value = withdraw_map.entry(account_id).or_default();
3247 *value = value.checked_add(reserved_amount).unwrap();
3249 }
3250
3251 for obj in self.coin_reservation_obj_refs() {
3255 assert_reachable!("processing coin reservation withdrawal");
3256 let parsed = ParsedObjectRefWithdrawal::parse(&obj, chain_identifier).unwrap();
3258 let value = withdraw_map
3259 .entry(AccumulatorObjId::new_unchecked(parsed.unmasked_object_id))
3264 .or_default();
3265 *value = value.checked_add(parsed.reservation_amount()).unwrap();
3267 }
3268
3269 withdraw_map
3270 }
3271
3272 fn check_allowance_inputs(&self, input_objects: &InputObjects) -> UserInputResult<()> {
3273 let allowance_withdrawals: Vec<_> = self
3274 .get_funds_withdrawals()
3275 .filter_map(|w| match w.withdraw_from {
3276 WithdrawFrom::SenderAllowance { funder, allowance } => {
3277 Some((funder, allowance, w.type_arg.to_type_tag()))
3278 }
3279 _ => None,
3280 })
3281 .collect();
3282 if allowance_withdrawals.is_empty() {
3283 return Ok(());
3284 }
3285 let objects_by_id: BTreeMap<ObjectID, &Object> = input_objects
3287 .iter()
3288 .filter_map(|input| Some((input.id(), input.as_object()?)))
3289 .collect();
3290 let mut resolved_allowances: BTreeMap<ObjectID, ResolvedAllowance> = BTreeMap::new();
3293 for (specified_funder, allowance, requested_funds_type) in allowance_withdrawals {
3294 let resolved: &ResolvedAllowance = match resolved_allowances.entry(allowance) {
3295 Entry::Occupied(entry) => entry.into_mut(),
3296 Entry::Vacant(entry) => {
3297 let object = objects_by_id.get(&allowance).ok_or_else(|| {
3298 UserInputError::InvalidWithdrawReservation {
3299 error: format!(
3300 "Specified allowance {allowance} not found among the tx inputs"
3301 ),
3302 }
3303 })?;
3304 entry.insert(parse_allowance_object(object)?)
3305 }
3306 };
3307 if resolved.funder != specified_funder {
3308 return Err(UserInputError::InvalidWithdrawReservation {
3309 error: format!(
3310 "Specified funder {specified_funder} does not match the funder of \
3311 allowance {allowance}"
3312 ),
3313 });
3314 }
3315 if resolved.spender != Some(self.sender()) {
3316 return Err(UserInputError::InvalidWithdrawReservation {
3317 error: format!(
3318 "Transaction sender is not the spender of allowance {allowance}"
3319 ),
3320 });
3321 }
3322 if resolved.funds_type != requested_funds_type {
3323 return Err(UserInputError::InvalidWithdrawReservation {
3324 error: format!(
3325 "Allowance {allowance} is for {}, not {requested_funds_type}",
3326 resolved.funds_type
3327 ),
3328 });
3329 }
3330 }
3331 Ok(())
3332 }
3333
3334 fn has_funds_withdrawals(&self) -> bool {
3335 if self.is_gas_paid_from_address_balance() && self.gas_data().budget > 0 {
3336 return true;
3337 }
3338 if let TransactionKind::ProgrammableTransaction(pt) = &self.kind {
3339 for input in &pt.inputs {
3340 if matches!(input, CallArg::FundsWithdrawal(_)) {
3341 return true;
3342 }
3343 }
3344 }
3345 if self.coin_reservation_obj_refs().next().is_some() {
3346 return true;
3347 }
3348 false
3349 }
3350
3351 fn coin_reservation_obj_refs(
3352 &self,
3353 chain_identifier: ChainIdentifier,
3354 ) -> Vec<ParsedObjectRefWithdrawal> {
3355 self.coin_reservation_obj_refs()
3356 .filter_map(|obj_ref| ParsedObjectRefWithdrawal::parse(&obj_ref, chain_identifier))
3357 .collect()
3358 }
3359
3360 fn validity_check(&self, context: &TxValidityCheckContext<'_>) -> SuiResult {
3361 let config = context.config;
3362
3363 match self.expiration() {
3365 TransactionExpiration::None => (), TransactionExpiration::Epoch(max_epoch) => {
3367 if context.epoch > *max_epoch {
3368 return Err(SuiErrorKind::TransactionExpired.into());
3369 }
3370 }
3371 TransactionExpiration::ValidDuring {
3372 min_epoch,
3373 max_epoch,
3374 min_timestamp,
3375 max_timestamp,
3376 chain,
3377 ..
3378 }
3379 | TransactionExpiration::Validity {
3380 min_epoch,
3381 max_epoch,
3382 min_timestamp,
3383 max_timestamp,
3384 chain,
3385 ..
3386 } => {
3387 if matches!(self.expiration(), TransactionExpiration::Validity { .. }) {
3390 fp_ensure!(
3391 config.allowed_proposers(),
3392 UserInputError::Unsupported(
3393 "Restricting the proposers of a transaction is not supported"
3394 .to_string(),
3395 )
3396 .into()
3397 );
3398 }
3399
3400 if let Some(allowed_proposers) = self.expiration().allowed_proposers(context.epoch)
3403 {
3404 let proposers = &allowed_proposers.proposers;
3405
3406 let max_proposers = MAX_UNPAID_ALLOWED_PROPOSERS
3413 .max(self.gas_data.price / context.reference_gas_price.max(1))
3414 .min(context.committee_size as u64);
3415 fp_ensure!(
3416 proposers.len() as u64 <= max_proposers,
3417 UserInputError::InvalidExpiration {
3418 error: format!(
3419 "allowed_proposers has {} entries, but at most {max_proposers} \
3420 are permitted with gas price {}, reference gas price {}, and a \
3421 committee of {}",
3422 proposers.len(),
3423 self.gas_data.price,
3424 context.reference_gas_price,
3425 context.committee_size,
3426 ),
3427 }
3428 .into()
3429 );
3430
3431 fp_ensure!(
3434 proposers.iter().is_sorted_by(|a, b| a < b),
3435 UserInputError::InvalidExpiration {
3436 error: "allowed_proposers must be strictly increasing".to_string(),
3437 }
3438 .into()
3439 );
3440
3441 if let Some(out_of_range) =
3444 proposers.iter().find(|i| **i >= context.committee_size)
3445 {
3446 return Err(UserInputError::InvalidExpiration {
3447 error: format!(
3448 "allowed_proposers contains index {out_of_range}, but the \
3449 committee has {} members",
3450 context.committee_size,
3451 ),
3452 }
3453 .into());
3454 }
3455 }
3456
3457 if min_timestamp.is_some() || max_timestamp.is_some() {
3458 return Err(UserInputError::Unsupported(
3459 "Timestamp-based transaction expiration is not yet supported".to_string(),
3460 )
3461 .into());
3462 }
3463
3464 match (min_epoch, max_epoch) {
3469 _ if config.relax_valid_during_for_owned_inputs() => (),
3470 (Some(min), Some(max)) => {
3471 if config.enable_multi_epoch_transaction_expiration() {
3472 if !(*max == *min || *max == min.saturating_add(1)) {
3473 return Err(UserInputError::Unsupported(
3474 "max_epoch must be at most min_epoch + 1".to_string(),
3475 )
3476 .into());
3477 }
3478 } else if min != max {
3479 return Err(UserInputError::Unsupported(
3480 "min_epoch must equal max_epoch".to_string(),
3481 )
3482 .into());
3483 }
3484 }
3485 _ => {
3486 return Err(UserInputError::Unsupported(
3487 "Both min_epoch and max_epoch must be specified".to_string(),
3488 )
3489 .into());
3490 }
3491 }
3492
3493 if *chain != context.chain_identifier {
3494 return Err(UserInputError::InvalidChainId {
3495 provided: format!("{:?}", chain),
3496 expected: format!("{:?}", context.chain_identifier),
3497 }
3498 .into());
3499 }
3500
3501 if let Some(min) = min_epoch
3502 && context.epoch < *min
3503 {
3504 return Err(SuiErrorKind::TransactionExpired.into());
3505 }
3506 if let Some(max) = max_epoch
3507 && context.epoch > *max
3508 {
3509 return Err(SuiErrorKind::TransactionExpired.into());
3510 }
3511 }
3512 }
3513
3514 if self.has_funds_withdrawals() {
3515 fp_ensure!(
3518 !self.gas().is_empty() || config.enable_address_balance_gas_payments(),
3519 UserInputError::MissingGasPayment.into()
3520 );
3521
3522 fp_ensure!(
3523 config.enable_accumulators(),
3524 UserInputError::Unsupported("Address balance withdraw is not enabled".to_string())
3525 .into()
3526 );
3527
3528 let max_withdraws = 10;
3530 let mut num_reservations = 0;
3531
3532 for withdraw in self.kind.get_funds_withdrawals() {
3533 num_reservations += 1;
3534 match withdraw.withdraw_from {
3535 WithdrawFrom::Sender => (),
3536 WithdrawFrom::Sponsor => {
3537 return Err(UserInputError::InvalidWithdrawReservation {
3538 error: "Explicit sponsor withdrawals are not yet supported".to_string(),
3539 }
3540 .into());
3541 }
3542 WithdrawFrom::SenderAllowance { .. } => {
3545 fp_ensure!(
3546 config.enable_allowances(),
3547 UserInputError::Unsupported(
3548 "Allowance withdrawals are not enabled".to_string()
3549 )
3550 .into()
3551 );
3552 }
3553 }
3554
3555 match withdraw.reservation {
3556 Reservation::MaxAmountU64(amount) => {
3557 fp_ensure!(
3558 amount > 0,
3559 UserInputError::InvalidWithdrawReservation {
3560 error: "Balance withdraw reservation amount must be non-zero"
3561 .to_string(),
3562 }
3563 .into()
3564 );
3565 }
3566 };
3567 }
3568
3569 for parsed in self.parsed_coin_reservations(context.chain_identifier) {
3570 num_reservations += 1;
3571 if parsed.epoch_id() != context.epoch && parsed.epoch_id() + 1 != context.epoch {
3575 return Err(SuiErrorKind::TransactionExpired.into());
3576 }
3577 if parsed.reservation_amount() == 0 {
3578 return Err(UserInputError::InvalidWithdrawReservation {
3579 error: "Balance withdraw reservation amount must be non-zero".to_string(),
3580 }
3581 .into());
3582 }
3583 }
3584
3585 if config.enable_address_balance_gas_payments()
3587 && self.is_gas_paid_from_address_balance()
3588 {
3589 num_reservations += 1;
3590 }
3591
3592 fp_ensure!(
3593 num_reservations <= max_withdraws,
3594 UserInputError::InvalidWithdrawReservation {
3595 error: format!(
3596 "Maximum number of balance withdraw reservations is {max_withdraws}"
3597 ),
3598 }
3599 .into()
3600 );
3601 }
3602
3603 if config.enable_accumulators()
3604 && config.enable_address_balance_gas_payments()
3605 && self.is_gas_paid_from_address_balance()
3606 {
3607 if config.address_balance_gas_reject_gas_coin_arg()
3608 && let TransactionKind::ProgrammableTransaction(pt) = &self.kind
3609 {
3610 fp_ensure!(
3611 !pt.commands.iter().any(|cmd| cmd.is_gas_coin_used()),
3612 UserInputError::Unsupported(
3613 "Argument::GasCoin is not supported with address balance gas payments"
3614 .to_string(),
3615 )
3616 .into()
3617 );
3618 }
3619
3620 let is_gasless = config.enable_gasless() && self.is_gasless_transaction();
3621 if config.address_balance_gas_check_rgp_at_signing() && !is_gasless {
3622 fp_ensure!(
3623 self.gas_data.price >= context.reference_gas_price,
3624 UserInputError::GasPriceUnderRGP {
3625 gas_price: self.gas_data.price,
3626 reference_gas_price: context.reference_gas_price,
3627 }
3628 .into()
3629 );
3630 }
3631
3632 if !config.relax_valid_during_for_owned_inputs() {
3637 if matches!(self.expiration(), TransactionExpiration::None) {
3638 return Err(UserInputError::MissingGasPayment.into());
3641 }
3642
3643 if !self.expiration().is_replay_protected() {
3644 return Err(UserInputError::InvalidExpiration {
3645 error: "Address balance gas payments require ValidDuring expiration"
3646 .to_string(),
3647 }
3648 .into());
3649 }
3650 }
3651 } else {
3652 fp_ensure!(
3653 !self.gas().is_empty(),
3654 UserInputError::MissingGasPayment.into()
3655 );
3656 }
3657
3658 let gas_len = self.gas().len();
3659 let max_gas_objects = config.max_gas_payment_objects() as usize;
3660
3661 let within_limit = if config.correct_gas_payment_limit_check() {
3662 gas_len <= max_gas_objects
3663 } else {
3664 gas_len < max_gas_objects
3665 };
3666
3667 fp_ensure!(
3668 within_limit,
3669 UserInputError::SizeLimitExceeded {
3670 limit: "maximum number of gas payment objects".to_string(),
3671 value: config.max_gas_payment_objects().to_string()
3672 }
3673 .into()
3674 );
3675
3676 if !config.enable_coin_reservation_obj_refs() {
3677 for (_, _, gas_digest) in self.gas() {
3678 fp_ensure!(
3679 !ParsedDigest::is_coin_reservation_digest(gas_digest),
3680 UserInputError::GasObjectNotOwnedObject {
3681 owner: Owner::AddressOwner(self.sender)
3682 }
3683 .into()
3684 );
3685 }
3686 } else {
3687 let sui_accumulator_id =
3690 *AccumulatorValue::get_field_id(self.sender, &Balance::type_tag(GAS::type_tag()))?
3691 .inner();
3692
3693 for gas_ref in self.gas() {
3694 if let Some(parsed) =
3695 ParsedObjectRefWithdrawal::parse(gas_ref, context.chain_identifier)
3696 {
3697 fp_ensure!(
3700 self.gas_owner() == self.sender,
3701 UserInputError::GasObjectNotOwnedObject {
3702 owner: Owner::AddressOwner(self.sender)
3703 }
3704 .into()
3705 );
3706 fp_ensure!(
3707 parsed.unmasked_object_id == sui_accumulator_id,
3708 UserInputError::GasObjectNotOwnedObject {
3709 owner: Owner::AddressOwner(self.sender)
3710 }
3711 .into()
3712 );
3713 }
3714 }
3715 }
3716
3717 if !self.is_system_tx() {
3718 fp_ensure!(
3719 !check_for_gas_price_too_high(config.gas_model_version())
3720 || self.gas_data.price < config.max_gas_price(),
3721 UserInputError::GasPriceTooHigh {
3722 max_gas_price: config.max_gas_price(),
3723 }
3724 .into()
3725 );
3726 let cost_table = SuiCostTable::new(config, self.gas_data.price);
3727
3728 fp_ensure!(
3729 self.gas_data.budget <= cost_table.max_gas_budget,
3730 UserInputError::GasBudgetTooHigh {
3731 gas_budget: self.gas_data().budget,
3732 max_budget: cost_table.max_gas_budget,
3733 }
3734 .into()
3735 );
3736 let is_gasless = config.enable_gasless() && self.is_gasless_transaction();
3737 if is_gasless {
3738 fp_ensure!(
3739 self.gas_data.budget == 0,
3740 UserInputError::Unsupported(
3741 "gas_budget must be 0 for gasless transactions".to_string()
3742 )
3743 .into()
3744 );
3745 } else {
3746 fp_ensure!(
3747 self.gas_data.budget >= cost_table.min_transaction_cost,
3748 UserInputError::GasBudgetTooLow {
3749 gas_budget: self.gas_data.budget,
3750 min_budget: cost_table.min_transaction_cost,
3751 }
3752 .into()
3753 );
3754 }
3755 }
3756
3757 self.kind().validity_check(config)?;
3758
3759 if config.enable_gasless() && self.is_gasless_transaction() {
3760 let TransactionKind::ProgrammableTransaction(pt) = &self.kind else {
3761 debug_fatal!("gasless transaction is not a ProgrammableTransaction");
3762 return Err(UserInputError::Unsupported(
3763 "Gasless transactions must be programmable transactions".to_string(),
3764 )
3765 .into());
3766 };
3767 pt.validate_gasless_transaction(config)?;
3768 }
3769
3770 self.check_sponsorship()?;
3771 Ok(())
3772 }
3773
3774 fn is_sponsored_tx(&self) -> bool {
3776 self.gas_owner() != self.sender
3777 }
3778
3779 fn is_gas_paid_from_address_balance(&self) -> bool {
3783 is_gas_paid_from_address_balance(&self.gas_data, &self.kind)
3784 }
3785
3786 fn is_gasless_transaction(&self) -> bool {
3787 is_gasless_transaction(&self.gas_data, &self.kind)
3788 }
3789
3790 fn check_sponsorship(&self) -> UserInputResult {
3792 if self.gas_owner() == self.sender() {
3794 return Ok(());
3795 }
3796 if matches!(&self.kind, TransactionKind::ProgrammableTransaction(_)) {
3797 return Ok(());
3798 }
3799 Err(UserInputError::UnsupportedSponsoredTransactionKind)
3800 }
3801
3802 fn is_end_of_epoch_tx(&self) -> bool {
3803 matches!(
3804 self.kind,
3805 TransactionKind::ChangeEpoch(_) | TransactionKind::EndOfEpochTransaction(_)
3806 )
3807 }
3808
3809 fn is_consensus_commit_prologue(&self) -> bool {
3810 match &self.kind {
3811 TransactionKind::ConsensusCommitPrologue(_)
3812 | TransactionKind::ConsensusCommitPrologueV2(_)
3813 | TransactionKind::ConsensusCommitPrologueV3(_)
3814 | TransactionKind::ConsensusCommitPrologueV4(_) => true,
3815
3816 TransactionKind::ProgrammableTransaction(_)
3817 | TransactionKind::ProgrammableSystemTransaction(_)
3818 | TransactionKind::ChangeEpoch(_)
3819 | TransactionKind::Genesis(_)
3820 | TransactionKind::AuthenticatorStateUpdate(_)
3821 | TransactionKind::EndOfEpochTransaction(_)
3822 | TransactionKind::RandomnessStateUpdate(_) => false,
3823 }
3824 }
3825
3826 fn is_system_tx(&self) -> bool {
3827 self.kind.is_system_tx()
3828 }
3829
3830 fn is_genesis_tx(&self) -> bool {
3831 matches!(self.kind, TransactionKind::Genesis(_))
3832 }
3833
3834 fn sender_mut_for_testing(&mut self) -> &mut SuiAddress {
3835 &mut self.sender
3836 }
3837
3838 fn gas_data_mut(&mut self) -> &mut GasData {
3839 &mut self.gas_data
3840 }
3841
3842 fn expiration_mut_for_testing(&mut self) -> &mut TransactionExpiration {
3843 &mut self.expiration
3844 }
3845}
3846
3847impl TransactionDataV1 {
3848 fn accumulate_funds_withdrawals(
3849 &self,
3850 chain_identifier: ChainIdentifier,
3851 coin_resolver: &dyn CoinReservationResolverTrait,
3852 include_gas_payment: bool,
3853 ) -> UserInputResult<BTreeMap<AccumulatorObjId, (u64, TypeTag, SuiAddress)>> {
3854 let mut withdraws: Vec<_> = self.get_funds_withdrawals().collect();
3855
3856 for withdraw in self.parsed_coin_reservations(chain_identifier) {
3857 let withdrawal_arg =
3858 coin_resolver.resolve_funds_withdrawal(self.sender(), withdraw, None)?;
3859 withdraws.push(withdrawal_arg);
3860 }
3861
3862 if include_gas_payment {
3863 withdraws.extend(self.get_funds_withdrawal_for_gas_payment());
3864 }
3865
3866 let mut withdraw_map: BTreeMap<AccumulatorObjId, (u64, TypeTag, SuiAddress)> =
3867 BTreeMap::new();
3868 for withdraw in withdraws {
3869 let reserved_amount = match &withdraw.reservation {
3870 Reservation::MaxAmountU64(amount) => {
3871 if *amount == 0 {
3872 return Err(UserInputError::InvalidWithdrawReservation {
3873 error: "Balance withdraw reservation amount must be non-zero"
3874 .to_string(),
3875 });
3876 }
3877 *amount
3878 }
3879 };
3880
3881 let account_address = withdraw.owner_for_withdrawal(self);
3882 let type_tag = withdraw.type_arg.to_type_tag();
3883 let account_id =
3884 AccumulatorValue::get_field_id(account_address, &type_tag).map_err(|e| {
3885 UserInputError::InvalidWithdrawReservation {
3886 error: e.to_string(),
3887 }
3888 })?;
3889
3890 let (current_amount, _, _) = withdraw_map
3891 .entry(account_id)
3892 .or_insert_with(|| (0, type_tag, account_address));
3893 *current_amount = current_amount.checked_add(reserved_amount).ok_or(
3894 UserInputError::InvalidWithdrawReservation {
3895 error: "Balance withdraw reservation overflow".to_string(),
3896 },
3897 )?;
3898 }
3899
3900 Ok(withdraw_map)
3901 }
3902
3903 fn get_funds_withdrawal_for_gas_payment(&self) -> Option<FundsWithdrawalArg> {
3904 if self.is_gas_paid_from_address_balance() && self.gas_data().budget > 0 {
3905 Some(if self.sender() != self.gas_owner() {
3906 FundsWithdrawalArg::balance_from_sponsor(self.gas_data().budget, GAS::type_tag())
3907 } else {
3908 FundsWithdrawalArg::balance_from_sender(self.gas_data().budget, GAS::type_tag())
3909 })
3910 } else {
3911 None
3912 }
3913 }
3914
3915 fn get_funds_withdrawals(&self) -> impl Iterator<Item = FundsWithdrawalArg> + '_ {
3916 self.kind.get_funds_withdrawals().cloned()
3917 }
3918
3919 fn coin_reservation_obj_refs(&self) -> impl Iterator<Item = ObjectRef> {
3920 self.kind
3921 .get_coin_reservation_obj_refs()
3922 .chain(self.gas().iter().filter_map(|gas_ref| {
3923 if ParsedDigest::is_coin_reservation_digest(&gas_ref.2) {
3924 Some(*gas_ref)
3925 } else {
3926 None
3927 }
3928 }))
3929 }
3930
3931 fn parsed_coin_reservations(
3932 &self,
3933 chain_identifier: ChainIdentifier,
3934 ) -> impl Iterator<Item = ParsedObjectRefWithdrawal> {
3935 self.coin_reservation_obj_refs().map(move |obj_ref| {
3936 ParsedObjectRefWithdrawal::parse(&obj_ref, chain_identifier).unwrap()
3937 })
3938 }
3939}
3940
3941pub struct TxValidityCheckContext<'a> {
3942 pub config: &'a ProtocolConfig,
3943 pub epoch: EpochId,
3944 pub chain_identifier: ChainIdentifier,
3945 pub reference_gas_price: u64,
3946 pub committee_size: u32,
3948}
3949
3950impl<'a> TxValidityCheckContext<'a> {
3951 pub fn from_cfg_for_testing(config: &'a ProtocolConfig) -> Self {
3952 Self {
3953 config,
3954 epoch: 0,
3955 chain_identifier: ChainIdentifier::default(),
3956 reference_gas_price: 1000,
3957 committee_size: 4,
3958 }
3959 }
3960}
3961
3962#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
3963pub struct SenderSignedData(SizeOneVec<SenderSignedTransaction>);
3964
3965#[derive(Debug, Clone, PartialEq, Eq, Hash)]
3966pub struct SenderSignedTransaction {
3967 pub intent_message: IntentMessage<TransactionData>,
3968 pub tx_signatures: Vec<GenericSignature>,
3972}
3973
3974impl Serialize for SenderSignedTransaction {
3975 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
3976 where
3977 S: serde::Serializer,
3978 {
3979 #[derive(Serialize)]
3980 #[serde(rename = "SenderSignedTransaction")]
3981 struct SignedTxn<'a> {
3982 intent_message: &'a IntentMessage<TransactionData>,
3983 tx_signatures: &'a Vec<GenericSignature>,
3984 }
3985
3986 if self.intent_message().intent != Intent::sui_transaction() {
3987 return Err(serde::ser::Error::custom("invalid Intent for Transaction"));
3988 }
3989
3990 let txn = SignedTxn {
3991 intent_message: self.intent_message(),
3992 tx_signatures: &self.tx_signatures,
3993 };
3994 txn.serialize(serializer)
3995 }
3996}
3997
3998impl<'de> Deserialize<'de> for SenderSignedTransaction {
3999 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
4000 where
4001 D: serde::Deserializer<'de>,
4002 {
4003 #[derive(Deserialize)]
4004 #[serde(rename = "SenderSignedTransaction")]
4005 struct SignedTxn {
4006 intent_message: IntentMessage<TransactionData>,
4007 tx_signatures: Vec<GenericSignature>,
4008 }
4009
4010 let SignedTxn {
4011 intent_message,
4012 tx_signatures,
4013 } = Deserialize::deserialize(deserializer)?;
4014
4015 if intent_message.intent != Intent::sui_transaction() {
4016 return Err(serde::de::Error::custom("invalid Intent for Transaction"));
4017 }
4018
4019 Ok(Self {
4020 intent_message,
4021 tx_signatures,
4022 })
4023 }
4024}
4025
4026impl SenderSignedTransaction {
4027 pub(crate) fn get_signer_sig_mapping(
4029 &self,
4030 verify_legacy_zklogin_address: bool,
4031 ) -> SuiResult<BTreeMap<SuiAddress, (u8, &GenericSignature)>> {
4032 let mut mapping = BTreeMap::new();
4033 for (idx, sig) in self.tx_signatures.iter().enumerate() {
4034 if verify_legacy_zklogin_address && let GenericSignature::ZkLoginAuthenticator(z) = sig
4035 {
4036 mapping.insert(SuiAddress::try_from_padded(&z.inputs)?, (idx as u8, sig));
4038 }
4039 let address = sig.try_into()?;
4040 mapping.insert(address, (idx as u8, sig));
4041 }
4042 Ok(mapping)
4043 }
4044
4045 pub fn intent_message(&self) -> &IntentMessage<TransactionData> {
4046 &self.intent_message
4047 }
4048}
4049
4050impl SenderSignedData {
4051 pub fn new(tx_data: TransactionData, tx_signatures: Vec<GenericSignature>) -> Self {
4052 Self(SizeOneVec::new(SenderSignedTransaction {
4053 intent_message: IntentMessage::new(Intent::sui_transaction(), tx_data),
4054 tx_signatures,
4055 }))
4056 }
4057
4058 pub fn new_from_sender_signature(tx_data: TransactionData, tx_signature: Signature) -> Self {
4059 Self(SizeOneVec::new(SenderSignedTransaction {
4060 intent_message: IntentMessage::new(Intent::sui_transaction(), tx_data),
4061 tx_signatures: vec![tx_signature.into()],
4062 }))
4063 }
4064
4065 pub fn inner(&self) -> &SenderSignedTransaction {
4066 self.0.element()
4067 }
4068
4069 pub fn into_inner(self) -> SenderSignedTransaction {
4070 self.0.into_inner()
4071 }
4072
4073 pub fn inner_mut(&mut self) -> &mut SenderSignedTransaction {
4074 self.0.element_mut()
4075 }
4076
4077 pub fn add_signature(&mut self, new_signature: Signature) {
4080 self.inner_mut().tx_signatures.push(new_signature.into());
4081 }
4082
4083 pub(crate) fn get_signer_sig_mapping(
4084 &self,
4085 verify_legacy_zklogin_address: bool,
4086 ) -> SuiResult<BTreeMap<SuiAddress, (u8, &GenericSignature)>> {
4087 self.inner()
4088 .get_signer_sig_mapping(verify_legacy_zklogin_address)
4089 }
4090
4091 pub fn transaction_data(&self) -> &TransactionData {
4092 &self.intent_message().value
4093 }
4094
4095 pub fn intent_message(&self) -> &IntentMessage<TransactionData> {
4096 self.inner().intent_message()
4097 }
4098
4099 pub fn tx_signatures(&self) -> &[GenericSignature] {
4100 &self.inner().tx_signatures
4101 }
4102
4103 pub fn has_zklogin_sig(&self) -> bool {
4104 self.tx_signatures().iter().any(|sig| sig.is_zklogin())
4105 }
4106
4107 pub fn has_upgraded_multisig(&self) -> bool {
4108 self.tx_signatures()
4109 .iter()
4110 .any(|sig| sig.is_upgraded_multisig())
4111 }
4112
4113 #[cfg(test)]
4114 pub fn intent_message_mut_for_testing(&mut self) -> &mut IntentMessage<TransactionData> {
4115 &mut self.inner_mut().intent_message
4116 }
4117
4118 pub fn tx_signatures_mut_for_testing(&mut self) -> &mut Vec<GenericSignature> {
4120 &mut self.inner_mut().tx_signatures
4121 }
4122
4123 pub fn full_message_digest_with_alias_versions(
4125 &self,
4126 alias_versions: &Vec<(SuiAddress, Option<SequenceNumber>)>,
4127 ) -> SenderSignedDataDigest {
4128 let mut digest = DefaultHash::default();
4129 bcs::serialize_into(&mut digest, self).expect("serialization should not fail");
4130 bcs::serialize_into(&mut digest, alias_versions).expect("serialization should not fail");
4131 let hash = digest.finalize();
4132 SenderSignedDataDigest::new(hash.into())
4133 }
4134
4135 pub fn serialized_size(&self) -> SuiResult<usize> {
4136 bcs::serialized_size(self).map_err(|e| {
4137 SuiErrorKind::TransactionSerializationError {
4138 error: e.to_string(),
4139 }
4140 .into()
4141 })
4142 }
4143
4144 fn check_user_signature_protocol_compatibility(&self, config: &ProtocolConfig) -> SuiResult {
4145 for sig in &self.inner().tx_signatures {
4146 match sig {
4147 GenericSignature::MultiSig(_) => {
4148 if !config.upgraded_multisig_supported() {
4149 return Err(SuiErrorKind::UserInputError {
4150 error: UserInputError::Unsupported(
4151 "upgraded multisig format not enabled on this network".to_string(),
4152 ),
4153 }
4154 .into());
4155 }
4156 }
4157 GenericSignature::ZkLoginAuthenticator(_) => {
4158 if !config.zklogin_auth() {
4159 return Err(SuiErrorKind::UserInputError {
4160 error: UserInputError::Unsupported(
4161 "zklogin is not enabled on this network".to_string(),
4162 ),
4163 }
4164 .into());
4165 }
4166 }
4167 GenericSignature::PasskeyAuthenticator(_) => {
4168 if !config.passkey_auth() {
4169 return Err(SuiErrorKind::UserInputError {
4170 error: UserInputError::Unsupported(
4171 "passkey is not enabled on this network".to_string(),
4172 ),
4173 }
4174 .into());
4175 }
4176 }
4177 GenericSignature::Signature(_) | GenericSignature::MultiSigLegacy(_) => (),
4178 }
4179 }
4180
4181 Ok(())
4182 }
4183
4184 pub fn validity_check(&self, context: &TxValidityCheckContext<'_>) -> Result<usize, SuiError> {
4187 self.check_user_signature_protocol_compatibility(context.config)?;
4189
4190 let tx_data = &self.transaction_data();
4195 fp_ensure!(
4196 !tx_data.is_system_tx(),
4197 SuiErrorKind::UserInputError {
4198 error: UserInputError::Unsupported(
4199 "SenderSignedData must not contain system transaction".to_string()
4200 )
4201 }
4202 .into()
4203 );
4204
4205 let tx_size = self.serialized_size()?;
4207 let max_tx_size_bytes = context.config.max_tx_size_bytes();
4208 fp_ensure!(
4209 tx_size as u64 <= max_tx_size_bytes,
4210 SuiErrorKind::UserInputError {
4211 error: UserInputError::SizeLimitExceeded {
4212 limit: format!(
4213 "serialized transaction size exceeded maximum of {max_tx_size_bytes}"
4214 ),
4215 value: tx_size.to_string(),
4216 }
4217 }
4218 .into()
4219 );
4220
4221 if context.config.enable_gasless() && tx_data.is_gasless_transaction() {
4222 let gasless_max = context.config.get_gasless_max_tx_size_bytes();
4223 fp_ensure!(
4224 tx_size as u64 <= gasless_max,
4225 SuiErrorKind::UserInputError {
4226 error: UserInputError::SizeLimitExceeded {
4227 limit: format!(
4228 "serialized gasless transaction size exceeded maximum of {gasless_max}"
4229 ),
4230 value: tx_size.to_string(),
4231 }
4232 }
4233 .into()
4234 );
4235 }
4236
4237 tx_data.validity_check(context)?;
4238
4239 Ok(tx_size)
4240 }
4241}
4242
4243impl Message for SenderSignedData {
4244 type DigestType = TransactionDigest;
4245 const SCOPE: IntentScope = IntentScope::SenderSignedTransaction;
4246
4247 fn digest(&self) -> Self::DigestType {
4249 self.intent_message().value.digest()
4250 }
4251}
4252
4253impl<S> Envelope<SenderSignedData, S> {
4254 pub fn sender_address(&self) -> SuiAddress {
4255 self.data().intent_message().value.sender()
4256 }
4257
4258 pub fn gas_owner(&self) -> SuiAddress {
4259 self.data().intent_message().value.gas_owner()
4260 }
4261
4262 pub fn gas(&self) -> &[ObjectRef] {
4263 self.data().intent_message().value.gas()
4264 }
4265
4266 pub fn is_consensus_tx(&self) -> bool {
4267 self.transaction_data().has_funds_withdrawals()
4268 || self.shared_input_objects().next().is_some()
4269 }
4270
4271 pub fn shared_input_objects(&self) -> impl Iterator<Item = SharedInputObject> + '_ {
4272 self.data()
4273 .inner()
4274 .intent_message
4275 .value
4276 .shared_input_objects()
4277 .into_iter()
4278 }
4279
4280 pub fn key(&self) -> TransactionKey {
4282 match &self.data().intent_message().value.kind() {
4283 TransactionKind::RandomnessStateUpdate(rsu) => {
4284 TransactionKey::RandomnessRound(rsu.epoch, rsu.randomness_round)
4285 }
4286 _ => TransactionKey::Digest(*self.digest()),
4287 }
4288 }
4289
4290 pub fn non_digest_key(&self) -> Option<TransactionKey> {
4295 match &self.data().intent_message().value.kind() {
4296 TransactionKind::RandomnessStateUpdate(rsu) => Some(TransactionKey::RandomnessRound(
4297 rsu.epoch,
4298 rsu.randomness_round,
4299 )),
4300 _ => None,
4301 }
4302 }
4303
4304 pub fn is_system_tx(&self) -> bool {
4305 self.data().intent_message().value.is_system_tx()
4306 }
4307
4308 pub fn is_sponsored_tx(&self) -> bool {
4309 self.data().intent_message().value.is_sponsored_tx()
4310 }
4311}
4312
4313impl Transaction {
4314 pub fn from_data_and_signer(
4315 data: TransactionData,
4316 signers: Vec<&dyn Signer<Signature>>,
4317 ) -> Self {
4318 let signatures = {
4319 let intent_msg = IntentMessage::new(Intent::sui_transaction(), &data);
4320 signers
4321 .into_iter()
4322 .map(|s| Signature::new_secure(&intent_msg, s))
4323 .collect()
4324 };
4325 Self::from_data(data, signatures)
4326 }
4327
4328 pub fn from_data(data: TransactionData, signatures: Vec<Signature>) -> Self {
4330 Self::from_generic_sig_data(data, signatures.into_iter().map(|s| s.into()).collect())
4331 }
4332
4333 pub fn signature_from_signer(
4334 data: TransactionData,
4335 intent: Intent,
4336 signer: &dyn Signer<Signature>,
4337 ) -> Signature {
4338 let intent_msg = IntentMessage::new(intent, data);
4339 Signature::new_secure(&intent_msg, signer)
4340 }
4341
4342 pub fn from_generic_sig_data(data: TransactionData, signatures: Vec<GenericSignature>) -> Self {
4343 Self::new(SenderSignedData::new(data, signatures))
4344 }
4345
4346 pub fn to_tx_bytes_and_signatures(&self) -> (Base64, Vec<Base64>) {
4349 (
4350 Base64::from_bytes(&bcs::to_bytes(&self.data().intent_message().value).unwrap()),
4351 self.data()
4352 .inner()
4353 .tx_signatures
4354 .iter()
4355 .map(|s| Base64::from_bytes(s.as_ref()))
4356 .collect(),
4357 )
4358 }
4359}
4360
4361impl VerifiedTransaction {
4362 pub fn new_change_epoch(
4363 next_epoch: EpochId,
4364 protocol_version: ProtocolVersion,
4365 storage_charge: u64,
4366 computation_charge: u64,
4367 storage_rebate: u64,
4368 non_refundable_storage_fee: u64,
4369 epoch_start_timestamp_ms: u64,
4370 system_packages: Vec<(SequenceNumber, Vec<Vec<u8>>, Vec<ObjectID>)>,
4371 ) -> Self {
4372 ChangeEpoch {
4373 epoch: next_epoch,
4374 protocol_version,
4375 storage_charge,
4376 computation_charge,
4377 storage_rebate,
4378 non_refundable_storage_fee,
4379 epoch_start_timestamp_ms,
4380 system_packages,
4381 }
4382 .pipe(TransactionKind::ChangeEpoch)
4383 .pipe(Self::new_system_transaction)
4384 }
4385
4386 pub fn new_genesis_transaction(objects: Vec<GenesisObject>) -> Self {
4387 GenesisTransaction { objects }
4388 .pipe(TransactionKind::Genesis)
4389 .pipe(Self::new_system_transaction)
4390 }
4391
4392 pub fn new_consensus_commit_prologue(
4393 epoch: u64,
4394 round: u64,
4395 commit_timestamp_ms: CheckpointTimestamp,
4396 ) -> Self {
4397 ConsensusCommitPrologue {
4398 epoch,
4399 round,
4400 commit_timestamp_ms,
4401 }
4402 .pipe(TransactionKind::ConsensusCommitPrologue)
4403 .pipe(Self::new_system_transaction)
4404 }
4405
4406 pub fn new_consensus_commit_prologue_v2(
4407 epoch: u64,
4408 round: u64,
4409 commit_timestamp_ms: CheckpointTimestamp,
4410 consensus_commit_digest: ConsensusCommitDigest,
4411 ) -> Self {
4412 ConsensusCommitPrologueV2 {
4413 epoch,
4414 round,
4415 commit_timestamp_ms,
4416 consensus_commit_digest,
4417 }
4418 .pipe(TransactionKind::ConsensusCommitPrologueV2)
4419 .pipe(Self::new_system_transaction)
4420 }
4421
4422 pub fn new_consensus_commit_prologue_v3(
4423 epoch: u64,
4424 round: u64,
4425 commit_timestamp_ms: CheckpointTimestamp,
4426 consensus_commit_digest: ConsensusCommitDigest,
4427 consensus_determined_version_assignments: ConsensusDeterminedVersionAssignments,
4428 ) -> Self {
4429 ConsensusCommitPrologueV3 {
4430 epoch,
4431 round,
4432 sub_dag_index: None,
4434 commit_timestamp_ms,
4435 consensus_commit_digest,
4436 consensus_determined_version_assignments,
4437 }
4438 .pipe(TransactionKind::ConsensusCommitPrologueV3)
4439 .pipe(Self::new_system_transaction)
4440 }
4441
4442 pub fn new_consensus_commit_prologue_v4(
4443 epoch: u64,
4444 round: u64,
4445 commit_timestamp_ms: CheckpointTimestamp,
4446 consensus_commit_digest: ConsensusCommitDigest,
4447 consensus_determined_version_assignments: ConsensusDeterminedVersionAssignments,
4448 additional_state_digest: AdditionalConsensusStateDigest,
4449 ) -> Self {
4450 ConsensusCommitPrologueV4 {
4451 epoch,
4452 round,
4453 sub_dag_index: None,
4455 commit_timestamp_ms,
4456 consensus_commit_digest,
4457 consensus_determined_version_assignments,
4458 additional_state_digest,
4459 }
4460 .pipe(TransactionKind::ConsensusCommitPrologueV4)
4461 .pipe(Self::new_system_transaction)
4462 }
4463
4464 pub fn new_authenticator_state_update(
4465 epoch: u64,
4466 round: u64,
4467 new_active_jwks: Vec<ActiveJwk>,
4468 authenticator_obj_initial_shared_version: SequenceNumber,
4469 ) -> Self {
4470 AuthenticatorStateUpdate {
4471 epoch,
4472 round,
4473 new_active_jwks,
4474 authenticator_obj_initial_shared_version,
4475 }
4476 .pipe(TransactionKind::AuthenticatorStateUpdate)
4477 .pipe(Self::new_system_transaction)
4478 }
4479
4480 pub fn new_randomness_state_update(
4481 epoch: u64,
4482 randomness_round: RandomnessRound,
4483 random_bytes: Vec<u8>,
4484 randomness_obj_initial_shared_version: SequenceNumber,
4485 ) -> Self {
4486 RandomnessStateUpdate {
4487 epoch,
4488 randomness_round,
4489 random_bytes,
4490 randomness_obj_initial_shared_version,
4491 }
4492 .pipe(TransactionKind::RandomnessStateUpdate)
4493 .pipe(Self::new_system_transaction)
4494 }
4495
4496 pub fn new_end_of_epoch_transaction(txns: Vec<EndOfEpochTransactionKind>) -> Self {
4497 TransactionKind::EndOfEpochTransaction(txns).pipe(Self::new_system_transaction)
4498 }
4499
4500 pub fn new_system_transaction(system_transaction: TransactionKind) -> Self {
4501 system_transaction
4502 .pipe(TransactionData::new_system_transaction)
4503 .pipe(|data| {
4504 SenderSignedData::new_from_sender_signature(
4505 data,
4506 Ed25519SuiSignature::from_bytes(&[0; Ed25519SuiSignature::LENGTH])
4507 .unwrap()
4508 .into(),
4509 )
4510 })
4511 .pipe(Transaction::new)
4512 .pipe(Self::new_from_verified)
4513 }
4514}
4515
4516impl VerifiedSignedTransaction {
4517 pub fn new(
4519 epoch: EpochId,
4520 transaction: VerifiedTransaction,
4521 authority: AuthorityName,
4522 secret: &dyn Signer<AuthoritySignature>,
4523 ) -> Self {
4524 Self::new_from_verified(SignedTransaction::new(
4525 epoch,
4526 transaction.into_inner().into_data(),
4527 secret,
4528 authority,
4529 ))
4530 }
4531}
4532
4533pub type Transaction = Envelope<SenderSignedData, EmptySignInfo>;
4535pub type VerifiedTransaction = VerifiedEnvelope<SenderSignedData, EmptySignInfo>;
4536pub type TrustedTransaction = TrustedEnvelope<SenderSignedData, EmptySignInfo>;
4537
4538pub type SignedTransaction = Envelope<SenderSignedData, AuthoritySignInfo>;
4540pub type VerifiedSignedTransaction = VerifiedEnvelope<SenderSignedData, AuthoritySignInfo>;
4541
4542impl Transaction {
4543 pub fn verify_signature_for_testing(
4544 &self,
4545 current_epoch: EpochId,
4546 verify_params: &VerifyParams,
4547 ) -> SuiResult {
4548 verify_sender_signed_data_message_signatures(
4549 self.data(),
4550 current_epoch,
4551 verify_params,
4552 Arc::new(VerifiedDigestCache::new_empty()),
4553 vec![],
4554 )?;
4555 Ok(())
4556 }
4557
4558 pub fn try_into_verified_for_testing(
4559 self,
4560 current_epoch: EpochId,
4561 verify_params: &VerifyParams,
4562 ) -> SuiResult<VerifiedTransaction> {
4563 self.verify_signature_for_testing(current_epoch, verify_params)?;
4564 Ok(VerifiedTransaction::new_from_verified(self))
4565 }
4566}
4567
4568impl SignedTransaction {
4569 pub fn verify_signatures_authenticated_for_testing(
4570 &self,
4571 committee: &Committee,
4572 verify_params: &VerifyParams,
4573 ) -> SuiResult {
4574 verify_sender_signed_data_message_signatures(
4575 self.data(),
4576 committee.epoch(),
4577 verify_params,
4578 Arc::new(VerifiedDigestCache::new_empty()),
4579 vec![],
4580 )?;
4581
4582 self.auth_sig().verify_secure(
4583 self.data(),
4584 Intent::sui_app(IntentScope::SenderSignedTransaction),
4585 committee,
4586 )
4587 }
4588
4589 pub fn try_into_verified_for_testing(
4590 self,
4591 committee: &Committee,
4592 verify_params: &VerifyParams,
4593 ) -> SuiResult<VerifiedSignedTransaction> {
4594 self.verify_signatures_authenticated_for_testing(committee, verify_params)?;
4595 Ok(VerifiedSignedTransaction::new_from_verified(self))
4596 }
4597}
4598
4599pub type CertifiedTransaction = Envelope<SenderSignedData, AuthorityStrongQuorumSignInfo>;
4600
4601impl CertifiedTransaction {
4602 pub fn gas_price(&self) -> u64 {
4603 self.data().transaction_data().gas_price()
4604 }
4605}
4606
4607pub type VerifiedCertificate = VerifiedEnvelope<SenderSignedData, AuthorityStrongQuorumSignInfo>;
4608pub type TrustedCertificate = TrustedEnvelope<SenderSignedData, AuthorityStrongQuorumSignInfo>;
4609
4610#[derive(Clone, Debug, Serialize, Deserialize)]
4611pub struct WithAliases<T>(
4612 T,
4613 #[serde(with = "nonempty_as_vec")] NonEmpty<(u8, Option<SequenceNumber>)>,
4614);
4615
4616impl<T> WithAliases<T> {
4617 pub fn new(tx: T, aliases: NonEmpty<(u8, Option<SequenceNumber>)>) -> Self {
4618 Self(tx, aliases)
4619 }
4620
4621 pub fn tx(&self) -> &T {
4622 &self.0
4623 }
4624
4625 pub fn aliases(&self) -> &NonEmpty<(u8, Option<SequenceNumber>)> {
4626 &self.1
4627 }
4628
4629 pub fn into_tx(self) -> T {
4630 self.0
4631 }
4632
4633 pub fn into_aliases(self) -> NonEmpty<(u8, Option<SequenceNumber>)> {
4634 self.1
4635 }
4636
4637 pub fn into_inner(self) -> (T, NonEmpty<(u8, Option<SequenceNumber>)>) {
4638 (self.0, self.1)
4639 }
4640}
4641
4642impl<T: Message, S> WithAliases<VerifiedEnvelope<T, S>> {
4643 pub fn serializable(self) -> WithAliases<TrustedEnvelope<T, S>> {
4645 WithAliases(self.0.serializable(), self.1)
4646 }
4647}
4648
4649impl<S> WithAliases<Envelope<SenderSignedData, S>> {
4650 pub fn no_aliases(tx: Envelope<SenderSignedData, S>) -> Self {
4653 let required_signers = tx.intent_message().value.required_signers();
4654 assert_eq!(required_signers.len(), tx.tx_signatures().len());
4655 let no_aliases = required_signers
4656 .iter()
4657 .enumerate()
4658 .map(|(idx, _)| (idx as u8, None))
4659 .collect::<Vec<_>>();
4660 Self::new(
4661 tx,
4662 NonEmpty::from_vec(no_aliases).expect("must have at least one required_signer"),
4663 )
4664 }
4665}
4666
4667impl<S> WithAliases<VerifiedEnvelope<SenderSignedData, S>> {
4668 pub fn no_aliases(tx: VerifiedEnvelope<SenderSignedData, S>) -> Self {
4671 let required_signers = tx.intent_message().value.required_signers();
4672 assert_eq!(required_signers.len(), tx.tx_signatures().len());
4673 let no_aliases = required_signers
4674 .iter()
4675 .enumerate()
4676 .map(|(idx, _)| (idx as u8, None))
4677 .collect::<Vec<_>>();
4678 Self::new(
4679 tx,
4680 NonEmpty::from_vec(no_aliases).expect("must have at least one required_signer"),
4681 )
4682 }
4683}
4684
4685pub type TransactionWithAliases = WithAliases<Transaction>;
4686pub type VerifiedTransactionWithAliases = WithAliases<VerifiedTransaction>;
4687pub type TrustedTransactionWithAliases = WithAliases<TrustedTransaction>;
4688
4689#[derive(Clone, Debug, Serialize, Deserialize)]
4694pub struct DeprecatedWithAliases<T>(
4695 T,
4696 #[serde(with = "nonempty_as_vec")] NonEmpty<(SuiAddress, Option<SequenceNumber>)>,
4697);
4698
4699impl<T> DeprecatedWithAliases<T> {
4700 pub fn into_inner(self) -> (T, NonEmpty<(SuiAddress, Option<SequenceNumber>)>) {
4701 (self.0, self.1)
4702 }
4703}
4704
4705impl<T: Message, S> From<WithAliases<VerifiedEnvelope<T, S>>> for WithAliases<Envelope<T, S>> {
4706 fn from(value: WithAliases<VerifiedEnvelope<T, S>>) -> Self {
4707 Self(value.0.into(), value.1)
4708 }
4709}
4710
4711impl<T: Message, S> From<WithAliases<TrustedEnvelope<T, S>>>
4712 for WithAliases<VerifiedEnvelope<T, S>>
4713{
4714 fn from(value: WithAliases<TrustedEnvelope<T, S>>) -> Self {
4715 Self(value.0.into(), value.1)
4716 }
4717}
4718
4719mod nonempty_as_vec {
4720 use super::*;
4721 use serde::{Deserialize, Deserializer, Serialize, Serializer};
4722
4723 pub fn serialize<S, T>(value: &NonEmpty<T>, serializer: S) -> Result<S::Ok, S::Error>
4724 where
4725 S: Serializer,
4726 T: Serialize,
4727 {
4728 let vec: Vec<&T> = value.iter().collect();
4729 vec.serialize(serializer)
4730 }
4731
4732 pub fn deserialize<'de, D, T>(deserializer: D) -> Result<NonEmpty<T>, D::Error>
4733 where
4734 D: Deserializer<'de>,
4735 T: Deserialize<'de> + Clone,
4736 {
4737 use serde::de::{SeqAccess, Visitor};
4738 use std::fmt;
4739 use std::marker::PhantomData;
4740
4741 struct NonEmptyVisitor<T>(PhantomData<T>);
4742
4743 impl<'de, T> Visitor<'de> for NonEmptyVisitor<T>
4744 where
4745 T: Deserialize<'de> + Clone,
4746 {
4747 type Value = NonEmpty<T>;
4748
4749 fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
4750 formatter.write_str("a non-empty sequence")
4751 }
4752
4753 fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
4754 where
4755 A: SeqAccess<'de>,
4756 {
4757 let head = seq
4758 .next_element()?
4759 .ok_or_else(|| serde::de::Error::custom("empty vector"))?;
4760
4761 let mut tail = Vec::new();
4762 while let Some(elem) = seq.next_element()? {
4763 tail.push(elem);
4764 }
4765
4766 Ok(NonEmpty { head, tail })
4767 }
4768 }
4769
4770 deserializer.deserialize_seq(NonEmptyVisitor(PhantomData))
4771 }
4772}
4773
4774#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
4784pub enum TransactionClaim {
4785 #[deprecated(note = "Use AddressAliasesV2")]
4787 AddressAliases(
4788 #[serde(with = "nonempty_as_vec")] NonEmpty<(SuiAddress, Option<SequenceNumber>)>,
4789 ),
4790
4791 ImmutableInputObjects(Vec<ObjectID>),
4794
4795 AddressAliasesV2(#[serde(with = "nonempty_as_vec")] NonEmpty<(u8, Option<SequenceNumber>)>),
4799}
4800
4801#[derive(Clone, Debug, Serialize, Deserialize)]
4803pub struct TransactionWithClaims<T> {
4804 tx: T,
4805 claims: Vec<TransactionClaim>,
4806}
4807
4808impl<T> TransactionWithClaims<T> {
4809 pub fn new(tx: T, claims: Vec<TransactionClaim>) -> Self {
4810 Self { tx, claims }
4811 }
4812
4813 pub fn from_aliases(tx: T, aliases: NonEmpty<(u8, Option<SequenceNumber>)>) -> Self {
4815 Self {
4816 tx,
4817 claims: vec![TransactionClaim::AddressAliasesV2(aliases)],
4818 }
4819 }
4820
4821 pub fn no_aliases(tx: T) -> Self {
4823 Self { tx, claims: vec![] }
4824 }
4825
4826 pub fn tx(&self) -> &T {
4827 &self.tx
4828 }
4829
4830 pub fn into_tx(self) -> T {
4831 self.tx
4832 }
4833
4834 pub fn aliases(&self) -> Option<NonEmpty<(u8, Option<SequenceNumber>)>> {
4836 self.claims
4837 .iter()
4838 .find_map(|c| match c {
4839 TransactionClaim::AddressAliasesV2(aliases) => Some(aliases),
4840 _ => None,
4841 })
4842 .cloned()
4843 }
4844
4845 #[allow(deprecated)]
4847 pub fn aliases_v1(&self) -> Option<NonEmpty<(SuiAddress, Option<SequenceNumber>)>> {
4848 self.claims
4849 .iter()
4850 .find_map(|c| match c {
4851 TransactionClaim::AddressAliases(aliases) => Some(aliases),
4852 _ => None,
4853 })
4854 .cloned()
4855 }
4856
4857 pub fn get_immutable_objects(&self) -> Vec<ObjectID> {
4859 self.claims
4860 .iter()
4861 .find_map(|c| match c {
4862 TransactionClaim::ImmutableInputObjects(objs) => Some(objs.clone()),
4863 _ => None,
4864 })
4865 .unwrap_or_default()
4866 }
4867}
4868
4869pub type PlainTransactionWithClaims = TransactionWithClaims<Transaction>;
4870
4871impl<T: Message, S> From<WithAliases<VerifiedEnvelope<T, S>>>
4874 for TransactionWithClaims<Envelope<T, S>>
4875{
4876 fn from(value: WithAliases<VerifiedEnvelope<T, S>>) -> Self {
4877 let (tx, aliases) = value.into_inner();
4878 Self::from_aliases(tx.into(), aliases)
4879 }
4880}
4881
4882#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize, PartialOrd, Ord, Hash)]
4883pub enum InputObjectKind {
4884 MovePackage(ObjectID),
4886 ImmOrOwnedMoveObject(ObjectRef),
4888 SharedMoveObject {
4890 id: ObjectID,
4891 initial_shared_version: SequenceNumber,
4892 mutability: SharedObjectMutability,
4893 },
4894}
4895
4896#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize, PartialOrd, Ord, Hash)]
4897pub enum SharedObjectMutability {
4898 Immutable,
4900 Mutable,
4901 NonExclusiveWrite,
4905}
4906
4907impl SharedObjectMutability {
4908 pub fn is_exclusive(&self) -> bool {
4909 match self {
4910 SharedObjectMutability::Mutable => true,
4911 SharedObjectMutability::Immutable => false,
4912 SharedObjectMutability::NonExclusiveWrite => false,
4913 }
4914 }
4915}
4916
4917impl InputObjectKind {
4918 pub fn object_id(&self) -> ObjectID {
4919 self.full_object_id().id()
4920 }
4921
4922 pub fn full_object_id(&self) -> FullObjectID {
4923 match self {
4924 Self::MovePackage(id) => FullObjectID::Fastpath(*id),
4925 Self::ImmOrOwnedMoveObject((id, _, _)) => FullObjectID::Fastpath(*id),
4926 Self::SharedMoveObject {
4927 id,
4928 initial_shared_version,
4929 ..
4930 } => FullObjectID::Consensus((*id, *initial_shared_version)),
4931 }
4932 }
4933
4934 pub fn version(&self) -> Option<SequenceNumber> {
4935 match self {
4936 Self::MovePackage(..) => None,
4937 Self::ImmOrOwnedMoveObject((_, version, _)) => Some(*version),
4938 Self::SharedMoveObject { .. } => None,
4939 }
4940 }
4941
4942 pub fn object_not_found_error(&self) -> UserInputError {
4943 match *self {
4944 Self::MovePackage(package_id) => {
4945 UserInputError::DependentPackageNotFound { package_id }
4946 }
4947 Self::ImmOrOwnedMoveObject((object_id, version, _)) => UserInputError::ObjectNotFound {
4948 object_id,
4949 version: Some(version),
4950 },
4951 Self::SharedMoveObject { id, .. } => UserInputError::ObjectNotFound {
4952 object_id: id,
4953 version: None,
4954 },
4955 }
4956 }
4957
4958 pub fn is_shared_object(&self) -> bool {
4959 matches!(self, Self::SharedMoveObject { .. })
4960 }
4961}
4962
4963#[derive(Clone, Debug)]
4966pub struct ObjectReadResult {
4967 pub input_object_kind: InputObjectKind,
4968 pub object: ObjectReadResultKind,
4969}
4970
4971#[derive(Clone)]
4972pub enum ObjectReadResultKind {
4973 Object(Object),
4974 ObjectConsensusStreamEnded(SequenceNumber, TransactionDigest),
4977 CancelledTransactionSharedObject(SequenceNumber),
4979}
4980
4981impl ObjectReadResultKind {
4982 pub fn is_cancelled(&self) -> bool {
4983 matches!(
4984 self,
4985 ObjectReadResultKind::CancelledTransactionSharedObject(_)
4986 )
4987 }
4988
4989 pub fn version(&self) -> SequenceNumber {
4990 match self {
4991 ObjectReadResultKind::Object(object) => object.version(),
4992 ObjectReadResultKind::ObjectConsensusStreamEnded(seq, _) => *seq,
4993 ObjectReadResultKind::CancelledTransactionSharedObject(seq) => *seq,
4994 }
4995 }
4996}
4997
4998impl std::fmt::Debug for ObjectReadResultKind {
4999 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
5000 match self {
5001 ObjectReadResultKind::Object(obj) => {
5002 write!(f, "Object({:?})", obj.compute_object_reference())
5003 }
5004 ObjectReadResultKind::ObjectConsensusStreamEnded(seq, digest) => {
5005 write!(f, "ObjectConsensusStreamEnded({}, {:?})", seq, digest)
5006 }
5007 ObjectReadResultKind::CancelledTransactionSharedObject(seq) => {
5008 write!(f, "CancelledTransactionSharedObject({})", seq)
5009 }
5010 }
5011 }
5012}
5013
5014impl From<Object> for ObjectReadResultKind {
5015 fn from(object: Object) -> Self {
5016 Self::Object(object)
5017 }
5018}
5019
5020impl ObjectReadResult {
5021 pub fn new(input_object_kind: InputObjectKind, object: ObjectReadResultKind) -> Self {
5022 if let (
5023 InputObjectKind::ImmOrOwnedMoveObject(_),
5024 ObjectReadResultKind::ObjectConsensusStreamEnded(_, _),
5025 ) = (&input_object_kind, &object)
5026 {
5027 panic!("only consensus objects can be ObjectConsensusStreamEnded");
5028 }
5029
5030 if let (
5031 InputObjectKind::ImmOrOwnedMoveObject(_),
5032 ObjectReadResultKind::CancelledTransactionSharedObject(_),
5033 ) = (&input_object_kind, &object)
5034 {
5035 panic!("only consensus objects can be CancelledTransactionSharedObject");
5036 }
5037
5038 Self {
5039 input_object_kind,
5040 object,
5041 }
5042 }
5043
5044 pub fn id(&self) -> ObjectID {
5045 self.input_object_kind.object_id()
5046 }
5047
5048 pub fn as_object(&self) -> Option<&Object> {
5049 match &self.object {
5050 ObjectReadResultKind::Object(object) => Some(object),
5051 ObjectReadResultKind::ObjectConsensusStreamEnded(_, _) => None,
5052 ObjectReadResultKind::CancelledTransactionSharedObject(_) => None,
5053 }
5054 }
5055
5056 pub fn new_from_gas_object(gas: &Object) -> Self {
5057 let objref = gas.compute_object_reference();
5058 Self {
5059 input_object_kind: InputObjectKind::ImmOrOwnedMoveObject(objref),
5060 object: ObjectReadResultKind::Object(gas.clone()),
5061 }
5062 }
5063
5064 pub fn is_mutable(&self) -> bool {
5065 match (&self.input_object_kind, &self.object) {
5066 (InputObjectKind::MovePackage(_), _) => false,
5067 (InputObjectKind::ImmOrOwnedMoveObject(_), ObjectReadResultKind::Object(object)) => {
5068 !object.is_immutable()
5069 }
5070 (
5071 InputObjectKind::ImmOrOwnedMoveObject(_),
5072 ObjectReadResultKind::ObjectConsensusStreamEnded(_, _),
5073 ) => unreachable!(),
5074 (
5075 InputObjectKind::ImmOrOwnedMoveObject(_),
5076 ObjectReadResultKind::CancelledTransactionSharedObject(_),
5077 ) => unreachable!(),
5078 (InputObjectKind::SharedMoveObject { mutability, .. }, _) => match mutability {
5079 SharedObjectMutability::Mutable => true,
5080 SharedObjectMutability::Immutable => false,
5081 SharedObjectMutability::NonExclusiveWrite => false,
5082 },
5083 }
5084 }
5085
5086 pub fn is_shared_object(&self) -> bool {
5087 self.input_object_kind.is_shared_object()
5088 }
5089
5090 pub fn is_consensus_stream_ended(&self) -> bool {
5091 self.consensus_stream_end_info().is_some()
5092 }
5093
5094 pub fn consensus_stream_end_info(&self) -> Option<(SequenceNumber, TransactionDigest)> {
5095 match &self.object {
5096 ObjectReadResultKind::ObjectConsensusStreamEnded(v, tx) => Some((*v, *tx)),
5097 _ => None,
5098 }
5099 }
5100
5101 pub fn get_address_owned_objref(&self) -> Option<ObjectRef> {
5103 match (&self.input_object_kind, &self.object) {
5104 (InputObjectKind::MovePackage(_), _) => None,
5105 (
5106 InputObjectKind::ImmOrOwnedMoveObject(objref),
5107 ObjectReadResultKind::Object(object),
5108 ) => {
5109 if object.is_immutable() {
5110 None
5111 } else {
5112 Some(*objref)
5113 }
5114 }
5115 (
5116 InputObjectKind::ImmOrOwnedMoveObject(_),
5117 ObjectReadResultKind::ObjectConsensusStreamEnded(_, _),
5118 ) => unreachable!(),
5119 (
5120 InputObjectKind::ImmOrOwnedMoveObject(_),
5121 ObjectReadResultKind::CancelledTransactionSharedObject(_),
5122 ) => unreachable!(),
5123 (InputObjectKind::SharedMoveObject { .. }, _) => None,
5124 }
5125 }
5126
5127 pub fn is_address_owned(&self) -> bool {
5128 self.get_address_owned_objref().is_some()
5129 }
5130
5131 pub fn is_replay_protected_input(&self) -> bool {
5132 if let InputObjectKind::ImmOrOwnedMoveObject(obj_ref) = &self.input_object_kind
5133 && ParsedDigest::is_coin_reservation_digest(&obj_ref.2)
5134 {
5135 true
5136 } else {
5137 self.is_address_owned()
5138 }
5139 }
5140
5141 pub fn to_shared_input(&self) -> Option<SharedInput> {
5142 match self.input_object_kind {
5143 InputObjectKind::MovePackage(_) => None,
5144 InputObjectKind::ImmOrOwnedMoveObject(_) => None,
5145 InputObjectKind::SharedMoveObject { id, mutability, .. } => Some(match &self.object {
5146 ObjectReadResultKind::Object(obj) => {
5147 SharedInput::Existing(obj.compute_object_reference())
5148 }
5149 ObjectReadResultKind::ObjectConsensusStreamEnded(seq, digest) => {
5150 SharedInput::ConsensusStreamEnded((id, *seq, mutability, *digest))
5151 }
5152 ObjectReadResultKind::CancelledTransactionSharedObject(seq) => {
5153 SharedInput::Cancelled((id, *seq))
5154 }
5155 }),
5156 }
5157 }
5158
5159 pub fn get_previous_transaction(&self) -> Option<TransactionDigest> {
5160 match &self.object {
5161 ObjectReadResultKind::Object(obj) => Some(obj.previous_transaction),
5162 ObjectReadResultKind::ObjectConsensusStreamEnded(_, digest) => Some(*digest),
5163 ObjectReadResultKind::CancelledTransactionSharedObject(_) => None,
5164 }
5165 }
5166}
5167
5168#[derive(Clone)]
5169pub struct InputObjects {
5170 objects: Vec<ObjectReadResult>,
5171}
5172
5173impl std::fmt::Debug for InputObjects {
5174 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
5175 f.debug_list().entries(self.objects.iter()).finish()
5176 }
5177}
5178
5179#[derive(Clone)]
5182pub struct CheckedInputObjects(InputObjects);
5183
5184impl CheckedInputObjects {
5190 pub fn new_with_checked_transaction_inputs(inputs: InputObjects) -> Self {
5192 Self(inputs)
5193 }
5194
5195 pub fn new_for_genesis(input_objects: Vec<ObjectReadResult>) -> Self {
5197 Self(InputObjects::new(input_objects))
5198 }
5199
5200 pub fn new_for_replay(input_objects: InputObjects) -> Self {
5202 Self(input_objects)
5203 }
5204
5205 pub fn inner(&self) -> &InputObjects {
5206 &self.0
5207 }
5208
5209 pub fn into_inner(self) -> InputObjects {
5210 self.0
5211 }
5212}
5213
5214impl From<Vec<ObjectReadResult>> for InputObjects {
5215 fn from(objects: Vec<ObjectReadResult>) -> Self {
5216 Self::new(objects)
5217 }
5218}
5219
5220impl InputObjects {
5221 pub fn new(objects: Vec<ObjectReadResult>) -> Self {
5222 Self { objects }
5223 }
5224
5225 pub fn len(&self) -> usize {
5226 self.objects.len()
5227 }
5228
5229 pub fn is_empty(&self) -> bool {
5230 self.objects.is_empty()
5231 }
5232
5233 pub fn contains_consensus_stream_ended_objects(&self) -> bool {
5234 self.objects
5235 .iter()
5236 .any(|obj| obj.is_consensus_stream_ended())
5237 }
5238
5239 pub fn get_cancelled_objects(&self) -> Option<(Vec<ObjectID>, SequenceNumber)> {
5242 let mut contains_cancelled = false;
5243 let mut cancel_reason = None;
5244 let mut cancelled_objects = Vec::new();
5245 for obj in &self.objects {
5246 if let ObjectReadResultKind::CancelledTransactionSharedObject(version) = obj.object {
5247 contains_cancelled = true;
5248 if version == SequenceNumber::CONGESTED
5249 || version == SequenceNumber::RANDOMNESS_UNAVAILABLE
5250 {
5251 assert!(cancel_reason.is_none() || cancel_reason == Some(version));
5253 cancel_reason = Some(version);
5254 cancelled_objects.push(obj.id());
5255 }
5256 }
5257 }
5258
5259 if !cancelled_objects.is_empty() {
5260 Some((
5261 cancelled_objects,
5262 cancel_reason
5263 .expect("there should be a cancel reason if there are cancelled objects"),
5264 ))
5265 } else {
5266 assert!(!contains_cancelled);
5267 None
5268 }
5269 }
5270
5271 pub fn filter_owned_objects(&self) -> Vec<ObjectRef> {
5272 let owned_objects: Vec<_> = self
5273 .objects
5274 .iter()
5275 .filter_map(|obj| obj.get_address_owned_objref())
5276 .collect();
5277
5278 trace!(
5279 num_mutable_objects = owned_objects.len(),
5280 "Checked locks and found mutable objects"
5281 );
5282
5283 owned_objects
5284 }
5285
5286 pub fn filter_shared_objects(&self) -> Vec<SharedInput> {
5287 self.objects
5288 .iter()
5289 .filter(|obj| obj.is_shared_object())
5290 .map(|obj| {
5291 obj.to_shared_input()
5292 .expect("already filtered for shared objects")
5293 })
5294 .collect()
5295 }
5296
5297 pub fn transaction_dependencies(&self) -> BTreeSet<TransactionDigest> {
5298 self.objects
5299 .iter()
5300 .filter_map(|obj| obj.get_previous_transaction())
5301 .collect()
5302 }
5303
5304 pub fn exclusive_mutable_inputs(&self) -> BTreeMap<ObjectID, (VersionDigest, Owner)> {
5307 self.mutables_with_input_kinds()
5308 .filter_map(|(id, (version, owner, kind))| match kind {
5309 InputObjectKind::SharedMoveObject { mutability, .. } => match mutability {
5310 SharedObjectMutability::Mutable => Some((id, (version, owner))),
5311 SharedObjectMutability::Immutable => None,
5312 SharedObjectMutability::NonExclusiveWrite => None,
5313 },
5314 _ => Some((id, (version, owner))),
5315 })
5316 .collect()
5317 }
5318
5319 pub fn non_exclusive_input_objects(&self) -> BTreeMap<ObjectID, Object> {
5320 self.objects
5321 .iter()
5322 .filter_map(|read_result| {
5323 match (read_result.as_object(), read_result.input_object_kind) {
5324 (
5325 Some(object),
5326 InputObjectKind::SharedMoveObject {
5327 mutability: SharedObjectMutability::NonExclusiveWrite,
5328 ..
5329 },
5330 ) => Some((read_result.id(), object.clone())),
5331 _ => None,
5332 }
5333 })
5334 .collect()
5335 }
5336
5337 pub fn all_mutable_inputs(&self) -> BTreeMap<ObjectID, (VersionDigest, Owner)> {
5340 self.mutables_with_input_kinds()
5341 .filter_map(|(id, (version, owner, kind))| match kind {
5342 InputObjectKind::SharedMoveObject { mutability, .. } => match mutability {
5343 SharedObjectMutability::Mutable => Some((id, (version, owner))),
5344 SharedObjectMutability::Immutable => None,
5345 SharedObjectMutability::NonExclusiveWrite => Some((id, (version, owner))),
5346 },
5347 _ => Some((id, (version, owner))),
5348 })
5349 .collect()
5350 }
5351
5352 fn mutables_with_input_kinds(
5353 &self,
5354 ) -> impl Iterator<Item = (ObjectID, (VersionDigest, Owner, InputObjectKind))> + '_ {
5355 self.objects.iter().filter_map(
5356 |ObjectReadResult {
5357 input_object_kind,
5358 object,
5359 }| match (input_object_kind, object) {
5360 (InputObjectKind::MovePackage(_), _) => None,
5361 (
5362 InputObjectKind::ImmOrOwnedMoveObject(object_ref),
5363 ObjectReadResultKind::Object(object),
5364 ) => {
5365 if object.is_immutable() {
5366 None
5367 } else {
5368 Some((
5369 object_ref.0,
5370 (
5371 (object_ref.1, object_ref.2),
5372 object.owner.clone(),
5373 *input_object_kind,
5374 ),
5375 ))
5376 }
5377 }
5378 (
5379 InputObjectKind::ImmOrOwnedMoveObject(_),
5380 ObjectReadResultKind::ObjectConsensusStreamEnded(_, _),
5381 ) => {
5382 unreachable!()
5383 }
5384 (
5385 InputObjectKind::SharedMoveObject { .. },
5386 ObjectReadResultKind::ObjectConsensusStreamEnded(_, _),
5387 ) => None,
5388 (
5389 InputObjectKind::SharedMoveObject { mutability, .. },
5390 ObjectReadResultKind::Object(object),
5391 ) => match *mutability {
5392 SharedObjectMutability::Mutable => {
5393 let oref = object.compute_object_reference();
5394 Some((
5395 oref.0,
5396 ((oref.1, oref.2), object.owner.clone(), *input_object_kind),
5397 ))
5398 }
5399 SharedObjectMutability::Immutable => None,
5400 SharedObjectMutability::NonExclusiveWrite => {
5401 let oref = object.compute_object_reference();
5402 Some((
5403 oref.0,
5404 ((oref.1, oref.2), object.owner.clone(), *input_object_kind),
5405 ))
5406 }
5407 },
5408 (
5409 InputObjectKind::ImmOrOwnedMoveObject(_),
5410 ObjectReadResultKind::CancelledTransactionSharedObject(_),
5411 ) => {
5412 unreachable!()
5413 }
5414 (
5415 InputObjectKind::SharedMoveObject { .. },
5416 ObjectReadResultKind::CancelledTransactionSharedObject(_),
5417 ) => None,
5418 },
5419 )
5420 }
5421
5422 pub fn lamport_timestamp(&self, receiving_objects: &[ObjectRef]) -> SequenceNumber {
5426 let input_versions = self
5427 .objects
5428 .iter()
5429 .filter_map(|object| match &object.object {
5430 ObjectReadResultKind::Object(object) => {
5431 object.data.try_as_move().map(MoveObject::version)
5432 }
5433 ObjectReadResultKind::ObjectConsensusStreamEnded(v, _) => Some(*v),
5434 ObjectReadResultKind::CancelledTransactionSharedObject(_) => None,
5435 })
5436 .chain(receiving_objects.iter().map(|object_ref| object_ref.1));
5437
5438 SequenceNumber::lamport_increment(input_versions)
5439 }
5440
5441 pub fn object_kinds(&self) -> impl Iterator<Item = &InputObjectKind> {
5442 self.objects.iter().map(
5443 |ObjectReadResult {
5444 input_object_kind, ..
5445 }| input_object_kind,
5446 )
5447 }
5448
5449 pub fn consensus_stream_ended_objects(&self) -> BTreeMap<ObjectID, SequenceNumber> {
5450 self.objects
5451 .iter()
5452 .filter_map(|obj| {
5453 if let InputObjectKind::SharedMoveObject {
5454 id,
5455 initial_shared_version,
5456 ..
5457 } = obj.input_object_kind
5458 {
5459 obj.is_consensus_stream_ended()
5460 .then_some((id, initial_shared_version))
5461 } else {
5462 None
5463 }
5464 })
5465 .collect()
5466 }
5467
5468 pub fn into_object_map(self) -> BTreeMap<ObjectID, Object> {
5469 self.objects
5470 .into_iter()
5471 .filter_map(|o| o.as_object().map(|object| (o.id(), object.clone())))
5472 .collect()
5473 }
5474
5475 pub fn push(&mut self, object: ObjectReadResult) {
5476 self.objects.push(object);
5477 }
5478
5479 pub fn iter(&self) -> impl Iterator<Item = &ObjectReadResult> {
5480 self.objects.iter()
5481 }
5482
5483 pub fn iter_objects(&self) -> impl Iterator<Item = &Object> {
5484 self.objects.iter().filter_map(|o| o.as_object())
5485 }
5486
5487 pub fn non_exclusive_mutable_inputs(
5488 &self,
5489 ) -> impl Iterator<Item = (ObjectID, SequenceNumber)> + '_ {
5490 self.objects.iter().filter_map(
5491 |ObjectReadResult {
5492 input_object_kind,
5493 object,
5494 }| match input_object_kind {
5495 InputObjectKind::SharedMoveObject {
5499 id,
5500 mutability: SharedObjectMutability::NonExclusiveWrite,
5501 ..
5502 } if !object.is_cancelled() => Some((*id, object.version())),
5503 _ => None,
5504 },
5505 )
5506 }
5507}
5508
5509#[derive(Clone, Debug)]
5513pub enum ReceivingObjectReadResultKind {
5514 Object(Object),
5515 PreviouslyReceivedObject,
5517}
5518
5519impl ReceivingObjectReadResultKind {
5520 pub fn as_object(&self) -> Option<&Object> {
5521 match &self {
5522 Self::Object(object) => Some(object),
5523 Self::PreviouslyReceivedObject => None,
5524 }
5525 }
5526}
5527
5528pub struct ReceivingObjectReadResult {
5529 pub object_ref: ObjectRef,
5530 pub object: ReceivingObjectReadResultKind,
5531}
5532
5533impl ReceivingObjectReadResult {
5534 pub fn new(object_ref: ObjectRef, object: ReceivingObjectReadResultKind) -> Self {
5535 Self { object_ref, object }
5536 }
5537
5538 pub fn is_previously_received(&self) -> bool {
5539 matches!(
5540 self.object,
5541 ReceivingObjectReadResultKind::PreviouslyReceivedObject
5542 )
5543 }
5544}
5545
5546impl From<Object> for ReceivingObjectReadResultKind {
5547 fn from(object: Object) -> Self {
5548 Self::Object(object)
5549 }
5550}
5551
5552pub struct ReceivingObjects {
5553 pub objects: Vec<ReceivingObjectReadResult>,
5554}
5555
5556impl ReceivingObjects {
5557 pub fn iter(&self) -> impl Iterator<Item = &ReceivingObjectReadResult> {
5558 self.objects.iter()
5559 }
5560
5561 pub fn iter_objects(&self) -> impl Iterator<Item = &Object> {
5562 self.objects.iter().filter_map(|o| o.object.as_object())
5563 }
5564}
5565
5566impl From<Vec<ReceivingObjectReadResult>> for ReceivingObjects {
5567 fn from(objects: Vec<ReceivingObjectReadResult>) -> Self {
5568 Self { objects }
5569 }
5570}
5571
5572impl Display for CertifiedTransaction {
5573 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
5574 let mut writer = String::new();
5575 writeln!(writer, "Transaction Hash: {:?}", self.digest())?;
5576 writeln!(
5577 writer,
5578 "Signed Authorities Bitmap : {:?}",
5579 self.auth_sig().signers_map
5580 )?;
5581 write!(writer, "{}", &self.data().intent_message().value.kind())?;
5582 write!(f, "{}", writer)
5583 }
5584}
5585
5586#[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
5590pub enum TransactionKey {
5591 Digest(TransactionDigest),
5592 RandomnessRound(EpochId, RandomnessRound),
5593 AccumulatorSettlement(EpochId, u64 ),
5594 ConsensusCommitPrologue(EpochId, u64 , u32 ),
5595}
5596
5597impl TransactionKey {
5598 pub fn unwrap_digest(&self) -> &TransactionDigest {
5599 match self {
5600 TransactionKey::Digest(d) => d,
5601 _ => panic!("called unwrap_digest on a non-Digest TransactionKey: {self:?}"),
5602 }
5603 }
5604
5605 pub fn as_digest(&self) -> Option<&TransactionDigest> {
5606 match self {
5607 TransactionKey::Digest(d) => Some(d),
5608 _ => None,
5609 }
5610 }
5611}