iota_transactional_test_runner/
args.rs

1// Copyright (c) Mysten Labs, Inc.
2// Modifications Copyright (c) 2024 IOTA Stiftung
3// SPDX-License-Identifier: Apache-2.0
4
5use std::path::PathBuf;
6
7use anyhow::{bail, ensure};
8use clap::{self, Args, Parser};
9use iota_types::{
10    base_types::{IotaAddress, SequenceNumber},
11    move_package::UpgradePolicy,
12    object::{Object, Owner},
13    programmable_transaction_builder::ProgrammableTransactionBuilder,
14    transaction::{Argument, CallArg, ObjectArg},
15};
16use move_compiler::editions::Flavor;
17use move_core_types::{
18    parsing::{
19        parser::{Parser as MoveCLParser, parse_u64, parse_u256},
20        values::{ParsableValue, ParsedValue, ValueToken},
21    },
22    runtime_value::{MoveStruct, MoveValue},
23    u256::U256,
24};
25use move_symbol_pool::Symbol;
26use move_transactional_test_runner::tasks::{RunCommand, SyntaxChoice};
27
28use crate::test_adapter::{FakeID, IotaTestAdapter};
29
30pub const IOTA_ARGS_LONG: &str = "iota-args";
31
32#[derive(Clone, Debug, clap::Parser)]
33pub struct IotaRunArgs {
34    #[arg(long)]
35    pub sender: Option<String>,
36    #[arg(long)]
37    pub gas_price: Option<u64>,
38    #[arg(long)]
39    pub summarize: bool,
40}
41
42#[derive(Debug, clap::Parser, Default)]
43pub struct IotaPublishArgs {
44    #[arg(long)]
45    pub sender: Option<String>,
46    #[arg(long, action = clap::ArgAction::SetTrue)]
47    pub upgradeable: bool,
48    #[arg(long, num_args(1..))]
49    pub dependencies: Vec<String>,
50    #[arg(long)]
51    pub gas_price: Option<u64>,
52}
53
54#[derive(Debug, clap::Parser)]
55pub struct IotaInitArgs {
56    #[arg(long, num_args(1..))]
57    pub accounts: Option<Vec<String>>,
58    #[arg(long)]
59    pub protocol_version: Option<u64>,
60    #[arg(long)]
61    pub max_gas: Option<u64>,
62    #[arg(long)]
63    pub move_binary_format_version: Option<u32>,
64    #[arg(long)]
65    pub simulator: bool,
66    #[arg(long)]
67    pub custom_validator_account: bool,
68    #[arg(long)]
69    pub reference_gas_price: Option<u64>,
70    #[arg(long)]
71    pub default_gas_price: Option<u64>,
72    #[arg(long)]
73    pub objects_snapshot_min_checkpoint_lag: Option<usize>,
74    #[arg(long)]
75    pub flavor: Option<Flavor>,
76    /// The number of epochs to keep in the database. Epochs outside of this
77    /// range will be pruned by the indexer.
78    #[arg(long)]
79    pub epochs_to_keep: Option<u64>,
80    /// Dir for simulacrum to write checkpoint files to. To be passed to the
81    /// offchain indexer and reader.
82    #[clap(long)]
83    pub data_ingestion_path: Option<PathBuf>,
84    /// URL for the IOTA REST API. To be passed to the offchain indexer and
85    /// reader.
86    #[clap(long)]
87    pub rest_api_url: Option<String>,
88}
89
90#[derive(Debug, clap::Parser)]
91pub struct ViewObjectCommand {
92    #[arg(value_parser = parse_fake_id)]
93    pub id: FakeID,
94}
95
96#[derive(Debug, clap::Parser)]
97pub struct TransferObjectCommand {
98    #[arg(value_parser = parse_fake_id)]
99    pub id: FakeID,
100    #[arg(long)]
101    pub recipient: String,
102    #[arg(long)]
103    pub sender: Option<String>,
104    #[arg(long)]
105    pub gas_budget: Option<u64>,
106    #[arg(long)]
107    pub gas_price: Option<u64>,
108}
109
110#[derive(Debug, clap::Parser)]
111pub struct ConsensusCommitPrologueCommand {
112    #[arg(long)]
113    pub timestamp_ms: u64,
114}
115
116#[derive(Debug, clap::Parser)]
117pub struct ProgrammableTransactionCommand {
118    #[arg(long)]
119    pub sender: Option<String>,
120    #[clap(long = "sponsor")]
121    pub sponsor: Option<String>,
122    #[arg(long = "gas-budget")]
123    pub gas_budget: Option<u64>,
124    #[arg(long)]
125    pub gas_price: Option<u64>,
126    #[clap(long = "gas-payment", value_parser = parse_fake_id)]
127    pub gas_payment: Option<FakeID>,
128    #[arg(long = "dev-inspect")]
129    pub dev_inspect: bool,
130    #[clap(long = "dry-run")]
131    pub dry_run: bool,
132    #[arg(
133        long,
134        value_parser = ParsedValue::<IotaExtraValueArgs>::parse,
135        num_args(1..),
136        action = clap::ArgAction::Append,
137    )]
138    pub inputs: Vec<ParsedValue<IotaExtraValueArgs>>,
139}
140
141#[derive(Debug, clap::Parser)]
142pub struct UpgradePackageCommand {
143    #[arg(long)]
144    pub package: String,
145    #[arg(long, value_parser = parse_fake_id)]
146    pub upgrade_capability: FakeID,
147    #[arg(long, num_args(1..))]
148    pub dependencies: Vec<String>,
149    #[arg(long)]
150    pub sender: String,
151    #[arg(long)]
152    pub gas_budget: Option<u64>,
153    #[arg(long)]
154    pub syntax: Option<SyntaxChoice>,
155    #[arg(long, default_value="compatible", value_parser = parse_policy)]
156    pub policy: u8,
157    #[arg(long)]
158    pub gas_price: Option<u64>,
159}
160
161#[derive(Debug, clap::Parser)]
162pub struct StagePackageCommand {
163    #[arg(long)]
164    pub syntax: Option<SyntaxChoice>,
165    #[arg(long, num_args(1..))]
166    pub dependencies: Vec<String>,
167}
168
169#[derive(Debug, clap::Parser)]
170pub struct SetAddressCommand {
171    pub address: String,
172    #[arg(value_parser = ParsedValue::<IotaExtraValueArgs>::parse)]
173    pub input: ParsedValue<IotaExtraValueArgs>,
174}
175
176#[derive(Debug, clap::Parser)]
177pub struct AdvanceClockCommand {
178    #[arg(long)]
179    pub duration_ns: u64,
180}
181
182#[derive(Debug, clap::Parser)]
183pub struct RunGraphqlCommand {
184    #[arg(long)]
185    pub show_usage: bool,
186    #[arg(long)]
187    pub show_headers: bool,
188    #[arg(long)]
189    pub show_service_version: bool,
190    #[arg(long, num_args(1..))]
191    pub cursors: Vec<String>,
192    #[arg(long)]
193    pub wait_for_checkpoint_pruned: Option<u64>,
194}
195
196#[derive(Debug, clap::Parser)]
197pub struct CreateCheckpointCommand {
198    pub count: Option<u64>,
199}
200
201#[derive(Debug, clap::Parser)]
202pub struct AdvanceEpochCommand {
203    pub count: Option<u64>,
204}
205
206#[derive(Debug, clap::Parser)]
207pub struct SetRandomStateCommand {
208    #[arg(long)]
209    pub randomness_round: u64,
210    #[arg(long)]
211    pub random_bytes: String,
212    #[arg(long)]
213    pub randomness_initial_version: u64,
214}
215
216#[derive(Debug)]
217pub enum IotaSubcommand<ExtraValueArgs: ParsableValue, ExtraRunArgs: Parser> {
218    ViewObject(ViewObjectCommand),
219    TransferObject(TransferObjectCommand),
220    ConsensusCommitPrologue(ConsensusCommitPrologueCommand),
221    ProgrammableTransaction(ProgrammableTransactionCommand),
222    UpgradePackage(UpgradePackageCommand),
223    StagePackage(StagePackageCommand),
224    SetAddress(SetAddressCommand),
225    CreateCheckpoint(CreateCheckpointCommand),
226    AdvanceEpoch(AdvanceEpochCommand),
227    AdvanceClock(AdvanceClockCommand),
228    SetRandomState(SetRandomStateCommand),
229    ViewCheckpoint,
230    RunGraphql(RunGraphqlCommand),
231    Bench(RunCommand<ExtraValueArgs>, ExtraRunArgs),
232}
233
234impl<ExtraValueArgs: ParsableValue, ExtraRunArgs: Parser> clap::FromArgMatches
235    for IotaSubcommand<ExtraValueArgs, ExtraRunArgs>
236{
237    fn from_arg_matches(matches: &clap::ArgMatches) -> Result<Self, clap::Error> {
238        Ok(match matches.subcommand() {
239            Some(("view-object", matches)) => {
240                IotaSubcommand::ViewObject(ViewObjectCommand::from_arg_matches(matches)?)
241            }
242            Some(("transfer-object", matches)) => {
243                IotaSubcommand::TransferObject(TransferObjectCommand::from_arg_matches(matches)?)
244            }
245            Some(("consensus-commit-prologue", matches)) => {
246                IotaSubcommand::ConsensusCommitPrologue(
247                    ConsensusCommitPrologueCommand::from_arg_matches(matches)?,
248                )
249            }
250            Some(("programmable", matches)) => IotaSubcommand::ProgrammableTransaction(
251                ProgrammableTransactionCommand::from_arg_matches(matches)?,
252            ),
253            Some(("upgrade", matches)) => {
254                IotaSubcommand::UpgradePackage(UpgradePackageCommand::from_arg_matches(matches)?)
255            }
256            Some(("stage-package", matches)) => {
257                IotaSubcommand::StagePackage(StagePackageCommand::from_arg_matches(matches)?)
258            }
259            Some(("set-address", matches)) => {
260                IotaSubcommand::SetAddress(SetAddressCommand::from_arg_matches(matches)?)
261            }
262            Some(("create-checkpoint", matches)) => IotaSubcommand::CreateCheckpoint(
263                CreateCheckpointCommand::from_arg_matches(matches)?,
264            ),
265            Some(("advance-epoch", matches)) => {
266                IotaSubcommand::AdvanceEpoch(AdvanceEpochCommand::from_arg_matches(matches)?)
267            }
268            Some(("advance-clock", matches)) => {
269                IotaSubcommand::AdvanceClock(AdvanceClockCommand::from_arg_matches(matches)?)
270            }
271            Some(("set-random-state", matches)) => {
272                IotaSubcommand::SetRandomState(SetRandomStateCommand::from_arg_matches(matches)?)
273            }
274            Some(("view-checkpoint", _)) => IotaSubcommand::ViewCheckpoint,
275            Some(("run-graphql", matches)) => {
276                IotaSubcommand::RunGraphql(RunGraphqlCommand::from_arg_matches(matches)?)
277            }
278            Some(("bench", matches)) => IotaSubcommand::Bench(
279                RunCommand::from_arg_matches(matches)?,
280                ExtraRunArgs::from_arg_matches(matches)?,
281            ),
282            _ => {
283                return Err(clap::Error::raw(
284                    clap::error::ErrorKind::InvalidSubcommand,
285                    "Invalid submcommand",
286                ));
287            }
288        })
289    }
290
291    fn update_from_arg_matches(&mut self, matches: &clap::ArgMatches) -> Result<(), clap::Error> {
292        *self = Self::from_arg_matches(matches)?;
293        Ok(())
294    }
295}
296
297impl<ExtraValueArgs: ParsableValue, ExtraRunArgs: Parser> clap::CommandFactory
298    for IotaSubcommand<ExtraValueArgs, ExtraRunArgs>
299{
300    fn command() -> clap::Command {
301        clap::Command::new("iota_sub_command")
302            .subcommand(ViewObjectCommand::command().name("view-object"))
303            .subcommand(TransferObjectCommand::command().name("transfer-object"))
304            .subcommand(ConsensusCommitPrologueCommand::command().name("consensus-commit-prologue"))
305            .subcommand(ProgrammableTransactionCommand::command().name("programmable"))
306            .subcommand(UpgradePackageCommand::command().name("upgrade"))
307            .subcommand(StagePackageCommand::command().name("stage-package"))
308            .subcommand(SetAddressCommand::command().name("set-address"))
309            .subcommand(CreateCheckpointCommand::command().name("create-checkpoint"))
310            .subcommand(AdvanceEpochCommand::command().name("advance-epoch"))
311            .subcommand(AdvanceClockCommand::command().name("advance-clock"))
312            .subcommand(SetRandomStateCommand::command().name("set-random-state"))
313            .subcommand(clap::Command::new("view-checkpoint"))
314            .subcommand(RunGraphqlCommand::command().name("run-graphql"))
315            .subcommand(
316                RunCommand::<ExtraValueArgs>::augment_args(ExtraRunArgs::command()).name("bench"),
317            )
318    }
319
320    fn command_for_update() -> clap::Command {
321        todo!()
322    }
323}
324
325impl<ExtraValueArgs: ParsableValue, ExtraRunArgs: Parser> clap::Parser
326    for IotaSubcommand<ExtraValueArgs, ExtraRunArgs>
327{
328}
329
330#[derive(Clone, Debug)]
331pub enum IotaExtraValueArgs {
332    Object(FakeID, Option<SequenceNumber>),
333    Digest(String),
334    Receiving(FakeID, Option<SequenceNumber>),
335    ImmShared(FakeID, Option<SequenceNumber>),
336}
337
338#[derive(Clone)]
339pub enum IotaValue {
340    MoveValue(MoveValue),
341    Object(FakeID, Option<SequenceNumber>),
342    ObjVec(Vec<(FakeID, Option<SequenceNumber>)>),
343    Digest(String),
344    Receiving(FakeID, Option<SequenceNumber>),
345    ImmShared(FakeID, Option<SequenceNumber>),
346}
347
348impl IotaExtraValueArgs {
349    fn parse_object_value<'a, I: Iterator<Item = (ValueToken, &'a str)>>(
350        parser: &mut MoveCLParser<'a, ValueToken, I>,
351    ) -> anyhow::Result<Self> {
352        let (fake_id, version) = Self::parse_receiving_or_object_value(parser, "object")?;
353        Ok(IotaExtraValueArgs::Object(fake_id, version))
354    }
355
356    fn parse_receiving_value<'a, I: Iterator<Item = (ValueToken, &'a str)>>(
357        parser: &mut MoveCLParser<'a, ValueToken, I>,
358    ) -> anyhow::Result<Self> {
359        let (fake_id, version) = Self::parse_receiving_or_object_value(parser, "receiving")?;
360        Ok(IotaExtraValueArgs::Receiving(fake_id, version))
361    }
362
363    fn parse_read_shared_value<'a, I: Iterator<Item = (ValueToken, &'a str)>>(
364        parser: &mut MoveCLParser<'a, ValueToken, I>,
365    ) -> anyhow::Result<Self> {
366        let (fake_id, version) = Self::parse_receiving_or_object_value(parser, "immshared")?;
367        Ok(IotaExtraValueArgs::ImmShared(fake_id, version))
368    }
369
370    fn parse_digest_value<'a, I: Iterator<Item = (ValueToken, &'a str)>>(
371        parser: &mut MoveCLParser<'a, ValueToken, I>,
372    ) -> anyhow::Result<Self> {
373        let contents = parser.advance(ValueToken::Ident)?;
374        ensure!(contents == "digest");
375        parser.advance(ValueToken::LParen)?;
376        let package = parser.advance(ValueToken::Ident)?;
377        parser.advance(ValueToken::RParen)?;
378        Ok(IotaExtraValueArgs::Digest(package.to_owned()))
379    }
380
381    fn parse_receiving_or_object_value<'a, I: Iterator<Item = (ValueToken, &'a str)>>(
382        parser: &mut MoveCLParser<'a, ValueToken, I>,
383        ident_name: &str,
384    ) -> anyhow::Result<(FakeID, Option<SequenceNumber>)> {
385        let contents = parser.advance(ValueToken::Ident)?;
386        ensure!(contents == ident_name);
387        parser.advance(ValueToken::LParen)?;
388        let i_str = parser.advance(ValueToken::Number)?;
389        let (i, _) = parse_u256(i_str)?;
390        let fake_id = if let Some(ValueToken::Comma) = parser.peek_tok() {
391            parser.advance(ValueToken::Comma)?;
392            let j_str = parser.advance(ValueToken::Number)?;
393            let (j, _) = parse_u64(j_str)?;
394            if i > U256::from(u64::MAX) {
395                bail!("Object ID too large")
396            }
397            FakeID::Enumerated(i.unchecked_as_u64(), j)
398        } else {
399            let mut u256_bytes = i.to_le_bytes().to_vec();
400            u256_bytes.reverse();
401            let address: IotaAddress = IotaAddress::from_bytes(&u256_bytes).unwrap();
402            FakeID::Known(address.into())
403        };
404        parser.advance(ValueToken::RParen)?;
405        let version = if let Some(ValueToken::AtSign) = parser.peek_tok() {
406            parser.advance(ValueToken::AtSign)?;
407            let v_str = parser.advance(ValueToken::Number)?;
408            let (v, _) = parse_u64(v_str)?;
409            Some(SequenceNumber::from_u64(v))
410        } else {
411            None
412        };
413        Ok((fake_id, version))
414    }
415}
416
417impl IotaValue {
418    fn assert_move_value(self) -> MoveValue {
419        match self {
420            IotaValue::MoveValue(v) => v,
421            IotaValue::Object(_, _) => panic!("unexpected nested IOTA object in args"),
422            IotaValue::ObjVec(_) => panic!("unexpected nested IOTA object vector in args"),
423            IotaValue::Digest(_) => panic!("unexpected nested IOTA package digest in args"),
424            IotaValue::Receiving(_, _) => panic!("unexpected nested IOTA receiving object in args"),
425            IotaValue::ImmShared(_, _) => panic!("unexpected nested IOTA shared object in args"),
426        }
427    }
428
429    fn assert_object(self) -> (FakeID, Option<SequenceNumber>) {
430        match self {
431            IotaValue::MoveValue(_) => panic!("unexpected nested non-object value in args"),
432            IotaValue::Object(id, version) => (id, version),
433            IotaValue::ObjVec(_) => panic!("unexpected nested IOTA object vector in args"),
434            IotaValue::Digest(_) => panic!("unexpected nested IOTA package digest in args"),
435            IotaValue::Receiving(_, _) => panic!("unexpected nested IOTA receiving object in args"),
436            IotaValue::ImmShared(_, _) => panic!("unexpected nested IOTA shared object in args"),
437        }
438    }
439
440    fn resolve_object(
441        fake_id: FakeID,
442        version: Option<SequenceNumber>,
443        test_adapter: &IotaTestAdapter,
444    ) -> anyhow::Result<Object> {
445        let id = match test_adapter.fake_to_real_object_id(fake_id) {
446            Some(id) => id,
447            None => bail!("INVALID TEST. Unknown object, object({})", fake_id),
448        };
449        let obj_res = if let Some(v) = version {
450            iota_types::storage::ObjectStore::get_object_by_key(&*test_adapter.executor, &id, v)
451        } else {
452            iota_types::storage::ObjectStore::get_object(&*test_adapter.executor, &id)
453        };
454        let obj = match obj_res {
455            Ok(Some(obj)) => obj,
456            Err(_) | Ok(None) => bail!("INVALID TEST. Could not load object argument {}", id),
457        };
458        Ok(obj)
459    }
460
461    fn receiving_arg(
462        fake_id: FakeID,
463        version: Option<SequenceNumber>,
464        test_adapter: &IotaTestAdapter,
465    ) -> anyhow::Result<ObjectArg> {
466        let obj = Self::resolve_object(fake_id, version, test_adapter)?;
467        Ok(ObjectArg::Receiving(obj.compute_object_reference()))
468    }
469
470    fn read_shared_arg(
471        fake_id: FakeID,
472        version: Option<SequenceNumber>,
473        test_adapter: &IotaTestAdapter,
474    ) -> anyhow::Result<ObjectArg> {
475        let obj = Self::resolve_object(fake_id, version, test_adapter)?;
476        let id = obj.id();
477        if let Owner::Shared {
478            initial_shared_version,
479        } = obj.owner
480        {
481            Ok(ObjectArg::SharedObject {
482                id,
483                initial_shared_version,
484                mutable: false,
485            })
486        } else {
487            bail!("{fake_id} is not a shared object.")
488        }
489    }
490
491    fn object_arg(
492        fake_id: FakeID,
493        version: Option<SequenceNumber>,
494        test_adapter: &IotaTestAdapter,
495    ) -> anyhow::Result<ObjectArg> {
496        let obj = Self::resolve_object(fake_id, version, test_adapter)?;
497        let id = obj.id();
498        match obj.owner {
499            Owner::Shared {
500                initial_shared_version,
501            } => Ok(ObjectArg::SharedObject {
502                id,
503                initial_shared_version,
504                mutable: true,
505            }),
506            Owner::AddressOwner(_) | Owner::ObjectOwner(_) | Owner::Immutable => {
507                let obj_ref = obj.compute_object_reference();
508                Ok(ObjectArg::ImmOrOwnedObject(obj_ref))
509            }
510        }
511    }
512
513    pub(crate) fn into_call_arg(self, test_adapter: &IotaTestAdapter) -> anyhow::Result<CallArg> {
514        Ok(match self {
515            IotaValue::Object(fake_id, version) => {
516                CallArg::Object(Self::object_arg(fake_id, version, test_adapter)?)
517            }
518            IotaValue::MoveValue(v) => CallArg::Pure(v.simple_serialize().unwrap()),
519            IotaValue::Receiving(fake_id, version) => {
520                CallArg::Object(Self::receiving_arg(fake_id, version, test_adapter)?)
521            }
522            IotaValue::ImmShared(fake_id, version) => {
523                CallArg::Object(Self::read_shared_arg(fake_id, version, test_adapter)?)
524            }
525            IotaValue::ObjVec(_) => bail!("obj vec is not supported as an input"),
526            IotaValue::Digest(pkg) => {
527                let pkg = Symbol::from(pkg);
528                let Some(staged) = test_adapter.staged_modules.get(&pkg) else {
529                    bail!("Unbound staged package '{pkg}'")
530                };
531                CallArg::Pure(bcs::to_bytes(&staged.digest).unwrap())
532            }
533        })
534    }
535
536    pub(crate) fn into_argument(
537        self,
538        builder: &mut ProgrammableTransactionBuilder,
539        test_adapter: &IotaTestAdapter,
540    ) -> anyhow::Result<Argument> {
541        match self {
542            IotaValue::ObjVec(vec) => builder.make_obj_vec(
543                vec.iter()
544                    .map(|(fake_id, version)| Self::object_arg(*fake_id, *version, test_adapter))
545                    .collect::<Result<Vec<ObjectArg>, _>>()?,
546            ),
547            value => {
548                let call_arg = value.into_call_arg(test_adapter)?;
549                builder.input(call_arg)
550            }
551        }
552    }
553}
554
555impl ParsableValue for IotaExtraValueArgs {
556    type ConcreteValue = IotaValue;
557
558    fn parse_value<'a, I: Iterator<Item = (ValueToken, &'a str)>>(
559        parser: &mut MoveCLParser<'a, ValueToken, I>,
560    ) -> Option<anyhow::Result<Self>> {
561        match parser.peek()? {
562            (ValueToken::Ident, "object") => Some(Self::parse_object_value(parser)),
563            (ValueToken::Ident, "digest") => Some(Self::parse_digest_value(parser)),
564            (ValueToken::Ident, "receiving") => Some(Self::parse_receiving_value(parser)),
565            (ValueToken::Ident, "immshared") => Some(Self::parse_read_shared_value(parser)),
566            _ => None,
567        }
568    }
569
570    fn move_value_into_concrete(v: MoveValue) -> anyhow::Result<Self::ConcreteValue> {
571        Ok(IotaValue::MoveValue(v))
572    }
573
574    fn concrete_vector(elems: Vec<Self::ConcreteValue>) -> anyhow::Result<Self::ConcreteValue> {
575        if !elems.is_empty() && matches!(elems[0], IotaValue::Object(_, _)) {
576            Ok(IotaValue::ObjVec(
577                elems.into_iter().map(IotaValue::assert_object).collect(),
578            ))
579        } else {
580            Ok(IotaValue::MoveValue(MoveValue::Vector(
581                elems
582                    .into_iter()
583                    .map(IotaValue::assert_move_value)
584                    .collect(),
585            )))
586        }
587    }
588
589    fn concrete_struct(values: Vec<Self::ConcreteValue>) -> anyhow::Result<Self::ConcreteValue> {
590        Ok(IotaValue::MoveValue(MoveValue::Struct(MoveStruct(
591            values.into_iter().map(|v| v.assert_move_value()).collect(),
592        ))))
593    }
594
595    fn into_concrete_value(
596        self,
597        _mapping: &impl Fn(&str) -> Option<move_core_types::account_address::AccountAddress>,
598    ) -> anyhow::Result<Self::ConcreteValue> {
599        match self {
600            IotaExtraValueArgs::Object(id, version) => Ok(IotaValue::Object(id, version)),
601            IotaExtraValueArgs::Digest(pkg) => Ok(IotaValue::Digest(pkg)),
602            IotaExtraValueArgs::Receiving(id, version) => Ok(IotaValue::Receiving(id, version)),
603            IotaExtraValueArgs::ImmShared(id, version) => Ok(IotaValue::ImmShared(id, version)),
604        }
605    }
606}
607
608fn parse_fake_id(s: &str) -> anyhow::Result<FakeID> {
609    Ok(if let Some((s1, s2)) = s.split_once(',') {
610        let (i, _) = parse_u64(s1)?;
611        let (j, _) = parse_u64(s2)?;
612        FakeID::Enumerated(i, j)
613    } else {
614        let (i, _) = parse_u256(s)?;
615        let mut u256_bytes = i.to_le_bytes().to_vec();
616        u256_bytes.reverse();
617        let address: IotaAddress = IotaAddress::from_bytes(&u256_bytes).unwrap();
618        FakeID::Known(address.into())
619    })
620}
621
622fn parse_policy(x: &str) -> anyhow::Result<u8> {
623    Ok(match x {
624        "compatible" => UpgradePolicy::COMPATIBLE,
625        "additive" => UpgradePolicy::ADDITIVE,
626        "dep_only" => UpgradePolicy::DEP_ONLY,
627        _ => bail!(
628            "Invalid upgrade policy {x}. Policy must be one of 'compatible', 'additive', or 'dep_only'"
629        ),
630    })
631}