2023-12-14 11:18:19 +00:00
|
|
|
#![doc = include_str!("../README.md")]
|
|
|
|
#![forbid(unsafe_code)]
|
|
|
|
#![warn(
|
|
|
|
clippy::checked_conversions,
|
|
|
|
clippy::panic,
|
|
|
|
clippy::panic_in_result_fn,
|
|
|
|
clippy::unwrap_used,
|
|
|
|
missing_docs,
|
|
|
|
trivial_casts,
|
|
|
|
trivial_numeric_casts,
|
|
|
|
rust_2018_idioms,
|
|
|
|
unused_lifetimes,
|
|
|
|
unused_import_braces,
|
|
|
|
unused_qualifications
|
|
|
|
)]
|
|
|
|
|
|
|
|
use std::error::Error;
|
|
|
|
|
|
|
|
use clap::{Parser, Subcommand};
|
|
|
|
use cosmrs::AccountId;
|
2023-12-19 14:39:03 +00:00
|
|
|
use ibc_relayer_types::{
|
2023-12-22 10:35:42 +00:00
|
|
|
core::ics23_commitment::commitment::CommitmentRoot, core::ics23_commitment::specs::ProofSpecs,
|
2023-12-19 14:39:03 +00:00
|
|
|
};
|
2023-12-22 10:30:57 +00:00
|
|
|
use tendermint::block::Height;
|
|
|
|
use tendermint::AppHash;
|
|
|
|
use tendermint_rpc::endpoint::abci_query::AbciQuery;
|
|
|
|
use tendermint_rpc::endpoint::status::Response;
|
2023-12-19 09:11:07 +00:00
|
|
|
use tendermint_rpc::{client::HttpClient as TmRpcClient, Client, HttpClientUrl};
|
2023-12-14 11:18:19 +00:00
|
|
|
|
2023-12-22 10:35:42 +00:00
|
|
|
mod merkle;
|
|
|
|
|
2023-12-14 11:18:19 +00:00
|
|
|
#[derive(Debug, Parser)]
|
|
|
|
#[command(author, version, about, long_about = None)]
|
|
|
|
struct Cli {
|
|
|
|
/// Main command
|
|
|
|
#[command(subcommand)]
|
|
|
|
command: Command,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug, Subcommand)]
|
|
|
|
enum Command {
|
|
|
|
/// Retrieve a merkle-proof for CosmWasm state
|
|
|
|
CwQueryProofs {
|
|
|
|
#[clap(long, default_value = "http://127.0.0.1:26657")]
|
|
|
|
rpc_url: HttpClientUrl,
|
|
|
|
|
|
|
|
/// Address of the CosmWasm contract
|
|
|
|
#[clap(long)]
|
|
|
|
contract_address: AccountId,
|
|
|
|
|
|
|
|
/// Storage key of the state item for which proofs must be retrieved
|
|
|
|
#[clap(long)]
|
|
|
|
storage_key: String,
|
2023-12-22 09:55:58 +00:00
|
|
|
|
|
|
|
/// Storage namespace of the state item for which proofs must be retrieved
|
|
|
|
/// (only makes sense when dealing with maps)
|
|
|
|
#[clap(long)]
|
|
|
|
storage_namespace: Option<String>,
|
2023-12-14 11:18:19 +00:00
|
|
|
},
|
|
|
|
}
|
|
|
|
|
2023-12-19 09:11:07 +00:00
|
|
|
const WASM_STORE_KEY: &str = "/store/wasm/key";
|
|
|
|
const CONTRACT_STORE_PREFIX: u8 = 0x03;
|
|
|
|
|
2023-12-14 11:18:19 +00:00
|
|
|
#[tokio::main]
|
|
|
|
async fn main() -> Result<(), Box<dyn Error>> {
|
|
|
|
let args = Cli::parse();
|
|
|
|
|
|
|
|
match args.command {
|
|
|
|
Command::CwQueryProofs {
|
|
|
|
rpc_url,
|
|
|
|
contract_address,
|
|
|
|
storage_key,
|
2023-12-22 09:55:58 +00:00
|
|
|
storage_namespace,
|
2023-12-14 11:18:19 +00:00
|
|
|
} => {
|
|
|
|
let client = TmRpcClient::builder(rpc_url).build()?;
|
2023-12-19 14:39:03 +00:00
|
|
|
let status = client.status().await?;
|
2023-12-22 10:30:57 +00:00
|
|
|
let (proof_height, latest_app_hash) = latest_proof_height_hash(status);
|
|
|
|
|
|
|
|
let path = WASM_STORE_KEY.to_owned();
|
|
|
|
let data = query_data(&contract_address, storage_key, storage_namespace);
|
2023-12-19 09:11:07 +00:00
|
|
|
let result = client
|
2023-12-22 10:30:57 +00:00
|
|
|
.abci_query(Some(path), data, Some(proof_height), true)
|
2023-12-19 09:11:07 +00:00
|
|
|
.await?;
|
|
|
|
|
2023-12-22 10:30:57 +00:00
|
|
|
let value = verify_proof(latest_app_hash, result)?;
|
|
|
|
println!("{}", String::from_utf8(value)?);
|
2023-12-14 11:18:19 +00:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
2023-12-19 14:39:03 +00:00
|
|
|
|
2023-12-22 10:30:57 +00:00
|
|
|
fn latest_proof_height_hash(status: Response) -> (Height, AppHash) {
|
|
|
|
let proof_height = {
|
|
|
|
let latest_height = status.sync_info.latest_block_height;
|
|
|
|
(latest_height.value() - 1)
|
|
|
|
.try_into()
|
|
|
|
.expect("infallible conversion")
|
|
|
|
};
|
|
|
|
let latest_app_hash = status.sync_info.latest_app_hash;
|
|
|
|
|
|
|
|
(proof_height, latest_app_hash)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn query_data(
|
|
|
|
contract_address: &AccountId,
|
|
|
|
storage_key: String,
|
|
|
|
storage_namespace: Option<String>,
|
|
|
|
) -> Vec<u8> {
|
|
|
|
let mut data = vec![CONTRACT_STORE_PREFIX];
|
|
|
|
data.append(&mut contract_address.to_bytes());
|
|
|
|
if let Some(namespace) = storage_namespace {
|
|
|
|
data.extend_from_slice(&encode_length(namespace.as_bytes()));
|
|
|
|
data.append(&mut namespace.into_bytes());
|
|
|
|
}
|
|
|
|
data.append(&mut storage_key.into_bytes());
|
|
|
|
data
|
|
|
|
}
|
|
|
|
|
2023-12-22 09:55:58 +00:00
|
|
|
// Copied from cw-storage-plus
|
|
|
|
fn encode_length(namespace: &[u8]) -> [u8; 2] {
|
2023-12-22 10:30:57 +00:00
|
|
|
assert!(
|
|
|
|
namespace.len() <= 0xFFFF,
|
|
|
|
"only supports namespaces up to length 0xFFFF"
|
|
|
|
);
|
|
|
|
|
2023-12-22 09:55:58 +00:00
|
|
|
let length_bytes = (namespace.len() as u32).to_be_bytes();
|
|
|
|
[length_bytes[2], length_bytes[3]]
|
|
|
|
}
|
|
|
|
|
2023-12-22 10:30:57 +00:00
|
|
|
fn verify_proof(latest_app_hash: AppHash, result: AbciQuery) -> Result<Vec<u8>, Box<dyn Error>> {
|
2023-12-22 10:35:42 +00:00
|
|
|
let proof = merkle::convert_tm_to_ics_merkle_proof(&result.proof.expect("queried with proof"))?;
|
2023-12-22 10:30:57 +00:00
|
|
|
let root = CommitmentRoot::from_bytes(latest_app_hash.as_bytes());
|
|
|
|
let prefixed_key = vec!["wasm".to_string().into_bytes(), result.key];
|
|
|
|
|
|
|
|
proof.verify_membership(
|
|
|
|
&ProofSpecs::cosmos(),
|
|
|
|
root.into(),
|
|
|
|
prefixed_key,
|
|
|
|
result.value.clone(),
|
|
|
|
0,
|
|
|
|
)?;
|
|
|
|
|
|
|
|
Ok(result.value)
|
|
|
|
}
|