af_ptbuilder/
lib.rs

1#![cfg_attr(all(doc, not(doctest)), feature(doc_auto_cfg))]
2
3//! Builder for programmable transactions.
4//!
5//! Check out the [`ptb`](crate::ptb) and [`ptbuilder`](crate::ptbuilder) macros for an ergonomic
6//! way of building transactions, or
7//! [`ProgrammableTransactionBuilder`](crate::ProgrammableTransactionBuilder) for a macro-less
8//! approach.
9
10#[doc(no_inline)]
11pub use af_sui_types::Argument;
12#[doc(hidden)]
13pub use af_sui_types::IdentStr;
14#[doc(no_inline)]
15pub use af_sui_types::MoveCall;
16#[doc(inline)]
17pub use af_sui_types::ObjectArg;
18#[doc(no_inline)]
19pub use af_sui_types::ObjectId;
20#[doc(no_inline)]
21pub use af_sui_types::TypeTag;
22use af_sui_types::{Identifier, ProgrammableTransaction};
23use indexmap::IndexMap;
24use serde::{Deserialize, Serialize};
25use sui_sdk_types::Input;
26
27#[cfg(test)]
28mod tests;
29
30pub type Result<T> = ::std::result::Result<T, Error>;
31
32#[derive(thiserror::Error, Debug)]
33pub enum Error {
34    #[error("Serializing to BCS: {0}")]
35    Bcs(#[from] bcs::Error),
36
37    #[error("invariant violation! object has pure argument")]
38    ObjInvariantViolation,
39
40    #[error("invariant violation! object has id does not match call arg")]
41    InvalidObjArgUpdate,
42
43    #[error(transparent)]
44    MismatchedObjArgKinds(Box<MismatchedObjArgKindsError>),
45}
46
47#[derive(thiserror::Error, Debug)]
48#[error(
49    "Mismatched Object argument kind for object {id}. \
50        {old_value:?} is not compatible with {new_value:?}"
51)]
52pub struct MismatchedObjArgKindsError {
53    pub id: ObjectId,
54    pub old_value: Input,
55    pub new_value: Input,
56}
57
58/// Builder for a [`ProgrammableTransaction`].
59#[derive(Clone, Debug, Default)]
60pub struct ProgrammableTransactionBuilder {
61    inputs: IndexMap<BuilderArg, Input>,
62    commands: Vec<af_sui_types::Command>,
63}
64
65/// Base API.
66impl ProgrammableTransactionBuilder {
67    pub fn new() -> Self {
68        Self::default()
69    }
70
71    pub fn finish(self) -> ProgrammableTransaction {
72        let Self { inputs, commands } = self;
73        let inputs = inputs.into_values().collect();
74        ProgrammableTransaction { inputs, commands }
75    }
76
77    /// Potentially adds a pure argument to the PTB.
78    ///
79    /// May not create a new PTB input if a previous one already has the same contents.
80    pub fn pure<T: Serialize + ?Sized>(&mut self, value: &T) -> Result<Argument> {
81        Ok(self.pure_bytes(bcs::to_bytes(value)?, false))
82    }
83
84    /// Like [`Self::pure`] but forces a separate input entry
85    pub fn force_separate_pure<T: Serialize>(&mut self, value: T) -> Result<Argument> {
86        Ok(self.pure_bytes(bcs::to_bytes(&value)?, true))
87    }
88
89    /// Adds a pure argument to the PTB.
90    ///
91    /// # Arguments
92    /// - `bytes`: the BCS-serialized contents of the argument
93    /// - `force_separate`: whether to force a separate input argument to the PTB, else the builder
94    ///   re-uses a previously declared input argument if it has the same contents.
95    pub fn pure_bytes(&mut self, bytes: Vec<u8>, force_separate: bool) -> Argument {
96        let key = if force_separate {
97            BuilderArg::ForcedNonUniquePure(self.inputs.len())
98        } else {
99            BuilderArg::Pure(bytes.clone())
100        };
101        let (i, _) = self.inputs.insert_full(key, Input::Pure { value: bytes });
102        Argument::Input(i as u16)
103    }
104
105    /// Adds an object input to the PTB, returning the corresponding argument which can be used in
106    /// the body.
107    ///
108    /// May fail if overriding a previously declared input.
109    pub fn obj(&mut self, obj_arg: ObjectArg) -> Result<Argument> {
110        let id = obj_arg.id();
111        let key = BuilderArg::Object(id);
112        let mut input_arg = obj_arg.into();
113
114        if let Some(old_value) = self.inputs.get(&key) {
115            // Check if the key hash didn't collide with a previous pure input
116            if matches!(old_value, Input::Pure { .. }) {
117                return Err(Error::ObjInvariantViolation);
118            }
119
120            input_arg = match (old_value, input_arg) {
121                // The only update allowed: changing the `mutable` flag for a shared object input
122                (
123                    Input::Shared {
124                        object_id: id1,
125                        initial_shared_version: v1,
126                        mutable: mut1,
127                    },
128                    Input::Shared {
129                        object_id: id2,
130                        initial_shared_version: v2,
131                        mutable: mut2,
132                    },
133                ) if v1 == &v2 => {
134                    if id1 != &id2 {
135                        return Err(Error::InvalidObjArgUpdate);
136                    }
137                    Input::Shared {
138                        object_id: id2,
139                        initial_shared_version: v2,
140                        mutable: *mut1 || mut2,
141                    }
142                }
143
144                // Changing anything else about an existing object input is disallowed
145                (old_value, new_value) if old_value != &new_value => {
146                    return Err(Error::MismatchedObjArgKinds(Box::new(
147                        MismatchedObjArgKindsError {
148                            id,
149                            old_value: old_value.clone(),
150                            new_value,
151                        },
152                    )));
153                }
154
155                // If we already declared this exact same object input in the transaction, it will
156                // be automatically reused
157                (_, new_value) => new_value,
158            };
159        }
160
161        let (i, _) = self.inputs.insert_full(key, input_arg);
162        Ok(Argument::Input(i as u16))
163    }
164
165    /// Add a command to the PTB.
166    ///
167    /// This will come after any commands that were previously declared.
168    pub fn command(&mut self, command: impl Into<af_sui_types::Command>) -> Argument {
169        let i = self.commands.len();
170        self.commands.push(command.into());
171        Argument::Result(i as u16)
172    }
173}
174
175/// Extensions to the base API.
176impl ProgrammableTransactionBuilder {
177    /// Like `.command(Command::SplitCoins(coin_arg, balances))`, but also takes care of unpacking
178    /// each entry in the returned vector as its own [`Argument`].
179    ///
180    /// # Panics
181    ///
182    /// Panics if the `balances` input vector has a length that exceeds [`u16::MAX`].
183    pub fn split_coins_into_vec(
184        &mut self,
185        coin: Argument,
186        amounts: Vec<Argument>,
187    ) -> Vec<Argument> {
188        let idxs = 0..amounts.len() as u16;
189        let Argument::Result(coin_vec) = self.command(Command::SplitCoins(coin, amounts)) else {
190            panic!("ProgrammableTransactionBuilder::command always gives an Argument::Result")
191        };
192        idxs.map(|i| Argument::NestedResult(coin_vec, i)).collect()
193    }
194}
195
196#[derive(Clone, Debug, PartialEq, Eq, Hash)]
197enum BuilderArg {
198    Object(ObjectId),
199    Pure(Vec<u8>),
200    ForcedNonUniquePure(usize),
201}
202
203impl From<ProgrammableTransactionBuilder> for ProgrammableTransaction {
204    fn from(value: ProgrammableTransactionBuilder) -> Self {
205        value.finish()
206    }
207}
208
209impl TryFrom<ProgrammableTransaction> for ProgrammableTransactionBuilder {
210    type Error = Error;
211
212    fn try_from(
213        ProgrammableTransaction { inputs, commands }: ProgrammableTransaction,
214    ) -> Result<Self> {
215        use Input::*;
216        let mut self_ = Self::new();
217        for input in inputs {
218            match input {
219                Pure { value } => {
220                    self_.pure_bytes(value, true);
221                }
222                ImmutableOrOwned(oref) => {
223                    self_.obj(ObjectArg::ImmOrOwnedObject(oref.into_parts()))?;
224                }
225                Shared {
226                    object_id,
227                    initial_shared_version,
228                    mutable,
229                } => {
230                    self_.obj(ObjectArg::SharedObject {
231                        id: object_id,
232                        initial_shared_version,
233                        mutable,
234                    })?;
235                }
236                Receiving(oref) => {
237                    self_.obj(ObjectArg::Receiving(oref.into_parts()))?;
238                }
239            }
240        }
241        for command in commands {
242            self_.command(command);
243        }
244        Ok(self_)
245    }
246}
247
248// =============================================================================
249//  Command compat for migration
250// =============================================================================
251
252/// A single command in a programmable transaction.
253///
254/// This type is here for backwards compatibility purposes, as [`sui_sdk_types::Command`]
255/// has a different shape that would be incompatible with the [`ptb!`] syntax.
256///
257/// The actual resulting [`ProgrammableTransaction`] does not contain this type.
258#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
259pub enum Command {
260    /// A call to either an entry or a public Move function.
261    ///
262    /// Either an entry function or a public function (which cannot return references).
263    MoveCall(Box<MoveCall>),
264    /// `(Vec<forall T:key+store. T>, address)`
265    /// It sends n-objects to the specified address. These objects must have store
266    /// (public transfer) and either the previous owner must be an address or the object must
267    /// be newly created.
268    TransferObjects(Vec<Argument>, Argument),
269    /// `(&mut Coin<T>, Vec<u64>)` -> `Vec<Coin<T>>`
270    /// It splits off some amounts into a new coins with those amounts
271    SplitCoins(Argument, Vec<Argument>),
272    /// `(&mut Coin<T>, Vec<Coin<T>>)`
273    /// It merges n-coins into the first coin
274    MergeCoins(Argument, Vec<Argument>),
275    /// Publishes a Move package. It takes the package bytes and a list of the package's transitive
276    /// dependencies to link against on-chain.
277    Publish(Vec<Vec<u8>>, Vec<ObjectId>),
278    /// `forall T: Vec<T> -> vector<T>`
279    /// Given n-values of the same type, it constructs a vector. For non objects or an empty vector,
280    /// the type tag must be specified.
281    MakeMoveVec(Option<TypeTag>, Vec<Argument>),
282    /// Upgrades a Move package
283    /// Takes (in order):
284    /// 1. A vector of serialized modules for the package.
285    /// 2. A vector of object ids for the transitive dependencies of the new package.
286    /// 3. The object ID of the package being upgraded.
287    /// 4. An argument holding the `UpgradeTicket` that must have been produced from an earlier command in the same
288    ///    programmable transaction.
289    Upgrade(Vec<Vec<u8>>, Vec<ObjectId>, ObjectId, Argument),
290}
291
292impl From<af_sui_types::Command> for Command {
293    fn from(value: af_sui_types::Command) -> Self {
294        use af_sui_types::Command::*;
295        match value {
296            MoveCall(args) => Self::MoveCall(Box::new(args)),
297            TransferObjects(args) => Self::TransferObjects(args.objects, args.address),
298            SplitCoins(args) => Self::SplitCoins(args.coin, args.amounts),
299            MergeCoins(args) => Self::MergeCoins(args.coin, args.coins_to_merge),
300            Publish(args) => Self::Publish(args.modules, args.dependencies),
301            MakeMoveVector(args) => Self::MakeMoveVec(args.type_, args.elements),
302            Upgrade(args) => {
303                Self::Upgrade(args.modules, args.dependencies, args.package, args.ticket)
304            }
305        }
306    }
307}
308
309impl From<Command> for af_sui_types::Command {
310    fn from(value: Command) -> Self {
311        use Command::*;
312        use sui_sdk_types::{
313            MakeMoveVector,
314            MergeCoins,
315            Publish,
316            SplitCoins,
317            TransferObjects,
318            Upgrade,
319        };
320        match value {
321            MoveCall(move_call) => Self::MoveCall(*move_call),
322            TransferObjects(objects, address) => {
323                Self::TransferObjects(TransferObjects { objects, address })
324            }
325            SplitCoins(coin, amounts) => Self::SplitCoins(SplitCoins { coin, amounts }),
326            MergeCoins(coin, coins_to_merge) => Self::MergeCoins(MergeCoins {
327                coin,
328                coins_to_merge,
329            }),
330            Publish(modules, dependencies) => Self::Publish(Publish {
331                modules,
332                dependencies,
333            }),
334            MakeMoveVec(type_, elements) => {
335                Self::MakeMoveVector(MakeMoveVector { type_, elements })
336            }
337            Upgrade(modules, dependencies, package, ticket) => Self::Upgrade(Upgrade {
338                modules,
339                dependencies,
340                package,
341                ticket,
342            }),
343        }
344    }
345}
346
347impl Command {
348    pub fn move_call(
349        package: ObjectId,
350        module: Identifier,
351        function: Identifier,
352        type_arguments: Vec<TypeTag>,
353        arguments: Vec<Argument>,
354    ) -> Self {
355        Self::MoveCall(Box::new(MoveCall {
356            package,
357            module,
358            function,
359            type_arguments,
360            arguments,
361        }))
362    }
363
364    pub const fn make_move_vec(ty: Option<TypeTag>, args: Vec<Argument>) -> Self {
365        Self::MakeMoveVec(ty, args)
366    }
367}
368
369// =============================================================================
370//  Macro helper
371// =============================================================================
372
373/// Build a programmable transaction using Move-like syntax.
374///
375/// # Overview
376///
377/// This automatically creates and finishes a [`ProgrammableTransactionBuilder`] and allows users
378/// to declare:
379/// - packages the transaction uses
380/// - type arguments for functions
381/// - object/pure inputs for the transaction
382/// - Move calls
383/// - Built-in PTB commands
384///
385/// Every Move call and built-in PTB command declared withing the macro's scope can be thought of
386/// as happening in 'programmable transaction time'. In this way, the macro also helps users more
387/// clearly separate what's being executed at Rust's runtime and chain's runtime (once the
388/// transaction is execute by validators).
389///
390/// ## Packages
391///
392/// Move functions expect the [`ObjectId`] of their package in the transaction payload (see
393/// [`MoveCall`]). One can declare the packages using the syntax
394/// ```no_run
395/// # use af_sui_types::ObjectId;
396/// let package_name = ObjectId::new(rand::random());
397/// let object_id = ObjectId::new(rand::random());
398/// af_ptbuilder::ptb!(
399///     package package_name;
400///     package package_name: object_id;
401/// // ...
402/// );
403/// ```
404/// Similar to struct initialization syntax;
405///
406/// ## Type arguments
407///
408/// Move functions that have type arguments expect [`TypeTag`] arguments in the transaction payload
409/// (see [`MoveCall`]). One can declare these variables using the syntax
410/// ```no_run
411/// # use af_sui_types::TypeTag;
412/// let T = TypeTag::U8;
413/// let type_tag = TypeTag::U32;
414/// af_ptbuilder::ptb!(
415///     type T;
416///     type T = type_tag;
417/// // ...
418/// );
419/// ```
420///
421/// ## Object/Pure inputs
422///
423/// [`ProgrammableTransaction`]s need all their inputs declared upfront. One can
424/// declare the two types of inputs using the syntax
425/// ```no_run
426/// # use af_sui_types::ObjectArg;
427/// # use af_sui_types::ObjectId;
428/// let clock = ObjectArg::CLOCK_IMM;
429/// let object = ObjectArg::SharedObject {
430///     id: ObjectId::new(rand::random()),
431///     initial_shared_version: 1,
432///     mutable: true
433/// };
434/// let count = &0_u64;
435/// af_ptbuilder::ptb!(
436///     input obj clock;
437///     input obj another: object;
438///     input pure count;
439///     input pure more: &1_u32;
440///     // ...
441/// );
442/// # eyre::Ok(())
443/// ```
444/// Similar to struct initialization syntax. `input obj`s expect [`ObjectArg`] values and
445/// become object [`Input`]s in the transaction payload. `input pure`s expect any type `T` that
446/// is [`Serialize`] `+ ?Sized` (see [`ProgrammableTransactionBuilder::pure`] for the internals) and
447/// become [`Input::Pure`]s in the transaction payload. Within the macro scope, both variables
448/// are [`Argument::Input`]s and can be used in Move/built-in calls.
449///
450/// ## Move calls
451///
452/// Use the syntax
453/// ```no_run
454/// # af_ptbuilder::ptb!(
455/// # package package: af_sui_types::ObjectId::new(rand::random());
456/// # type T = af_sui_types::TypeTag::U8;
457/// # input pure arg: &0_u32;
458///     package::module::function<T>(arg);
459/// # );
460/// # eyre::Ok(())
461/// ````
462/// To include a [`MoveCall`] in the transaction payload. `package`,`T`, and `arg`
463/// must have been declared earlier. `module` and `function` are simply pure identifiers[^1]. One
464/// can of course declare more than one type argument if the function requires, or none if the
465/// function does not have type parameters.
466///
467/// Functions that return can have their results assigned to a value or unpacked into several ones:
468/// ```no_run
469/// # use af_sui_types::ObjectArg;
470/// # use af_sui_types::ObjectId;
471/// # let clock = ObjectArg::CLOCK_IMM;
472/// # af_ptbuilder::ptb!(
473/// # package package: ObjectId::new(rand::random());
474/// # input obj a: clock;
475/// # input obj b: clock;
476/// # input obj arg: clock;
477/// let result = package::module::function(a, b);
478/// let (a, b) = package::module::function(arg);
479/// # );
480/// # eyre::Ok(())
481/// ```
482/// These, of course, happen at 'programmable transaction time' and the result are
483/// [`Argument::Result`]s that can be passed to other functions.
484///
485/// ## Built-in commands
486///
487/// Sui PTBs have access to some calls that do not declare a package, module and function. These
488/// use the syntax:
489/// ```text
490/// command! Variant(x, y, ...);
491/// ```
492/// The result of the command can be optionally assigned or unpacked (`let a =` or
493/// `let (a, b) =`). `Variant` refers to the variant of [`Command`] to use. See its
494/// documentation for more information.
495///
496/// # Example
497///
498/// ```no_run
499/// use af_ptbuilder::ptb;
500/// use af_sui_types::{
501///     IdentStr,
502///     ObjectId,
503///     StructTag,
504///     Address,
505///     TypeTag
506/// };
507/// use af_sui_types::ObjectArg;
508///
509/// let foo = ObjectId::new(rand::random());
510/// let otw = TypeTag::Struct(Box::new(StructTag {
511///     address: "0x2".parse()?,
512///     module: IdentStr::cast("sui").to_owned(),
513///     name: IdentStr::cast("SUI").to_owned(),
514///     type_params: vec![],
515/// }));
516/// let registry = ObjectArg::SharedObject {
517///     id: ObjectId::new(rand::random()),
518///     initial_shared_version: 1,
519///     mutable: true,
520/// };
521/// let sender = Address::new(rand::random());
522///
523/// ptb!(
524///     package foo;
525///
526///     type T = otw;
527///
528///     input obj registry;
529///     input pure sender: &sender;
530///
531///     let account = foo::registry::create_account<T>(registry);
532///     command! TransferObjects(vec![account], sender);
533/// );
534/// # eyre::Ok(())
535/// ```
536///
537/// [^1]: [`Identifier`]
538#[macro_export]
539macro_rules! ptb {
540    ($($tt:tt)*) => {
541        {
542            let mut builder = $crate::ProgrammableTransactionBuilder::new();
543            $crate::ptbuilder!(builder { $($tt)* });
544            builder.finish()
545        }
546    };
547}
548
549/// Build a programmable transaction using Move-like syntax and an existing builder.
550///
551/// This will make the package, type, input and argument variables declared inside the macro
552/// available in the outer scope.
553///
554/// # Overview
555///
556/// This allows users to incrementally build a programmable transaction using an existing
557/// [`ProgrammableTransactionBuilder`] with the syntax
558/// ```no_run
559/// # use af_ptbuilder::ProgrammableTransactionBuilder;
560/// let mut builder = ProgrammableTransactionBuilder::new();
561/// af_ptbuilder::ptbuilder!(builder {
562///     // ...
563/// });
564/// ```
565/// where everything inside the braces uses the same syntax as [`ptb!`]. The user is responsible
566/// for initializing the builder and calling [`ProgrammableTransactionBuilder::finish`] at the end.
567///
568/// This can be useful if the number of calls is only known at runtime or if it's desirable to only
569/// include some calls based on some runtime logic. It still allows users to use a convenient
570/// syntax and separate what happens at 'programmable transaction time'.
571#[macro_export]
572macro_rules! ptbuilder {
573    ($builder:ident {}) => { };
574
575    ($builder:ident {
576        package $name:ident $value:literal;
577        $($tt:tt)*
578    }) => {
579        let $name: $crate::ObjectId = $value.parse()?;
580
581        $crate::ptbuilder!($builder { $($tt)* });
582    };
583
584    ($builder:ident {
585        package $name:ident;
586        $($tt:tt)*
587    }) => {
588        let $name: $crate::ObjectId = $name;
589
590        $crate::ptbuilder!($builder { $($tt)* });
591    };
592
593    ($builder:ident {
594        package $name:ident: $value:expr_2021;
595        $($tt:tt)*
596    }) => {
597        let $name: $crate::ObjectId = $value;
598
599        $crate::ptbuilder!($builder { $($tt)* });
600    };
601
602    ($builder:ident {
603        input pure $name:ident;
604        $($tt:tt)*
605    }) => {
606        let $name = $builder.pure($name)?;
607
608        $crate::ptbuilder!($builder { $($tt)* });
609    };
610
611    ($builder:ident {
612        input pure $name:ident: $value:expr_2021;
613        $($tt:tt)*
614    }) => {
615        let $name = $builder.pure($value)?;
616
617        $crate::ptbuilder!($builder { $($tt)* });
618    };
619
620    ($builder:ident {
621        input obj $name:ident;
622        $($tt:tt)*
623    }) => {
624        let $name = $builder.obj($name)?;
625
626        $crate::ptbuilder!($builder { $($tt)* });
627    };
628
629    ($builder:ident {
630        input obj $name:ident: $value:expr_2021;
631        $($tt:tt)*
632    }) => {
633        let $name = $builder.obj($value)?;
634
635        $crate::ptbuilder!($builder { $($tt)* });
636    };
637
638    ($builder:ident {
639        type $T:ident;
640        $($tt:tt)*
641    }) => {
642        #[allow(non_snake_case)]
643        let $T: $crate::TypeTag = $T;
644
645        $crate::ptbuilder!($builder { $($tt)* });
646    };
647
648    ($builder:ident {
649        type $T:ident = $value:expr_2021;
650        $($tt:tt)*
651    }) => {
652        #[allow(non_snake_case)]
653        let $T: $crate::TypeTag = $value;
654
655        $crate::ptbuilder!($builder { $($tt)* });
656    };
657
658    ($builder:ident {
659        $package:ident::$module:ident::$fun:ident$(<$($T:ident),+>)?($($arg:ident),* $(,)?);
660        $($tt:tt)*
661    }) => {
662        let _module = stringify!($module);
663        let _fun = stringify!($fun);
664        $builder.command($crate::Command::move_call(
665            $package,
666            $crate::IdentStr::cast(_module).to_owned(),
667            $crate::IdentStr::cast(_fun).to_owned(),
668            vec![$($($T.clone()),+)?],
669            vec![$($arg),*]
670        ));
671
672        $crate::ptbuilder!($builder { $($tt)* });
673    };
674
675    ($builder:ident {
676        let $ret:ident = $package:ident::$module:ident::$fun:ident$(<$($T:ident),+>)?($($arg:ident),* $(,)?);
677        $($tt:tt)*
678    }) => {
679        let _module = stringify!($module);
680        let _fun = stringify!($fun);
681        let $ret = $builder.command($crate::Command::move_call(
682            $package,
683            $crate::IdentStr::cast(_module).to_owned(),
684            $crate::IdentStr::cast(_fun).to_owned(),
685            vec![$($($T.clone()),+)?],
686            vec![$($arg),*]
687        ));
688
689        $crate::ptbuilder!($builder { $($tt)* });
690    };
691
692    ($builder:ident {
693        let ($($ret:ident),+) = $package:ident::$module:ident::$fun:ident$(<$($T:ident),+>)?($($arg:ident),* $(,)?);
694        $($tt:tt)*
695    }) => {
696        let _module = stringify!($module);
697        let _fun = stringify!($fun);
698        let rets = $builder.command($crate::Command::move_call(
699            $package,
700            $crate::IdentStr::cast(_module).to_owned(),
701            $crate::IdentStr::cast(_fun).to_owned(),
702            vec![$($($T.clone()),+)?],
703            vec![$($arg),*]
704        ));
705        $crate::unpack_arg!(rets => { $($ret),+ });
706
707        $crate::ptbuilder!($builder { $($tt)* });
708    };
709
710    ($builder:ident {
711        $(let $ret:ident =)? command! $variant:ident($($args:tt)*);
712        $($tt:tt)*
713    }) => {
714        $(let $ret =)? $builder.command($crate::Command::$variant($($args)*));
715
716        $crate::ptbuilder!($builder { $($tt)* });
717    };
718
719    ($builder:ident {
720        let ($($ret:ident),+) = command! $variant:ident($($args:tt)*);
721        $($tt:tt)*
722    }) => {
723        let rets = $builder.command($crate::Command::$variant($($args)*));
724        $crate::unpack_arg!(rets => { $($ret),+ });
725
726        $crate::ptbuilder!($builder { $($tt)* });
727    };
728}
729
730/// Unpack the result of a programmable transaction call.
731///
732/// Useful for unpacking results from functions that return tuple or vector types.
733///
734/// # Example
735/// ```
736/// use af_ptbuilder::ProgrammableTransactionBuilder;
737/// use af_sui_types::Argument;
738///
739/// let mut builder = ProgrammableTransactionBuilder::new();
740/// let arg = Argument::Result(0);
741/// af_ptbuilder::unpack_arg!(arg => { sub1, sub2 });
742/// ```
743#[macro_export]
744macro_rules! unpack_arg {
745    ($arg:expr_2021 => {
746        $($name:ident),+ $(,)?
747    }) => {
748        let ($($name),+) = if let $crate::Argument::Result(tuple) = $arg {
749            let mut index = 0;
750            $(
751                let $name = $crate::Argument::NestedResult(
752                    tuple, index
753                );
754                index += 1;
755            )+
756            ($($name),+)
757        } else {
758            panic!(
759                "ProgrammableTransactionBuilder::command should always give a Argument::Result"
760            )
761        };
762    };
763}