iota_types/auth_context/
fields_v1.rs

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