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