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};
6
7use crate::{
8    IOTA_FRAMEWORK_ADDRESS,
9    base_types::{ObjectDigest, ObjectID, SequenceNumber},
10    transaction::{Argument, CallArg, Command, ObjectArg},
11    type_input::TypeName,
12};
13
14// ---------------------------------------------------------------------------
15// Module / struct name constants
16// ---------------------------------------------------------------------------
17
18pub const CALL_ARG_MODULE_NAME: &IdentStr = ident_str!("ptb_call_arg");
19pub const CALL_ARG_STRUCT_NAME: &IdentStr = ident_str!("CallArg");
20pub const OBJECT_ARG_STRUCT_NAME: &IdentStr = ident_str!("ObjectArg");
21pub const OBJECT_REF_STRUCT_NAME: &IdentStr = ident_str!("ObjectRef");
22
23pub const COMMAND_MODULE_NAME: &IdentStr = ident_str!("ptb_command");
24pub const COMMAND_STRUCT_NAME: &IdentStr = ident_str!("Command");
25pub const ARGUMENT_STRUCT_NAME: &IdentStr = ident_str!("Argument");
26pub const PROGRAMMABLE_MOVE_CALL_STRUCT_NAME: &IdentStr = ident_str!("ProgrammableMoveCall");
27pub const TRANSFER_OBJECTS_DATA_STRUCT_NAME: &IdentStr = ident_str!("TransferObjectsData");
28pub const SPLIT_COINS_DATA_STRUCT_NAME: &IdentStr = ident_str!("SplitCoinsData");
29pub const MERGE_COINS_DATA_STRUCT_NAME: &IdentStr = ident_str!("MergeCoinsData");
30pub const PUBLISH_DATA_STRUCT_NAME: &IdentStr = ident_str!("PublishData");
31pub const MAKE_MOVE_VEC_DATA_STRUCT_NAME: &IdentStr = ident_str!("MakeMoveVecData");
32pub const UPGRADE_DATA_STRUCT_NAME: &IdentStr = ident_str!("UpgradeData");
33
34// ---------------------------------------------------------------------------
35// MoveProgrammableMoveCall
36// ---------------------------------------------------------------------------
37
38/// Mirrors [`crate::transaction::ProgrammableMoveCall`] for use in
39/// [`MoveCommand`], substituting [`TypeName`] for
40/// [`crate::type_input::TypeInput`] so that the type can derive
41/// [`Serialize`]/[`Deserialize`] without a custom implementation.
42#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
43pub struct MoveProgrammableMoveCall {
44    pub package: ObjectID,
45    pub module: String,
46    pub function: String,
47    pub type_arguments: Vec<TypeName>,
48    pub arguments: Vec<Argument>,
49}
50
51// ---------------------------------------------------------------------------
52// MoveCommand
53// ---------------------------------------------------------------------------
54
55/// Mirrors [`crate::transaction::Command`], substituting [`TypeName`] for
56/// [`crate::type_input::TypeInput`] in `MoveCall` and `MakeMoveVec` so that
57/// the type matches the BCS layout expected by the Move-side
58/// `ptb_command::Command`.
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(Option<TypeName>, Vec<Argument>),
67    Upgrade(Vec<Vec<u8>>, Vec<ObjectID>, ObjectID, Argument),
68}
69
70impl From<&Command> for MoveCommand {
71    fn from(cmd: &Command) -> Self {
72        match cmd {
73            Command::MoveCall(m) => MoveCommand::MoveCall(Box::new(MoveProgrammableMoveCall {
74                package: m.package,
75                module: m.module.clone(),
76                function: m.function.clone(),
77                type_arguments: m.type_arguments.iter().map(TypeName::from).collect(),
78                arguments: m.arguments.clone(),
79            })),
80            Command::TransferObjects(objects, recipient) => {
81                MoveCommand::TransferObjects(objects.clone(), *recipient)
82            }
83            Command::SplitCoins(coin, amounts) => MoveCommand::SplitCoins(*coin, amounts.clone()),
84            Command::MergeCoins(target_coin, source_coins) => {
85                MoveCommand::MergeCoins(*target_coin, source_coins.clone())
86            }
87            Command::Publish(modules, dependencies) => {
88                MoveCommand::Publish(modules.clone(), dependencies.clone())
89            }
90            Command::MakeMoveVec(type_arg, elements) => {
91                MoveCommand::MakeMoveVec(type_arg.as_ref().map(TypeName::from), elements.clone())
92            }
93            Command::Upgrade(modules, dependencies, package, upgrade_ticket) => {
94                MoveCommand::Upgrade(
95                    modules.clone(),
96                    dependencies.clone(),
97                    *package,
98                    *upgrade_ticket,
99                )
100            }
101        }
102    }
103}
104
105impl MoveCommand {
106    pub fn type_() -> StructTag {
107        StructTag {
108            address: IOTA_FRAMEWORK_ADDRESS,
109            module: COMMAND_MODULE_NAME.to_owned(),
110            name: COMMAND_STRUCT_NAME.to_owned(),
111            type_params: vec![],
112        }
113    }
114}
115
116// ---------------------------------------------------------------------------
117// MoveCallArg
118// ---------------------------------------------------------------------------
119
120/// Mirrors [`crate::transaction::ObjectArg`], matching the BCS layout expected
121/// by the Move-side `ptb_call_arg::ObjectArg`.
122#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
123pub enum MoveObjectArg {
124    ImmOrOwnedObject((ObjectID, SequenceNumber, ObjectDigest)),
125    SharedObject {
126        id: ObjectID,
127        initial_shared_version: SequenceNumber,
128        mutable: bool,
129    },
130    Receiving((ObjectID, SequenceNumber, ObjectDigest)),
131}
132
133impl From<&ObjectArg> for MoveObjectArg {
134    fn from(obj: &ObjectArg) -> Self {
135        match obj {
136            ObjectArg::ImmOrOwnedObject(r) => MoveObjectArg::ImmOrOwnedObject(*r),
137            ObjectArg::SharedObject {
138                id,
139                initial_shared_version,
140                mutable,
141            } => MoveObjectArg::SharedObject {
142                id: *id,
143                initial_shared_version: *initial_shared_version,
144                mutable: *mutable,
145            },
146            ObjectArg::Receiving(r) => MoveObjectArg::Receiving(*r),
147        }
148    }
149}
150
151impl MoveObjectArg {
152    pub fn type_() -> StructTag {
153        StructTag {
154            address: IOTA_FRAMEWORK_ADDRESS,
155            module: CALL_ARG_MODULE_NAME.to_owned(),
156            name: OBJECT_ARG_STRUCT_NAME.to_owned(),
157            type_params: vec![],
158        }
159    }
160}
161
162/// Mirrors [`crate::transaction::CallArg`], matching the BCS layout expected
163/// by the Move-side `ptb_call_arg::CallArg`.
164#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
165pub enum MoveCallArg {
166    Pure(Vec<u8>),
167    Object(MoveObjectArg),
168}
169
170impl From<&CallArg> for MoveCallArg {
171    fn from(arg: &CallArg) -> Self {
172        match arg {
173            CallArg::Pure(bytes) => MoveCallArg::Pure(bytes.clone()),
174            CallArg::Object(obj_arg) => MoveCallArg::Object(MoveObjectArg::from(obj_arg)),
175        }
176    }
177}
178
179impl MoveCallArg {
180    pub fn type_() -> StructTag {
181        StructTag {
182            address: IOTA_FRAMEWORK_ADDRESS,
183            module: CALL_ARG_MODULE_NAME.to_owned(),
184            name: CALL_ARG_STRUCT_NAME.to_owned(),
185            type_params: vec![],
186        }
187    }
188}
189
190#[cfg(test)]
191mod tests {
192    use move_core_types::account_address::AccountAddress;
193
194    use super::*;
195    use crate::{
196        base_types::{ObjectDigest, ObjectID, SequenceNumber},
197        transaction::{Argument, CallArg, Command, ObjectArg, ProgrammableMoveCall},
198        type_input::{StructInput, TypeInput},
199    };
200
201    // ── helpers ─────────────────────────────────────────────────────────────
202
203    fn obj_id() -> ObjectID {
204        ObjectID::from_hex_literal("0x0000000000000000000000000000000000000001").unwrap()
205    }
206
207    fn obj_ref() -> (ObjectID, SequenceNumber, ObjectDigest) {
208        (
209            obj_id(),
210            SequenceNumber::from(1),
211            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 obj_arg = ObjectArg::ImmOrOwnedObject(obj_ref());
266        let converted = MoveCallArg::from(&CallArg::Object(obj_arg));
267        assert_eq!(
268            converted,
269            MoveCallArg::Object(MoveObjectArg::ImmOrOwnedObject(obj_ref()))
270        );
271    }
272
273    #[test]
274    fn call_arg_from_call_arg() {
275        let call_arg = CallArg::Pure(vec![99]);
276        let converted = MoveCallArg::from(&call_arg);
277        assert!(matches!(converted, MoveCallArg::Pure(_)));
278    }
279
280    // ── BCS compatibility: MoveObjectArg ↔ ObjectArg ─────────────────
281
282    #[test]
283    fn object_arg_bcs_compatible_imm_or_owned() {
284        let tx_arg = ObjectArg::ImmOrOwnedObject(obj_ref());
285        let ctx_arg = MoveObjectArg::from(&tx_arg);
286        assert_eq!(
287            bcs::to_bytes(&tx_arg).unwrap(),
288            bcs::to_bytes(&ctx_arg).unwrap()
289        );
290    }
291
292    #[test]
293    fn object_arg_bcs_compatible_shared() {
294        let tx_arg = ObjectArg::SharedObject {
295            id: obj_id(),
296            initial_shared_version: SequenceNumber::from(5),
297            mutable: true,
298        };
299        let ctx_arg = MoveObjectArg::from(&tx_arg);
300        assert_eq!(
301            bcs::to_bytes(&tx_arg).unwrap(),
302            bcs::to_bytes(&ctx_arg).unwrap()
303        );
304    }
305
306    #[test]
307    fn object_arg_bcs_compatible_receiving() {
308        let tx_arg = ObjectArg::Receiving(obj_ref());
309        let ctx_arg = MoveObjectArg::from(&tx_arg);
310        assert_eq!(
311            bcs::to_bytes(&tx_arg).unwrap(),
312            bcs::to_bytes(&ctx_arg).unwrap()
313        );
314    }
315
316    // ── MoveCommand round-trips ────────────────────────────────────────
317
318    fn sample_move_call() -> MoveCommand {
319        MoveCommand::MoveCall(Box::new(MoveProgrammableMoveCall {
320            package: obj_id(),
321            module: "my_module".to_string(),
322            function: "my_func".to_string(),
323            type_arguments: vec![TypeName {
324                name: "u64".to_string(),
325            }],
326            arguments: vec![Argument::GasCoin, Argument::Input(0)],
327        }))
328    }
329
330    #[test]
331    fn command_move_call_round_trip() {
332        assert_eq!(round_trip(&sample_move_call()), sample_move_call());
333    }
334
335    #[test]
336    fn command_transfer_objects_round_trip() {
337        let cmd = MoveCommand::TransferObjects(
338            vec![Argument::Input(0), Argument::Result(1)],
339            Argument::Input(2),
340        );
341        assert_eq!(round_trip(&cmd), cmd);
342    }
343
344    #[test]
345    fn command_split_coins_round_trip() {
346        let cmd = MoveCommand::SplitCoins(Argument::GasCoin, vec![Argument::Input(0)]);
347        assert_eq!(round_trip(&cmd), cmd);
348    }
349
350    #[test]
351    fn command_merge_coins_round_trip() {
352        let cmd = MoveCommand::MergeCoins(
353            Argument::GasCoin,
354            vec![Argument::Input(0), Argument::Input(1)],
355        );
356        assert_eq!(round_trip(&cmd), cmd);
357    }
358
359    #[test]
360    fn command_publish_round_trip() {
361        let cmd = MoveCommand::Publish(vec![vec![1, 2, 3]], vec![obj_id()]);
362        assert_eq!(round_trip(&cmd), cmd);
363    }
364
365    #[test]
366    fn command_make_move_vec_with_type_round_trip() {
367        let cmd = MoveCommand::MakeMoveVec(
368            Some(TypeName {
369                name: "0x2::coin::Coin<u64>".to_string(),
370            }),
371            vec![Argument::Input(0)],
372        );
373        assert_eq!(round_trip(&cmd), cmd);
374    }
375
376    #[test]
377    fn command_make_move_vec_no_type_round_trip() {
378        let cmd = MoveCommand::MakeMoveVec(None, vec![Argument::Result(0)]);
379        assert_eq!(round_trip(&cmd), cmd);
380    }
381
382    #[test]
383    fn command_upgrade_round_trip() {
384        let cmd = MoveCommand::Upgrade(
385            vec![vec![0xde, 0xad]],
386            vec![obj_id()],
387            obj_id(),
388            Argument::Result(0),
389        );
390        assert_eq!(round_trip(&cmd), cmd);
391    }
392
393    // ── From<&Command> for MoveCommand ────────────────────────────────
394
395    /// Primitive TypeInput variants (Bool, U8, …) must be converted to their
396    /// canonical string representation as TypeName.
397    #[test]
398    fn command_from_move_call_primitive_type_input() {
399        let cases = [
400            (TypeInput::Bool, "bool"),
401            (TypeInput::U8, "u8"),
402            (TypeInput::U64, "u64"),
403            (TypeInput::U128, "u128"),
404            (TypeInput::U16, "u16"),
405            (TypeInput::U32, "u32"),
406            (TypeInput::U256, "u256"),
407            (TypeInput::Address, "address"),
408        ];
409        for (type_input, expected_name) in cases {
410            let cmd = Command::MoveCall(Box::new(ProgrammableMoveCall {
411                package: obj_id(),
412                module: "m".to_string(),
413                function: "f".to_string(),
414                type_arguments: vec![type_input],
415                arguments: vec![],
416            }));
417            let MoveCommand::MoveCall(call) = MoveCommand::from(&cmd) else {
418                panic!("expected MoveCall");
419            };
420            assert_eq!(
421                call.type_arguments,
422                vec![TypeName {
423                    name: expected_name.to_string()
424                }],
425                "failed for {expected_name}"
426            );
427        }
428    }
429
430    /// Struct TypeInput must be converted to its canonical qualified name.
431    #[test]
432    fn command_from_move_call_struct_type_input() {
433        let type_input = TypeInput::Struct(Box::new(StructInput {
434            address: AccountAddress::from_hex_literal("0x2").unwrap(),
435            module: "coin".to_string(),
436            name: "Coin".to_string(),
437            type_params: vec![TypeInput::U64],
438        }));
439        let expected = TypeName::from(&type_input);
440
441        let cmd = Command::MoveCall(Box::new(ProgrammableMoveCall {
442            package: obj_id(),
443            module: "m".to_string(),
444            function: "f".to_string(),
445            type_arguments: vec![type_input],
446            arguments: vec![],
447        }));
448        let MoveCommand::MoveCall(call) = MoveCommand::from(&cmd) else {
449            panic!("expected MoveCall");
450        };
451        assert_eq!(call.type_arguments, vec![expected]);
452    }
453
454    #[test]
455    fn command_from_make_move_vec_type_input_becomes_type_name() {
456        let type_input = TypeInput::Bool;
457        let expected = TypeName::from(&type_input);
458        let cmd = Command::MakeMoveVec(Some(type_input), vec![Argument::Input(0)]);
459        let MoveCommand::MakeMoveVec(name, _) = MoveCommand::from(&cmd) else {
460            panic!("expected MakeMoveVec");
461        };
462        assert_eq!(name, Some(expected));
463    }
464
465    #[test]
466    fn command_from_make_move_vec_none_type() {
467        let cmd = Command::MakeMoveVec(None, vec![]);
468        let MoveCommand::MakeMoveVec(name, elements) = MoveCommand::from(&cmd) else {
469            panic!("expected MakeMoveVec");
470        };
471        assert!(name.is_none());
472        assert!(elements.is_empty());
473    }
474
475    #[test]
476    fn command_from_command() {
477        let cmd = Command::MoveCall(Box::new(ProgrammableMoveCall {
478            package: obj_id(),
479            module: "m".to_string(),
480            function: "f".to_string(),
481            type_arguments: vec![TypeInput::U8],
482            arguments: vec![],
483        }));
484        let converted = MoveCommand::from(&cmd);
485        assert!(matches!(converted, MoveCommand::MoveCall(_)));
486    }
487}