cycles-quartz/crates/utils/cw-client/src/grpc.rs
2025-02-25 15:09:28 +01:00

267 lines
7.6 KiB
Rust

use std::error::Error;
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;
pub struct GrpcClient {
sk: SigningKey,
url: Url,
}
impl GrpcClient {
pub fn new(sk: SigningKey, url: Url) -> Self {
Self { sk, 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,
) -> Result<String, Self::Error> {
let tm_pubkey = self.sk.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(
&self.sk,
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::{grpc::GrpcClient, CwClient};
#[tokio::test]
#[ignore]
async fn test_query() -> Result<(), Box<dyn Error>> {
let sk = hex::decode("ffc4d3c9119e9e8263de08c0f6e2368ac5c2dacecfeb393f6813da7d178873d2")
.unwrap()
.as_slice()
.try_into()
.unwrap();
let url = "https://grpc-falcron.pion-1.ntrn.tech:80".parse().unwrap();
let contract = "neutron15ruzx9wvrupt9cffzsp6868uad2svhfym2nsgxm2skpeqr3qrd4q4uwk83"
.parse()
.unwrap();
let cw_client = GrpcClient::new(sk, 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 = hex::decode("ffc4d3c9119e9e8263de08c0f6e2368ac5c2dacecfeb393f6813da7d178873d2")
.unwrap()
.as_slice()
.try_into()
.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, url);
let tx_hash = cw_client
.tx_execute(
&contract,
&chain_id,
2000000,
"/* unused since we're getting the account from the sk */",
json!([]),
)
.await?;
println!("{}", tx_hash);
Ok(())
}
}