Skip to main content

iota_types/auth_context/
fields_v1.rs

1// Copyright (c) 2026 IOTA Stiftung
2// SPDX-License-Identifier: Apache-2.0
3
4use iota_sdk_types::{Argument, Command, ObjectId, TypeTag};
5use move_core_types::{ident_str, identifier::IdentStr, language_storage::StructTag};
6use serde::{Deserialize, Serialize};
7use serde_with::serde_as;
8
9use crate::{
10    IOTA_FRAMEWORK_ADDRESS,
11    base_types::{ObjectRef, SequenceNumber},
12    iota_serde::TypeName,
13    transaction::CallArg,
14};
15
16// ---------------------------------------------------------------------------
17// Module / struct name constants
18// ---------------------------------------------------------------------------
19
20pub const CALL_ARG_MODULE_NAME: &IdentStr = ident_str!("ptb_call_arg");
21pub const CALL_ARG_STRUCT_NAME: &IdentStr = ident_str!("CallArg");
22pub const OBJECT_ARG_STRUCT_NAME: &IdentStr = ident_str!("ObjectArg");
23pub const OBJECT_REF_STRUCT_NAME: &IdentStr = ident_str!("ObjectRef");
24
25pub const COMMAND_MODULE_NAME: &IdentStr = ident_str!("ptb_command");
26pub const COMMAND_STRUCT_NAME: &IdentStr = ident_str!("Command");
27pub const ARGUMENT_STRUCT_NAME: &IdentStr = ident_str!("Argument");
28pub const PROGRAMMABLE_MOVE_CALL_STRUCT_NAME: &IdentStr = ident_str!("ProgrammableMoveCall");
29pub const TRANSFER_OBJECTS_DATA_STRUCT_NAME: &IdentStr = ident_str!("TransferObjectsData");
30pub const SPLIT_COINS_DATA_STRUCT_NAME: &IdentStr = ident_str!("SplitCoinsData");
31pub const MERGE_COINS_DATA_STRUCT_NAME: &IdentStr = ident_str!("MergeCoinsData");
32pub const PUBLISH_DATA_STRUCT_NAME: &IdentStr = ident_str!("PublishData");
33pub const MAKE_MOVE_VEC_DATA_STRUCT_NAME: &IdentStr = ident_str!("MakeMoveVecData");
34pub const UPGRADE_DATA_STRUCT_NAME: &IdentStr = ident_str!("UpgradeData");
35
36// ---------------------------------------------------------------------------
37// MoveProgrammableMoveCall
38// ---------------------------------------------------------------------------
39
40/// Mirrors [`iota_sdk_types::MoveCall`] for use in
41/// [`MoveCommand`], substituting [`TypeTag`] for a string in the type arguments
42/// so that the type matches the BCS layout expected by the Move-side
43/// `ptb_command::ProgrammableMoveCall`.
44#[serde_as]
45#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
46pub struct MoveProgrammableMoveCall {
47    pub package: ObjectId,
48    pub module: String,
49    pub function: String,
50    #[serde_as(as = "Vec<TypeName>")]
51    pub type_arguments: Vec<TypeTag>,
52    pub arguments: Vec<Argument>,
53}
54
55// ---------------------------------------------------------------------------
56// MoveCommand
57// ---------------------------------------------------------------------------
58
59/// Mirrors [`iota_sdk_types::Command`], substituting [`TypeTag`] for
60/// a string in `MoveCall` and `MakeMoveVec` so that
61/// the type matches the BCS layout expected by the Move-side
62/// `ptb_command::Command`.
63#[serde_as]
64#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
65pub enum MoveCommand {
66    MoveCall(Box<MoveProgrammableMoveCall>),
67    TransferObjects(Vec<Argument>, Argument),
68    SplitCoins(Argument, Vec<Argument>),
69    MergeCoins(Argument, Vec<Argument>),
70    Publish(Vec<Vec<u8>>, Vec<ObjectId>),
71    MakeMoveVec(
72        #[serde_as(as = "Option<TypeName>")] Option<TypeTag>,
73        Vec<Argument>,
74    ),
75    Upgrade(Vec<Vec<u8>>, Vec<ObjectId>, ObjectId, Argument),
76}
77
78impl From<&Command> for MoveCommand {
79    fn from(cmd: &Command) -> Self {
80        match cmd {
81            Command::MoveCall(cmd) => MoveCommand::MoveCall(Box::new(MoveProgrammableMoveCall {
82                package: cmd.package,
83                module: cmd.module.to_string(),
84                function: cmd.function.to_string(),
85                type_arguments: cmd.type_arguments.clone(),
86                arguments: cmd.arguments.clone(),
87            })),
88            Command::TransferObjects(cmd) => {
89                MoveCommand::TransferObjects(cmd.objects.clone(), cmd.address)
90            }
91            Command::SplitCoins(cmd) => MoveCommand::SplitCoins(cmd.coin, cmd.amounts.clone()),
92            Command::MergeCoins(cmd) => {
93                MoveCommand::MergeCoins(cmd.coin, cmd.coins_to_merge.clone())
94            }
95            Command::Publish(cmd) => {
96                MoveCommand::Publish(cmd.modules.clone(), cmd.dependencies.clone())
97            }
98            Command::MakeMoveVector(cmd) => {
99                MoveCommand::MakeMoveVec(cmd.type_.clone(), cmd.elements.clone())
100            }
101            Command::Upgrade(cmd) => MoveCommand::Upgrade(
102                cmd.modules.clone(),
103                cmd.dependencies.clone(),
104                cmd.package,
105                cmd.ticket,
106            ),
107            _ => unimplemented!("a new Command enum variant was added and needs to be handled"),
108        }
109    }
110}
111
112impl MoveCommand {
113    pub fn type_() -> StructTag {
114        StructTag {
115            address: IOTA_FRAMEWORK_ADDRESS,
116            module: COMMAND_MODULE_NAME.to_owned(),
117            name: COMMAND_STRUCT_NAME.to_owned(),
118            type_params: vec![],
119        }
120    }
121}
122
123// ---------------------------------------------------------------------------
124// MoveCallArg
125// ---------------------------------------------------------------------------
126
127/// Mirrors `ObjectArg`, matching the BCS layout expected
128/// by the Move-side `ptb_call_arg::ObjectArg`.
129#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
130pub enum MoveObjectArg {
131    ImmOrOwnedObject(ObjectRef),
132    SharedObject {
133        id: ObjectId,
134        initial_shared_version: SequenceNumber,
135        mutable: bool,
136    },
137    Receiving(ObjectRef),
138}
139
140impl MoveObjectArg {
141    pub fn type_() -> StructTag {
142        StructTag {
143            address: IOTA_FRAMEWORK_ADDRESS,
144            module: CALL_ARG_MODULE_NAME.to_owned(),
145            name: OBJECT_ARG_STRUCT_NAME.to_owned(),
146            type_params: vec![],
147        }
148    }
149}
150
151/// Mirrors [`crate::transaction::CallArg`], matching the BCS layout expected
152/// by the Move-side `ptb_call_arg::CallArg`.
153#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
154pub enum MoveCallArg {
155    Pure(Vec<u8>),
156    Object(MoveObjectArg),
157}
158
159impl From<&CallArg> for MoveCallArg {
160    fn from(arg: &CallArg) -> Self {
161        match arg {
162            CallArg::Pure(bytes) => MoveCallArg::Pure(bytes.clone()),
163            CallArg::ImmutableOrOwned(obj_arg) => {
164                MoveCallArg::Object(MoveObjectArg::ImmOrOwnedObject(*obj_arg))
165            }
166            CallArg::Shared(obj_arg) => MoveCallArg::Object(MoveObjectArg::SharedObject {
167                id: obj_arg.object_id,
168                initial_shared_version: obj_arg.initial_shared_version,
169                mutable: obj_arg.mutable,
170            }),
171            CallArg::Receiving(obj_arg) => MoveCallArg::Object(MoveObjectArg::Receiving(*obj_arg)),
172            _ => unimplemented!("a new CallArg enum variant was added and needs to be handled"),
173        }
174    }
175}
176
177impl MoveCallArg {
178    pub fn type_() -> StructTag {
179        StructTag {
180            address: IOTA_FRAMEWORK_ADDRESS,
181            module: CALL_ARG_MODULE_NAME.to_owned(),
182            name: CALL_ARG_STRUCT_NAME.to_owned(),
183            type_params: vec![],
184        }
185    }
186}
187
188#[cfg(test)]
189mod tests {
190    use std::str::FromStr;
191
192    use iota_sdk_types::{Identifier, ObjectReference, StructTag, TypeTag};
193
194    use super::*;
195    use crate::{
196        base_types::{IotaAddress, ObjectDigest, SequenceNumber},
197        transaction::{CallArg, SharedObjectRef},
198    };
199
200    // ── helpers ─────────────────────────────────────────────────────────────
201
202    fn obj_id() -> ObjectId {
203        ObjectId::from_prefixed_short_hex("0x0000000000000000000000000000000000000001").unwrap()
204    }
205
206    fn obj_ref() -> ObjectReference {
207        ObjectReference {
208            object_id: obj_id(),
209            version: SequenceNumber::from(1),
210            digest: ObjectDigest::new([1u8; 32]),
211        }
212    }
213
214    /// BCS round-trip helper.
215    fn round_trip<T>(value: &T) -> T
216    where
217        T: serde::Serialize + for<'de> serde::Deserialize<'de> + PartialEq + std::fmt::Debug,
218    {
219        let bytes = bcs::to_bytes(value).unwrap();
220        bcs::from_bytes(&bytes).unwrap()
221    }
222
223    // ── MoveCallArg ───────────────────────────────────────────────────
224
225    #[test]
226    fn call_arg_pure_round_trip() {
227        let arg = MoveCallArg::Pure(vec![1, 2, 3]);
228        assert_eq!(round_trip(&arg), arg);
229    }
230
231    #[test]
232    fn call_arg_imm_or_owned_round_trip() {
233        let arg = MoveCallArg::Object(MoveObjectArg::ImmOrOwnedObject(obj_ref()));
234        assert_eq!(round_trip(&arg), arg);
235    }
236
237    #[test]
238    fn call_arg_shared_object_round_trip() {
239        let arg = MoveCallArg::Object(MoveObjectArg::SharedObject {
240            id: obj_id(),
241            initial_shared_version: SequenceNumber::from(5),
242            mutable: true,
243        });
244        assert_eq!(round_trip(&arg), arg);
245    }
246
247    #[test]
248    fn call_arg_receiving_round_trip() {
249        let arg = MoveCallArg::Object(MoveObjectArg::Receiving(obj_ref()));
250        assert_eq!(round_trip(&arg), arg);
251    }
252
253    // ── From<&CallArg> for MoveCallArg ────────────────────────────────
254
255    #[test]
256    fn call_arg_from_pure() {
257        let data = vec![10, 20, 30];
258        let converted = MoveCallArg::from(&CallArg::Pure(data.clone()));
259        assert_eq!(converted, MoveCallArg::Pure(data));
260    }
261
262    #[test]
263    fn call_arg_from_object() {
264        let converted = MoveCallArg::from(&CallArg::ImmutableOrOwned(obj_ref()));
265        assert_eq!(
266            converted,
267            MoveCallArg::Object(MoveObjectArg::ImmOrOwnedObject(obj_ref()))
268        );
269    }
270
271    #[test]
272    fn call_arg_from_call_arg() {
273        let call_arg = CallArg::Pure(vec![99]);
274        let converted = MoveCallArg::from(&call_arg);
275        assert!(matches!(converted, MoveCallArg::Pure(_)));
276    }
277
278    // ── BCS compatibility: MoveCallArg ↔ CallArg ─────────────────────
279
280    #[test]
281    fn call_arg_bcs_compatible_imm_or_owned() {
282        let tx_arg = CallArg::ImmutableOrOwned(obj_ref());
283        let ctx_arg = MoveCallArg::from(&tx_arg);
284        assert_eq!(
285            bcs::to_bytes(&tx_arg).unwrap(),
286            bcs::to_bytes(&ctx_arg).unwrap()
287        );
288    }
289
290    #[test]
291    fn call_arg_bcs_compatible_shared() {
292        let tx_arg = CallArg::Shared(SharedObjectRef::new(
293            obj_id(),
294            SequenceNumber::from(5),
295            true,
296        ));
297        let ctx_arg = MoveCallArg::from(&tx_arg);
298        assert_eq!(
299            bcs::to_bytes(&tx_arg).unwrap(),
300            bcs::to_bytes(&ctx_arg).unwrap()
301        );
302    }
303
304    #[test]
305    fn call_arg_bcs_compatible_receiving() {
306        let tx_arg = CallArg::Receiving(obj_ref());
307        let ctx_arg = MoveCallArg::from(&tx_arg);
308        assert_eq!(
309            bcs::to_bytes(&tx_arg).unwrap(),
310            bcs::to_bytes(&ctx_arg).unwrap()
311        );
312    }
313
314    // ── MoveCommand round-trips ────────────────────────────────────────
315
316    fn sample_move_call() -> MoveCommand {
317        MoveCommand::MoveCall(Box::new(MoveProgrammableMoveCall {
318            package: obj_id(),
319            module: "my_module".to_string(),
320            function: "my_func".to_string(),
321            type_arguments: vec![TypeTag::U64],
322            arguments: vec![Argument::Gas, Argument::Input(0)],
323        }))
324    }
325
326    #[test]
327    fn command_move_call_round_trip() {
328        assert_eq!(round_trip(&sample_move_call()), sample_move_call());
329    }
330
331    #[test]
332    fn command_transfer_objects_round_trip() {
333        let cmd = MoveCommand::TransferObjects(
334            vec![Argument::Input(0), Argument::Result(1)],
335            Argument::Input(2),
336        );
337        assert_eq!(round_trip(&cmd), cmd);
338    }
339
340    #[test]
341    fn command_split_coins_round_trip() {
342        let cmd = MoveCommand::SplitCoins(Argument::Gas, vec![Argument::Input(0)]);
343        assert_eq!(round_trip(&cmd), cmd);
344    }
345
346    #[test]
347    fn command_merge_coins_round_trip() {
348        let cmd =
349            MoveCommand::MergeCoins(Argument::Gas, vec![Argument::Input(0), Argument::Input(1)]);
350        assert_eq!(round_trip(&cmd), cmd);
351    }
352
353    #[test]
354    fn command_publish_round_trip() {
355        let cmd = MoveCommand::Publish(vec![vec![1, 2, 3]], vec![obj_id()]);
356        assert_eq!(round_trip(&cmd), cmd);
357    }
358
359    #[test]
360    fn command_make_move_vec_with_type_round_trip() {
361        let cmd = MoveCommand::MakeMoveVec(
362            Some(TypeTag::from_str("0x2::coin::Coin<u64>").unwrap()),
363            vec![Argument::Input(0)],
364        );
365        assert_eq!(round_trip(&cmd), cmd);
366    }
367
368    #[test]
369    fn command_make_move_vec_no_type_round_trip() {
370        let cmd = MoveCommand::MakeMoveVec(None, vec![Argument::Result(0)]);
371        assert_eq!(round_trip(&cmd), cmd);
372    }
373
374    #[test]
375    fn command_upgrade_round_trip() {
376        let cmd = MoveCommand::Upgrade(
377            vec![vec![0xde, 0xad]],
378            vec![obj_id()],
379            obj_id(),
380            Argument::Result(0),
381        );
382        assert_eq!(round_trip(&cmd), cmd);
383    }
384
385    // ── From<&Command> for MoveCommand ────────────────────────────────
386
387    /// Primitive TypeTag variants (Bool, U8, …) must be converted to their
388    /// canonical string representation as TypeTag.
389    #[test]
390    fn command_from_move_call_primitive_type_tag() {
391        let cases = [
392            (TypeTag::Bool, "bool"),
393            (TypeTag::U8, "u8"),
394            (TypeTag::U64, "u64"),
395            (TypeTag::U128, "u128"),
396            (TypeTag::U16, "u16"),
397            (TypeTag::U32, "u32"),
398            (TypeTag::U256, "u256"),
399            (TypeTag::Address, "address"),
400        ];
401        for (type_tag, expected_name) in cases {
402            let cmd = Command::new_move_call(
403                obj_id(),
404                Identifier::new_unchecked("m"),
405                Identifier::new_unchecked("f"),
406                vec![type_tag],
407                vec![],
408            );
409            let MoveCommand::MoveCall(call) = MoveCommand::from(&cmd) else {
410                panic!("expected MoveCall");
411            };
412            assert_eq!(
413                call.type_arguments,
414                vec![TypeTag::from_str(expected_name).unwrap()],
415                "failed for {expected_name}"
416            );
417        }
418    }
419
420    /// Struct TypeTag must be converted to its canonical qualified name.
421    #[test]
422    fn command_from_move_call_struct_type_tag() {
423        let expected = TypeTag::Struct(Box::new(StructTag::new(
424            IotaAddress::FRAMEWORK,
425            "coin",
426            "Coin",
427            vec![TypeTag::U64],
428        )));
429
430        let cmd = Command::new_move_call(
431            obj_id(),
432            Identifier::new_unchecked("m"),
433            Identifier::new_unchecked("f"),
434            vec![expected.clone()],
435            vec![],
436        );
437        let MoveCommand::MoveCall(call) = MoveCommand::from(&cmd) else {
438            panic!("expected MoveCall");
439        };
440        assert_eq!(call.type_arguments, vec![expected]);
441    }
442
443    #[test]
444    fn command_from_make_move_vec_type_tag_becomes_type_name() {
445        let expected = TypeTag::Bool;
446        let cmd = Command::new_make_move_vector(Some(expected.clone()), vec![Argument::Input(0)]);
447        let MoveCommand::MakeMoveVec(name, _) = MoveCommand::from(&cmd) else {
448            panic!("expected MakeMoveVec");
449        };
450        assert_eq!(name, Some(expected));
451    }
452
453    #[test]
454    fn command_from_make_move_vec_none_type() {
455        let cmd = Command::new_make_move_vector(None, vec![]);
456        let MoveCommand::MakeMoveVec(name, elements) = MoveCommand::from(&cmd) else {
457            panic!("expected MakeMoveVec");
458        };
459        assert!(name.is_none());
460        assert!(elements.is_empty());
461    }
462
463    #[test]
464    fn command_from_command() {
465        let cmd = Command::new_move_call(
466            obj_id(),
467            Identifier::new_unchecked("m"),
468            Identifier::new_unchecked("f"),
469            vec![TypeTag::U8],
470            vec![],
471        );
472        let converted = MoveCommand::from(&cmd);
473        assert!(matches!(converted, MoveCommand::MoveCall(_)));
474    }
475}