1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
use clap::arg;
use soroban_env_host::xdr::{
    self, LedgerKey, LedgerKeyContractCode, LedgerKeyContractData, Limits, ReadXdr, ScAddress,
    ScVal,
};
use std::path::PathBuf;

use crate::{
    commands::contract::Durability,
    utils::{self},
    wasm,
};

#[derive(thiserror::Error, Debug)]
pub enum Error {
    #[error(transparent)]
    Spec(#[from] soroban_spec_tools::Error),
    #[error(transparent)]
    Xdr(#[from] xdr::Error),
    #[error("cannot parse contract ID {0}: {1}")]
    CannotParseContractId(String, stellar_strkey::DecodeError),
    #[error(transparent)]
    Wasm(#[from] wasm::Error),
}

#[derive(Debug, clap::Args, Clone)]
#[group(skip)]
pub struct Args {
    /// Contract ID to which owns the data entries.
    /// If no keys provided the Contract's instance will be extended
    #[arg(
        long = "id",
        required_unless_present = "wasm",
        required_unless_present = "wasm_hash"
    )]
    pub contract_id: Option<String>,
    /// Storage key (symbols only)
    #[arg(long = "key", conflicts_with = "key_xdr")]
    pub key: Option<Vec<String>>,
    /// Storage key (base64-encoded XDR)
    #[arg(long = "key-xdr", conflicts_with = "key")]
    pub key_xdr: Option<Vec<String>>,
    /// Path to Wasm file of contract code to extend
    #[arg(
        long,
        conflicts_with = "contract_id",
        conflicts_with = "key",
        conflicts_with = "key_xdr",
        conflicts_with = "wasm_hash"
    )]
    pub wasm: Option<PathBuf>,
    /// Path to Wasm file of contract code to extend
    #[arg(
        long,
        conflicts_with = "contract_id",
        conflicts_with = "key",
        conflicts_with = "key_xdr",
        conflicts_with = "wasm"
    )]
    pub wasm_hash: Option<String>,
    /// Storage entry durability
    #[arg(long, value_enum, required = true)]
    pub durability: Durability,
}

impl Args {
    pub fn parse_keys(&self) -> Result<Vec<LedgerKey>, Error> {
        let keys = if let Some(keys) = &self.key {
            keys.iter()
                .map(|key| {
                    Ok(soroban_spec_tools::from_string_primitive(
                        key,
                        &xdr::ScSpecTypeDef::Symbol,
                    )?)
                })
                .collect::<Result<Vec<_>, Error>>()?
        } else if let Some(keys) = &self.key_xdr {
            keys.iter()
                .map(|s| Ok(ScVal::from_xdr_base64(s, Limits::none())?))
                .collect::<Result<Vec<_>, Error>>()?
        } else if let Some(wasm) = &self.wasm {
            return Ok(vec![crate::wasm::Args { wasm: wasm.clone() }.try_into()?]);
        } else if let Some(wasm_hash) = &self.wasm_hash {
            return Ok(vec![LedgerKey::ContractCode(LedgerKeyContractCode {
                hash: xdr::Hash(
                    utils::contract_id_from_str(wasm_hash)
                        .map_err(|e| Error::CannotParseContractId(wasm_hash.clone(), e))?,
                ),
            })]);
        } else {
            vec![ScVal::LedgerKeyContractInstance]
        };
        let contract_id = contract_id(self.contract_id.as_ref().unwrap())?;

        Ok(keys
            .into_iter()
            .map(|key| {
                LedgerKey::ContractData(LedgerKeyContractData {
                    contract: ScAddress::Contract(xdr::Hash(contract_id)),
                    durability: (&self.durability).into(),
                    key,
                })
            })
            .collect())
    }
}

fn contract_id(s: &str) -> Result<[u8; 32], Error> {
    utils::contract_id_from_str(s).map_err(|e| Error::CannotParseContractId(s.to_string(), e))
}