Skip to content

Commit 7a03eff

Browse files
authored
Merge pull request #103 from vernonjohnson/vt-execute-and-query
2 parents ce0ecd8 + 141b803 commit 7a03eff

File tree

7 files changed

+307
-0
lines changed

7 files changed

+307
-0
lines changed

README.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,20 @@ beaker wasm upgrade counter --signer-account test1 --no-wasm-opt
290290

291291
You can find more information about their options [here](./docs/commands/beaker_wasm.md).
292292

293+
### Execute Contract Messages
294+
295+
Contract messages can be executed using the `beaker wasm execute` subcommand. For example:
296+
```sh
297+
beaker wasm execute counter --raw '{ "increment": {} }' --signer-account test1
298+
```
299+
300+
### Query Contract State
301+
You can query contract state by submitting query messages with the `beaker wasm query` command. For example:
302+
```sh
303+
beaker wasm query counter --raw '{"get_count": {}}'
304+
```
305+
306+
293307
### Signers
294308

295309
Whenever you run command that requires signing transactions, there are 3 options you can reference your private keys:

packages/cli/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ serde = "1.0.137"
2828
serde_json = "1.0.81"
2929
serde_yaml = "0.8"
3030
tendermint-rpc = "0.23.7"
31+
textwrap = "0.15.0"
3132
tokio = {version = "1.18.2", features = ["full"]}
3233
toml = "0.5.9"
3334

packages/cli/src/modules/wasm/entrypoint.rs

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,35 @@ pub enum WasmCmd {
252252
#[clap(subcommand)]
253253
cmd: ProposalCmd,
254254
},
255+
/// Execute contract messages
256+
Execute {
257+
contract_name: String,
258+
259+
#[clap(short, long, default_value = "default")]
260+
label: String,
261+
262+
#[clap(short, long)]
263+
raw: Option<String>,
264+
265+
#[clap(short, long)]
266+
funds: Option<String>,
267+
268+
#[clap(flatten)]
269+
base_tx_args: BaseTxArgs,
270+
},
271+
/// Query contract state
272+
Query {
273+
contract_name: String,
274+
275+
#[clap(short, long, default_value = "default")]
276+
label: String,
277+
278+
#[clap(short, long)]
279+
raw: Option<String>,
280+
281+
#[clap(flatten)]
282+
base_tx_args: BaseTxArgs,
283+
},
255284
}
256285

257286
#[derive(new)]
@@ -607,6 +636,49 @@ impl<'a> Module<'a, WasmConfig, WasmCmd, anyhow::Error> for WasmModule {
607636
run_command(node_pkg().arg("run").arg("build"))?;
608637
Ok(())
609638
}
639+
WasmCmd::Execute {
640+
contract_name,
641+
label,
642+
raw,
643+
funds,
644+
base_tx_args,
645+
} => {
646+
let BaseTxArgs {
647+
network,
648+
signer_args,
649+
gas_args,
650+
timeout_height,
651+
}: &BaseTxArgs = base_tx_args;
652+
ops::execute(
653+
&ctx,
654+
contract_name,
655+
label.as_str(),
656+
raw.as_ref(),
657+
funds.as_ref().map(|s| s.as_str()).try_into()?,
658+
network,
659+
timeout_height,
660+
{
661+
let global_conf = ctx.global_config()?;
662+
&Gas::from_args(
663+
gas_args,
664+
global_conf.gas_price(),
665+
global_conf.gas_adjustment(),
666+
)?
667+
},
668+
signer_args.private_key(&ctx.global_config()?)?,
669+
)?;
670+
Ok(())
671+
}
672+
WasmCmd::Query {
673+
contract_name,
674+
label,
675+
raw,
676+
base_tx_args,
677+
} => {
678+
let BaseTxArgs { network, .. }: &BaseTxArgs = base_tx_args;
679+
ops::query(&ctx, contract_name, label.as_str(), raw.as_ref(), network)?;
680+
Ok(())
681+
}
610682
}
611683
}
612684
}
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
use crate::attrs_format;
2+
use crate::modules::wasm::config::WasmConfig;
3+
use crate::support::coin::Coins;
4+
use crate::support::cosmos::ResponseValuePicker;
5+
use crate::support::future::block;
6+
use crate::support::gas::Gas;
7+
use crate::support::ops_response::OpResponseDisplay;
8+
use crate::support::state::State;
9+
use crate::{framework::Context, support::cosmos::Client};
10+
use anyhow::anyhow;
11+
use anyhow::Context as _;
12+
use anyhow::Result;
13+
use cosmrs::cosmwasm::MsgExecuteContract;
14+
use cosmrs::crypto::secp256k1::SigningKey;
15+
use cosmrs::tx::Msg;
16+
use cosmrs::AccountId;
17+
18+
use std::{fs, vec};
19+
20+
#[allow(clippy::too_many_arguments)]
21+
pub fn execute<'a, Ctx: Context<'a, WasmConfig>>(
22+
ctx: &Ctx,
23+
contract_name: &str,
24+
label: &str,
25+
raw: Option<&String>,
26+
funds: Coins,
27+
network: &str,
28+
timeout_height: &u32,
29+
gas: &Gas,
30+
signing_key: SigningKey,
31+
) -> Result<ExecuteResponse> {
32+
let global_config = ctx.global_config()?;
33+
let account_prefix = global_config.account_prefix().as_str();
34+
35+
let network_info = global_config
36+
.networks()
37+
.get(network)
38+
.with_context(|| format!("Unable to find network config: {network}"))?
39+
.to_owned();
40+
41+
let client = Client::new(network_info.clone()).to_signing_client(signing_key, account_prefix);
42+
let state = State::load_by_network(network_info, ctx.root()?)?;
43+
44+
let contract = state
45+
.get_ref(network, contract_name)?
46+
.addresses()
47+
.get(label)
48+
.with_context(|| format!("Unable to retrieve contract for {contract_name}:{label}"))?
49+
.parse::<AccountId>()
50+
.map_err(|e| anyhow!(e))?;
51+
52+
let msg_instantiate_contract = MsgExecuteContract {
53+
sender: client.signer_account_id(),
54+
contract,
55+
msg: raw
56+
.map(|s| s.as_bytes().to_vec())
57+
.map(Ok)
58+
.unwrap_or_else(|| {
59+
let path = ctx
60+
.root()?
61+
.join("contracts")
62+
.join(contract_name)
63+
.join("execute-msgs")
64+
.join(format!("{label}.json"));
65+
fs::read_to_string(&path)
66+
.with_context(|| format!("Unable to execute with `{}`", path.to_string_lossy()))
67+
.map(|s| s.as_bytes().to_vec())
68+
})?,
69+
funds: funds.into(),
70+
};
71+
72+
block(async {
73+
let response = client
74+
.sign_and_broadcast(
75+
vec![msg_instantiate_contract.to_any().unwrap()],
76+
gas,
77+
"",
78+
timeout_height,
79+
)
80+
.await?;
81+
82+
let contract_address = response.pick("execute", "_contract_address").to_string();
83+
84+
let execute_response = ExecuteResponse {
85+
contract_address,
86+
label: label.to_string(),
87+
};
88+
89+
execute_response.log();
90+
91+
Ok(execute_response)
92+
})
93+
}
94+
95+
#[allow(dead_code)]
96+
pub struct ExecuteResponse {
97+
pub label: String,
98+
pub contract_address: String,
99+
}
100+
101+
impl OpResponseDisplay for ExecuteResponse {
102+
fn headline() -> &'static str {
103+
"Contract executed successfully!! 🎉 "
104+
}
105+
fn attrs(&self) -> Vec<String> {
106+
attrs_format! { self | label, contract_address }
107+
}
108+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,23 @@
11
pub mod build;
22
pub mod clear_admin;
33
pub mod deploy;
4+
pub mod execute;
45
pub mod instantiate;
56
pub mod migrate;
67
pub mod new;
8+
pub mod query;
79
pub mod store_code;
810
pub mod update_admin;
911
pub mod upgrade;
1012

1113
pub use build::build;
1214
pub use clear_admin::clear_admin;
1315
pub use deploy::deploy;
16+
pub use execute::execute;
1417
pub use instantiate::instantiate;
1518
pub use migrate::migrate;
1619
pub use new::new;
20+
pub use query::query;
1721
pub use store_code::store_code;
1822
pub use update_admin::update_admin;
1923
pub use upgrade::upgrade;
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
use crate::attrs_format;
2+
use crate::modules::wasm::config::WasmConfig;
3+
use crate::support::future::block;
4+
use crate::support::ops_response::OpResponseDisplay;
5+
use crate::support::state::State;
6+
use crate::{framework::Context, support::cosmos::Client};
7+
use anyhow::anyhow;
8+
use anyhow::Context as _;
9+
use anyhow::Result;
10+
use cosmrs::AccountId;
11+
12+
use std::fs;
13+
14+
#[allow(clippy::too_many_arguments)]
15+
pub fn query<'a, Ctx: Context<'a, WasmConfig>>(
16+
ctx: &Ctx,
17+
contract_name: &str,
18+
label: &str,
19+
raw: Option<&String>,
20+
network: &str,
21+
) -> Result<QueryResponse> {
22+
let global_config = ctx.global_config()?;
23+
let network_info = global_config
24+
.networks()
25+
.get(network)
26+
.with_context(|| format!("Unable to find network config: {network}"))?
27+
.to_owned();
28+
29+
let client = Client::new(network_info.clone());
30+
let state = State::load_by_network(network_info, ctx.root()?)?;
31+
32+
let contract = state
33+
.get_ref(network, contract_name)?
34+
.addresses()
35+
.get(label)
36+
.with_context(|| format!("Unable to retrieve contract for {contract_name}:{label}"))?
37+
.parse::<AccountId>()
38+
.map_err(|e| anyhow!(e))?;
39+
40+
let query_msg = raw
41+
.map(|s| s.as_bytes().to_vec())
42+
.map(Ok)
43+
.unwrap_or_else(|| {
44+
let path = ctx
45+
.root()?
46+
.join("contracts")
47+
.join(contract_name)
48+
.join("query-msgs")
49+
.join(format!("{label}.json"));
50+
fs::read_to_string(&path)
51+
.with_context(|| format!("Unable to execute with `{}`", path.to_string_lossy()))
52+
.map(|s| s.as_bytes().to_vec())
53+
})?;
54+
55+
block(async {
56+
let response = client.query_smart(contract.to_string(), query_msg).await?;
57+
let pretty_json_response = serde_json::to_string_pretty(
58+
&serde_json::from_slice::<serde_json::Value>(&response)
59+
.with_context(|| "Unable to deserialize response")?,
60+
)?;
61+
62+
let query_response = QueryResponse {
63+
label: label.to_string(),
64+
contract_address: contract.to_string(),
65+
data: format!("\n{}", textwrap::indent(&pretty_json_response, " ")),
66+
};
67+
68+
query_response.log();
69+
70+
Ok(query_response)
71+
})
72+
}
73+
74+
#[allow(dead_code)]
75+
pub struct QueryResponse {
76+
pub label: String,
77+
pub contract_address: String,
78+
pub data: String,
79+
}
80+
81+
impl OpResponseDisplay for QueryResponse {
82+
fn headline() -> &'static str {
83+
"Succesffuly executed query!! 🎉 "
84+
}
85+
fn attrs(&self) -> Vec<String> {
86+
attrs_format! { self | label, contract_address, data }
87+
}
88+
}

packages/cli/src/support/cosmos.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,26 @@ impl Client {
102102
.map_err(|e: cosmrs::ErrorReport| anyhow!(e))
103103
}
104104

105+
pub async fn query_smart(&self, address: String, query_data: Vec<u8>) -> Result<Vec<u8>> {
106+
use cosmos_sdk_proto::cosmwasm::wasm::v1::*;
107+
let grpc_endpoint = self.network.grpc_endpoint();
108+
109+
let mut c = query_client::QueryClient::connect(self.network.grpc_endpoint().clone())
110+
.await
111+
.context(format!("Unable to connect to {grpc_endpoint}"))?;
112+
113+
let res = c
114+
.smart_contract_state(QuerySmartContractStateRequest {
115+
address,
116+
query_data,
117+
})
118+
.await?
119+
.into_inner()
120+
.data;
121+
122+
Ok(res)
123+
}
124+
105125
pub async fn proposal(&self, proposal_id: &u64) -> Result<Proposal> {
106126
use cosmos_sdk_proto::cosmos::gov::v1beta1::*;
107127
let grpc_endpoint = self.network.grpc_endpoint();

0 commit comments

Comments
 (0)