267 lines
7.6 KiB
Rust
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(())
|
|
}
|
|
}
|