layer_climb_cli/command/
contract.rs

1use anyhow::Result;
2use clap::Subcommand;
3use layer_climb::{prelude::*, proto::abci::TxResponse};
4use std::path::PathBuf;
5
6#[derive(Debug, Clone, Subcommand)]
7pub enum ContractCommand {
8    /// Uploads a contract to the chain
9    Upload {
10        /// Path to the .wasm file to upload
11        #[arg(long)]
12        wasm_file: PathBuf,
13    },
14
15    /// Instantiates a contract on the chain
16    Instantiate {
17        /// The code ID of the contract, obtained from `upload`
18        #[arg(long)]
19        code_id: u64,
20        /// The instantiation message, as a json-encoded string
21        #[arg(long)]
22        msg: Option<String>,
23        /// Optional label for the contract
24        #[arg(long)]
25        label: Option<String>,
26        /// Optional funds to send, if not set will use the chain gas denom
27        #[arg(long)]
28        funds_denom: Option<String>,
29        /// Optional funds to send, if not set no funds will be sent
30        #[arg(long)]
31        funds_amount: Option<String>,
32    },
33
34    /// Executes a contract on the chain
35    Execute {
36        /// The address of the contract, obtained from `instantiate`
37        #[arg(long)]
38        address: String,
39        /// The execution message, as a json-encoded string
40        #[arg(long)]
41        msg: Option<String>,
42        /// Optional funds to send, if not set will use the chain gas denom
43        #[arg(long)]
44        funds_denom: Option<String>,
45        /// Optional funds to send, if not set no funds will be sent
46        #[arg(long)]
47        funds_amount: Option<String>,
48    },
49
50    /// Queries a contract on the chain
51    Query {
52        /// The address of the contract, obtained from `instantiate`
53        #[arg(long)]
54        address: String,
55        /// The query message, as a json-encoded string
56        #[arg(long)]
57        msg: Option<String>,
58    },
59}
60
61impl ContractCommand {
62    pub async fn run(&self, client: impl Into<AnyClient>, log: impl Fn(ContractLog)) -> Result<()> {
63        let client = client.into();
64        match self {
65            ContractCommand::Upload { wasm_file } => {
66                let wasm_byte_code = tokio::fs::read(wasm_file).await?;
67                let (code_id, tx_resp) = client
68                    .as_signing()
69                    .contract_upload_file(wasm_byte_code, None)
70                    .await?;
71
72                log(ContractLog::Upload {
73                    code_id,
74                    tx_resp: Box::new(tx_resp),
75                });
76            }
77            ContractCommand::Instantiate {
78                code_id,
79                msg,
80                label,
81                funds_denom,
82                funds_amount,
83            } => {
84                let (addr, tx_resp) = client
85                    .as_signing()
86                    .contract_instantiate(
87                        client.as_signing().addr.clone(),
88                        *code_id,
89                        label.clone().unwrap_or_default(),
90                        &contract_str_to_msg(msg.as_deref())?,
91                        get_funds(
92                            &client.as_querier().chain_config,
93                            funds_denom.clone(),
94                            funds_amount.clone(),
95                        ),
96                        None,
97                    )
98                    .await?;
99
100                log(ContractLog::Instantiate {
101                    addr,
102                    tx_resp: Box::new(tx_resp),
103                });
104            }
105            ContractCommand::Execute {
106                address,
107                msg,
108                funds_denom,
109                funds_amount,
110            } => {
111                let address = client.as_querier().chain_config.parse_address(address)?;
112
113                let tx_resp = client
114                    .as_signing()
115                    .contract_execute(
116                        &address,
117                        &contract_str_to_msg(msg.as_deref())?,
118                        get_funds(
119                            &client.as_querier().chain_config,
120                            funds_denom.clone(),
121                            funds_amount.clone(),
122                        ),
123                        None,
124                    )
125                    .await?;
126
127                log(ContractLog::Execute {
128                    tx_resp: Box::new(tx_resp),
129                });
130            }
131            ContractCommand::Query { address, msg } => {
132                let address = client.as_querier().chain_config.parse_address(address)?;
133
134                let resp = client
135                    .as_querier()
136                    .contract_smart_raw(&address, &contract_str_to_msg(msg.as_deref())?)
137                    .await?;
138                let resp = std::str::from_utf8(&resp)?;
139
140                log(ContractLog::Query {
141                    response: resp.to_string(),
142                });
143            }
144        }
145        Ok(())
146    }
147}
148
149fn get_funds(
150    chain_config: &ChainConfig,
151    funds_denom: Option<String>,
152    funds_amount: Option<String>,
153) -> Vec<Coin> {
154    match funds_amount {
155        Some(funds_amount) => {
156            let funds_denom = funds_denom.unwrap_or(chain_config.gas_denom.clone());
157            vec![new_coin(funds_denom, funds_amount)]
158        }
159        None => Vec::new(),
160    }
161}
162
163pub enum ContractLog {
164    Upload {
165        code_id: u64,
166        tx_resp: Box<TxResponse>,
167    },
168    Instantiate {
169        addr: Address,
170        tx_resp: Box<TxResponse>,
171    },
172    Execute {
173        tx_resp: Box<TxResponse>,
174    },
175    Query {
176        response: String,
177    },
178}