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::{address, object_id, ObjectArg, TypeTag};
501///
502/// let foo = object_id(b"0xbeef");
503/// let otw: TypeTag = "0x2::sui::SUI".parse()?;
504/// let registry = ObjectArg::SharedObject {
505///     id: object_id(b"0xdeed"),
506///     initial_shared_version: 1,
507///     mutable: true,
508/// };
509/// let sender = address(b"0xabcd");
510///
511/// ptb!(
512///     package foo;
513///
514///     type T = otw;
515///
516///     input obj registry;
517///     input pure sender: &sender;
518///
519///     let account = foo::registry::create_account<T>(registry);
520///     command! TransferObjects(vec![account], sender);
521/// );
522/// # eyre::Ok(())
523/// ```
524///
525/// [^1]: [`Identifier`]
526#[macro_export]
527macro_rules! ptb {
528    ($($tt:tt)*) => {
529        {
530            let mut builder = $crate::ProgrammableTransactionBuilder::new();
531            $crate::ptbuilder!(builder { $($tt)* });
532            builder.finish()
533        }
534    };
535}
536
537/// Build a programmable transaction using Move-like syntax and an existing builder.
538///
539/// This will make the package, type, input and argument variables declared inside the macro
540/// available in the outer scope.
541///
542/// # Overview
543///
544/// This allows users to incrementally build a programmable transaction using an existing
545/// [`ProgrammableTransactionBuilder`] with the syntax
546/// ```no_run
547/// # use af_ptbuilder::ProgrammableTransactionBuilder;
548/// let mut builder = ProgrammableTransactionBuilder::new();
549/// af_ptbuilder::ptbuilder!(builder {
550///     // ...
551/// });
552/// ```
553/// where everything inside the braces uses the same syntax as [`ptb!`]. The user is responsible
554/// for initializing the builder and calling [`ProgrammableTransactionBuilder::finish`] at the end.
555///
556/// This can be useful if the number of calls is only known at runtime or if it's desirable to only
557/// include some calls based on some runtime logic. It still allows users to use a convenient
558/// syntax and separate what happens at 'programmable transaction time'.
559#[macro_export]
560macro_rules! ptbuilder {
561    ($builder:ident {}) => { };
562
563    ($builder:ident {
564        package $name:ident $value:literal;
565        $($tt:tt)*
566    }) => {
567        let $name: $crate::ObjectId = $value.parse()?;
568
569        $crate::ptbuilder!($builder { $($tt)* });
570    };
571
572    ($builder:ident {
573        package $name:ident;
574        $($tt:tt)*
575    }) => {
576        let $name: $crate::ObjectId = $name;
577
578        $crate::ptbuilder!($builder { $($tt)* });
579    };
580
581    ($builder:ident {
582        package $name:ident: $value:expr_2021;
583        $($tt:tt)*
584    }) => {
585        let $name: $crate::ObjectId = $value;
586
587        $crate::ptbuilder!($builder { $($tt)* });
588    };
589
590    ($builder:ident {
591        input pure $name:ident;
592        $($tt:tt)*
593    }) => {
594        let $name = $builder.pure($name)?;
595
596        $crate::ptbuilder!($builder { $($tt)* });
597    };
598
599    ($builder:ident {
600        input pure $name:ident: $value:expr_2021;
601        $($tt:tt)*
602    }) => {
603        let $name = $builder.pure($value)?;
604
605        $crate::ptbuilder!($builder { $($tt)* });
606    };
607
608    ($builder:ident {
609        input obj $name:ident;
610        $($tt:tt)*
611    }) => {
612        let $name = $builder.obj($name)?;
613
614        $crate::ptbuilder!($builder { $($tt)* });
615    };
616
617    ($builder:ident {
618        input obj $name:ident: $value:expr_2021;
619        $($tt:tt)*
620    }) => {
621        let $name = $builder.obj($value)?;
622
623        $crate::ptbuilder!($builder { $($tt)* });
624    };
625
626    ($builder:ident {
627        type $T:ident;
628        $($tt:tt)*
629    }) => {
630        #[allow(non_snake_case)]
631        let $T: $crate::TypeTag = $T;
632
633        $crate::ptbuilder!($builder { $($tt)* });
634    };
635
636    ($builder:ident {
637        type $T:ident = $value:expr_2021;
638        $($tt:tt)*
639    }) => {
640        #[allow(non_snake_case)]
641        let $T: $crate::TypeTag = $value;
642
643        $crate::ptbuilder!($builder { $($tt)* });
644    };
645
646    ($builder:ident {
647        $package:ident::$module:ident::$fun:ident$(<$($T:ident),+>)?($($arg:ident),* $(,)?);
648        $($tt:tt)*
649    }) => {
650        let _module = stringify!($module);
651        let _fun = stringify!($fun);
652        $builder.command($crate::Command::move_call(
653            $package,
654            $crate::IdentStr::cast(_module).to_owned(),
655            $crate::IdentStr::cast(_fun).to_owned(),
656            vec![$($($T.clone()),+)?],
657            vec![$($arg),*]
658        ));
659
660        $crate::ptbuilder!($builder { $($tt)* });
661    };
662
663    ($builder:ident {
664        let $ret:ident = $package:ident::$module:ident::$fun:ident$(<$($T:ident),+>)?($($arg:ident),* $(,)?);
665        $($tt:tt)*
666    }) => {
667        let _module = stringify!($module);
668        let _fun = stringify!($fun);
669        let $ret = $builder.command($crate::Command::move_call(
670            $package,
671            $crate::IdentStr::cast(_module).to_owned(),
672            $crate::IdentStr::cast(_fun).to_owned(),
673            vec![$($($T.clone()),+)?],
674            vec![$($arg),*]
675        ));
676
677        $crate::ptbuilder!($builder { $($tt)* });
678    };
679
680    ($builder:ident {
681        let ($($ret:ident),+) = $package:ident::$module:ident::$fun:ident$(<$($T:ident),+>)?($($arg:ident),* $(,)?);
682        $($tt:tt)*
683    }) => {
684        let _module = stringify!($module);
685        let _fun = stringify!($fun);
686        let rets = $builder.command($crate::Command::move_call(
687            $package,
688            $crate::IdentStr::cast(_module).to_owned(),
689            $crate::IdentStr::cast(_fun).to_owned(),
690            vec![$($($T.clone()),+)?],
691            vec![$($arg),*]
692        ));
693        $crate::unpack_arg!(rets => { $($ret),+ });
694
695        $crate::ptbuilder!($builder { $($tt)* });
696    };
697
698    ($builder:ident {
699        $(let $ret:ident =)? command! $variant:ident($($args:tt)*);
700        $($tt:tt)*
701    }) => {
702        $(let $ret =)? $builder.command($crate::Command::$variant($($args)*));
703
704        $crate::ptbuilder!($builder { $($tt)* });
705    };
706
707    ($builder:ident {
708        let ($($ret:ident),+) = command! $variant:ident($($args:tt)*);
709        $($tt:tt)*
710    }) => {
711        let rets = $builder.command($crate::Command::$variant($($args)*));
712        $crate::unpack_arg!(rets => { $($ret),+ });
713
714        $crate::ptbuilder!($builder { $($tt)* });
715    };
716}
717
718/// Unpack the result of a programmable transaction call.
719///
720/// Useful for unpacking results from functions that return tuple or vector types.
721///
722/// # Example
723/// ```
724/// use af_ptbuilder::ProgrammableTransactionBuilder;
725/// use af_sui_types::Argument;
726///
727/// let mut builder = ProgrammableTransactionBuilder::new();
728/// let arg = Argument::Result(0);
729/// af_ptbuilder::unpack_arg!(arg => { sub1, sub2 });
730/// ```
731#[macro_export]
732macro_rules! unpack_arg {
733    ($arg:expr_2021 => {
734        $($name:ident),+ $(,)?
735    }) => {
736        let ($($name),+) = if let $crate::Argument::Result(tuple) = $arg {
737            let mut index = 0;
738            $(
739                let $name = $crate::Argument::NestedResult(
740                    tuple, index
741                );
742                index += 1;
743            )+
744            ($($name),+)
745        } else {
746            panic!(
747                "ProgrammableTransactionBuilder::command should always give a Argument::Result"
748            )
749        };
750    };
751}