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