270 lines
7.8 KiB
Rust
270 lines
7.8 KiB
Rust
|
use std::{error::Error, fs::File, io::Read, path::PathBuf};
|
||
|
|
||
|
use anyhow::anyhow;
|
||
|
use cosmos_sdk_proto::{
|
||
|
cosmos::{
|
||
|
auth::v1beta1::{
|
||
|
query_client::QueryClient as AuthQueryClient, BaseAccount as RawBaseAccount,
|
||
|
QueryAccountRequest,
|
||
|
},
|
||
|
tx::v1beta1::{
|
||
|
service_client::ServiceClient, BroadcastMode, BroadcastTxRequest, BroadcastTxResponse,
|
||
|
},
|
||
|
},
|
||
|
cosmwasm::wasm::v1::{
|
||
|
query_client::QueryClient as WasmdQueryClient, QuerySmartContractStateRequest,
|
||
|
},
|
||
|
traits::Message,
|
||
|
Any,
|
||
|
};
|
||
|
use cosmrs::{
|
||
|
auth::BaseAccount,
|
||
|
cosmwasm::MsgExecuteContract,
|
||
|
crypto::{secp256k1::SigningKey, PublicKey},
|
||
|
tendermint::chain::Id as TmChainId,
|
||
|
tx,
|
||
|
tx::{Fee, Msg, SignDoc, SignerInfo},
|
||
|
AccountId, Coin,
|
||
|
};
|
||
|
use reqwest::Url;
|
||
|
use serde::de::DeserializeOwned;
|
||
|
|
||
|
use crate::CwClient;
|
||
|
|
||
|
#[derive(Clone, Debug)]
|
||
|
pub struct GrpcClient {
|
||
|
sk_file: PathBuf,
|
||
|
url: Url,
|
||
|
}
|
||
|
|
||
|
impl GrpcClient {
|
||
|
pub fn new(sk_file: PathBuf, url: Url) -> Self {
|
||
|
Self { sk_file, url }
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#[async_trait::async_trait]
|
||
|
impl CwClient for GrpcClient {
|
||
|
type Address = AccountId;
|
||
|
type Query = serde_json::Value;
|
||
|
type RawQuery = String;
|
||
|
type ChainId = TmChainId;
|
||
|
type Error = anyhow::Error;
|
||
|
|
||
|
async fn query_smart<R: DeserializeOwned + Send>(
|
||
|
&self,
|
||
|
contract: &Self::Address,
|
||
|
query: Self::Query,
|
||
|
) -> Result<R, Self::Error> {
|
||
|
let mut client = WasmdQueryClient::connect(self.url.to_string()).await?;
|
||
|
|
||
|
let raw_query_request = QuerySmartContractStateRequest {
|
||
|
address: contract.to_string(),
|
||
|
query_data: query.to_string().into_bytes(),
|
||
|
};
|
||
|
|
||
|
let raw_query_response = client.smart_contract_state(raw_query_request).await?;
|
||
|
|
||
|
let raw_value = raw_query_response.into_inner().data;
|
||
|
serde_json::from_slice(&raw_value)
|
||
|
.map_err(|e| anyhow!("failed to deserialize JSON reponse: {}", e))
|
||
|
}
|
||
|
|
||
|
fn query_raw<R: DeserializeOwned + Default>(
|
||
|
&self,
|
||
|
_contract: &Self::Address,
|
||
|
_query: Self::RawQuery,
|
||
|
) -> Result<R, Self::Error> {
|
||
|
unimplemented!()
|
||
|
}
|
||
|
|
||
|
fn query_tx<R: DeserializeOwned + Default>(&self, _txhash: &str) -> Result<R, Self::Error> {
|
||
|
unimplemented!()
|
||
|
}
|
||
|
|
||
|
async fn tx_execute<M: ToString + Send>(
|
||
|
&self,
|
||
|
contract: &Self::Address,
|
||
|
chain_id: &TmChainId,
|
||
|
gas: u64,
|
||
|
_sender: &str,
|
||
|
msg: M,
|
||
|
_fees: &str,
|
||
|
) -> Result<String, Self::Error> {
|
||
|
let secret = {
|
||
|
let mut secret_hex = String::new();
|
||
|
let mut sk_file = File::open(self.sk_file.clone())?;
|
||
|
sk_file.read_to_string(&mut secret_hex)?;
|
||
|
let secret = hex::decode(secret_hex)?;
|
||
|
SigningKey::from_slice(&secret)
|
||
|
.map_err(|e| anyhow!("failed to read secret key: {}", e))?
|
||
|
};
|
||
|
|
||
|
let tm_pubkey = secret.public_key();
|
||
|
let sender = tm_pubkey
|
||
|
.account_id("neutron")
|
||
|
.map_err(|e| anyhow!("failed to create AccountId from pubkey: {}", e))?;
|
||
|
|
||
|
let msgs = vec![MsgExecuteContract {
|
||
|
sender: sender.clone(),
|
||
|
contract: contract.clone(),
|
||
|
msg: msg.to_string().into_bytes(),
|
||
|
funds: vec![],
|
||
|
}
|
||
|
.to_any()
|
||
|
.unwrap()];
|
||
|
|
||
|
let account = account_info(self.url.to_string(), sender.to_string())
|
||
|
.await
|
||
|
.map_err(|e| anyhow!("error querying account info: {}", e))?;
|
||
|
let amount = Coin {
|
||
|
amount: 11000u128,
|
||
|
denom: "untrn".parse().expect("hardcoded denom"),
|
||
|
};
|
||
|
let tx_bytes = tx_bytes(
|
||
|
&secret,
|
||
|
amount,
|
||
|
gas,
|
||
|
tm_pubkey,
|
||
|
msgs,
|
||
|
account.sequence,
|
||
|
account.account_number,
|
||
|
chain_id,
|
||
|
)
|
||
|
.map_err(|e| anyhow!("failed to create msg/tx: {}", e))?;
|
||
|
|
||
|
let response = send_tx(self.url.to_string(), tx_bytes)
|
||
|
.await
|
||
|
.map_err(|e| anyhow!("failed to send tx: {}", e))?;
|
||
|
println!("{response:?}");
|
||
|
Ok(response
|
||
|
.tx_response
|
||
|
.map(|tx_response| tx_response.txhash)
|
||
|
.unwrap_or_default())
|
||
|
}
|
||
|
|
||
|
fn deploy<M: ToString>(
|
||
|
&self,
|
||
|
_chain_id: &TmChainId,
|
||
|
_sender: &str,
|
||
|
_wasm_path: M,
|
||
|
) -> Result<String, Self::Error> {
|
||
|
unimplemented!()
|
||
|
}
|
||
|
|
||
|
fn init<M: ToString>(
|
||
|
&self,
|
||
|
_chain_id: &TmChainId,
|
||
|
_sender: &str,
|
||
|
_code_id: u64,
|
||
|
_init_msg: M,
|
||
|
_label: &str,
|
||
|
) -> Result<String, Self::Error> {
|
||
|
unimplemented!()
|
||
|
}
|
||
|
|
||
|
fn trusted_height_hash(&self) -> Result<(u64, String), Self::Error> {
|
||
|
unimplemented!()
|
||
|
}
|
||
|
}
|
||
|
|
||
|
pub async fn account_info(
|
||
|
node: impl ToString,
|
||
|
address: impl ToString,
|
||
|
) -> Result<BaseAccount, Box<dyn Error>> {
|
||
|
let mut client = AuthQueryClient::connect(node.to_string()).await?;
|
||
|
let request = tonic::Request::new(QueryAccountRequest {
|
||
|
address: address.to_string(),
|
||
|
});
|
||
|
let response = client.account(request).await?;
|
||
|
let response = RawBaseAccount::decode(response.into_inner().account.unwrap().value.as_slice())?;
|
||
|
let account = BaseAccount::try_from(response)?;
|
||
|
Ok(account)
|
||
|
}
|
||
|
|
||
|
#[allow(clippy::too_many_arguments)]
|
||
|
pub fn tx_bytes(
|
||
|
secret: &SigningKey,
|
||
|
amount: Coin,
|
||
|
gas: u64,
|
||
|
tm_pubkey: PublicKey,
|
||
|
msgs: Vec<Any>,
|
||
|
sequence_number: u64,
|
||
|
account_number: u64,
|
||
|
chain_id: &TmChainId,
|
||
|
) -> Result<Vec<u8>, Box<dyn Error>> {
|
||
|
let tx_body = tx::Body::new(msgs, "", 0u16);
|
||
|
let signer_info = SignerInfo::single_direct(Some(tm_pubkey), sequence_number);
|
||
|
let auth_info = signer_info.auth_info(Fee::from_amount_and_gas(amount, gas));
|
||
|
let sign_doc = SignDoc::new(&tx_body, &auth_info, chain_id, account_number)?;
|
||
|
let tx_signed = sign_doc.sign(secret)?;
|
||
|
Ok(tx_signed.to_bytes()?)
|
||
|
}
|
||
|
|
||
|
pub async fn send_tx(
|
||
|
node: impl ToString,
|
||
|
tx_bytes: Vec<u8>,
|
||
|
) -> Result<BroadcastTxResponse, Box<dyn Error>> {
|
||
|
let mut client = ServiceClient::connect(node.to_string()).await?;
|
||
|
let request = tonic::Request::new(BroadcastTxRequest {
|
||
|
tx_bytes,
|
||
|
mode: BroadcastMode::Sync.into(),
|
||
|
});
|
||
|
let tx_response = client.broadcast_tx(request).await?;
|
||
|
Ok(tx_response.into_inner())
|
||
|
}
|
||
|
|
||
|
#[cfg(test)]
|
||
|
mod tests {
|
||
|
use std::error::Error;
|
||
|
|
||
|
use serde_json::json;
|
||
|
use transfers_contract::msg::{execute::Request, QueryMsg::GetRequests};
|
||
|
|
||
|
use crate::{CwClient, GrpcClient};
|
||
|
|
||
|
#[tokio::test]
|
||
|
#[ignore]
|
||
|
async fn test_query() -> Result<(), Box<dyn Error>> {
|
||
|
let sk_file = "../data/admin.sk".parse().unwrap();
|
||
|
let url = "https://grpc-falcron.pion-1.ntrn.tech:80".parse().unwrap();
|
||
|
let contract = "neutron15ruzx9wvrupt9cffzsp6868uad2svhfym2nsgxm2skpeqr3qrd4q4uwk83"
|
||
|
.parse()
|
||
|
.unwrap();
|
||
|
|
||
|
let cw_client = GrpcClient::new(sk_file, url);
|
||
|
let resp: Vec<Request> = cw_client
|
||
|
.query_smart(&contract, json!(GetRequests {}))
|
||
|
.await?;
|
||
|
println!("{resp:?}");
|
||
|
|
||
|
Ok(())
|
||
|
}
|
||
|
|
||
|
#[tokio::test]
|
||
|
#[ignore]
|
||
|
async fn test_execute() -> Result<(), Box<dyn Error>> {
|
||
|
let sk_file = "data/admin.sk".parse().unwrap();
|
||
|
let url = "https://grpc-falcron.pion-1.ntrn.tech:80".parse().unwrap();
|
||
|
let contract = "neutron15ruzx9wvrupt9cffzsp6868uad2svhfym2nsgxm2skpeqr3qrd4q4uwk83"
|
||
|
.parse()
|
||
|
.unwrap();
|
||
|
let chain_id = "pion-1".parse().unwrap();
|
||
|
|
||
|
let cw_client = GrpcClient::new(sk_file, url);
|
||
|
let tx_hash = cw_client
|
||
|
.tx_execute(
|
||
|
&contract,
|
||
|
&chain_id,
|
||
|
2000000,
|
||
|
"/* unused since we're getting the account from the sk */",
|
||
|
json!([]),
|
||
|
"11000untrn",
|
||
|
)
|
||
|
.await?;
|
||
|
println!("{}", tx_hash);
|
||
|
|
||
|
Ok(())
|
||
|
}
|
||
|
}
|