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}