Rust nymd/cosmwasm client (#724)
* Using forked cosmwasm * Basic wallet functionalities * WIP * Generic abci_query method * More API stubs with some semi-working code * More API stub work * Moving from fork of cosmos-rust to the upstream * Implemented most sign-less cosmwasm client methods * Full contract upload with log parsing * Implemented most of remaining methods on signing client * Some initial cleanup on existing code * Feature-locking nymd client * Better type for base account * Pagination handling * Searching transaction by concrete hash * basic search_tx * More cleanup * Disabled default validator-client features on wasm client * Fixed account conversion * Fixed typo in cargo.toml * Moving back to main cosmos-sdk repo * Re-exported connect functions * comment * Wallet no longer storing signing keys * Went back to the trait approach * Example stub of future API * Removed needless borrow * Fixed starting page * Fixed typo * Using centralised config defaults
This commit is contained in:
committed by
GitHub
parent
d643df74b3
commit
ebeac73f30
Generated
+763
-96
File diff suppressed because it is too large
Load Diff
@@ -28,7 +28,7 @@ crypto = { path = "../../common/crypto" }
|
||||
nymsphinx = { path = "../../common/nymsphinx" }
|
||||
topology = { path = "../../common/topology" }
|
||||
gateway-client = { path = "../../common/client-libs/gateway-client" }
|
||||
validator-client = { path = "../../common/client-libs/validator-client" }
|
||||
validator-client = { path = "../../common/client-libs/validator-client", default-features = false }
|
||||
wasm-utils = { path = "../../common/wasm-utils" }
|
||||
|
||||
# The `console_error_panic_hook` crate provides better debugging of panics by
|
||||
|
||||
@@ -18,7 +18,26 @@ log = "0.4"
|
||||
url = "2"
|
||||
wasm-timer = "0.2"
|
||||
|
||||
# required for nymd-client
|
||||
# at some point it might be possible to make it wasm-compatible
|
||||
# perhaps after https://github.com/cosmos/cosmos-rust/pull/97 is resolved (and tendermint-rs is updated)
|
||||
async-trait = { version = "0.1.51", optional = true }
|
||||
bip39 = { version = "1", features = ["rand"], optional = true }
|
||||
config = { path = "../../config", optional = true}
|
||||
cosmos_sdk = { git = "https://github.com/cosmos/cosmos-rust/", commit="ba012bd820240d3df2d9a0ab1deabe4ecd9a2f30", features = ["rpc", "bip32", "cosmwasm"], optional = true }
|
||||
prost = { version = "0.7", default-features = false, optional = true }
|
||||
flate2 = { version = "1.0.20", optional = true }
|
||||
sha2 = { version = "0.9.5", optional = true }
|
||||
itertools = { version = "0.10", optional = true }
|
||||
cosmwasm-std = { git = "https://github.com/jstuczyn/cosmwasm", branch="0.14.1-updatedk256", optional = true }
|
||||
|
||||
[target."cfg(target_arch = \"wasm32\")".dependencies.getrandom]
|
||||
version = "0.2"
|
||||
features = ["js"]
|
||||
|
||||
[dev-dependencies]
|
||||
tokio = {version = "1.5", features = ["full"]}
|
||||
|
||||
[features]
|
||||
default = ["nymd-client"]
|
||||
nymd-client = ["async-trait", "bip39", "config", "cosmos_sdk", "prost", "flate2", "sha2", "itertools", "cosmwasm-std"]
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
#[cfg(feature = "nymd-client")]
|
||||
use crate::nymd::cosmwasm_client::types::ContractCodeId;
|
||||
use crate::validator_api;
|
||||
|
||||
#[cfg(feature = "nymd-client")]
|
||||
use cosmos_sdk::tendermint::block;
|
||||
#[cfg(feature = "nymd-client")]
|
||||
use cosmos_sdk::{bip32, rpc, tx, AccountId};
|
||||
use serde::Deserialize;
|
||||
use std::io;
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
@@ -10,18 +16,169 @@ pub enum ValidatorClientError {
|
||||
#[from]
|
||||
source: reqwest::Error,
|
||||
},
|
||||
|
||||
#[error("There was an issue with the validator-api request - {source}")]
|
||||
ValidatorAPIError {
|
||||
#[from]
|
||||
source: validator_api::error::ValidatorAPIClientError,
|
||||
},
|
||||
|
||||
#[error("An IO error has occured: {source}")]
|
||||
IoError {
|
||||
#[from]
|
||||
source: std::io::Error,
|
||||
source: io::Error,
|
||||
},
|
||||
|
||||
#[error("There was an issue with the validator client - {0}")]
|
||||
ValidatorError(String),
|
||||
|
||||
#[cfg(feature = "nymd-client")]
|
||||
#[error("There was an issue with bip32 - {0}")]
|
||||
Bip32Error(bip32::Error),
|
||||
|
||||
#[cfg(feature = "nymd-client")]
|
||||
#[error("There was an issue with bip32 - {0}")]
|
||||
Bip39Error(bip39::Error),
|
||||
|
||||
#[cfg(feature = "nymd-client")]
|
||||
#[error("Failed to derive account address")]
|
||||
AccountDerivationError,
|
||||
|
||||
#[cfg(feature = "nymd-client")]
|
||||
#[error("Address {0} was not found in the wallet")]
|
||||
SigningAccountNotFound(AccountId),
|
||||
|
||||
#[cfg(feature = "nymd-client")]
|
||||
#[error("Failed to sign raw transaction")]
|
||||
SigningFailure,
|
||||
|
||||
#[cfg(feature = "nymd-client")]
|
||||
#[error("{0} is not a valid tx hash")]
|
||||
InvalidTxHash(String),
|
||||
|
||||
#[cfg(feature = "nymd-client")]
|
||||
#[error("There was an issue with a tendermint RPC request - {0}")]
|
||||
TendermintError(rpc::Error),
|
||||
|
||||
#[cfg(feature = "nymd-client")]
|
||||
#[error("There was an issue when attempting to serialize data")]
|
||||
SerializationError(String),
|
||||
|
||||
#[cfg(feature = "nymd-client")]
|
||||
#[error("There was an issue when attempting to deserialize data")]
|
||||
DeserializationError(String),
|
||||
|
||||
#[cfg(feature = "nymd-client")]
|
||||
#[error("There was an issue when attempting to encode our protobuf data - {0}")]
|
||||
ProtobufEncodingError(prost::EncodeError),
|
||||
|
||||
#[cfg(feature = "nymd-client")]
|
||||
#[error("There was an issue when attempting to decode our protobuf data - {0}")]
|
||||
ProtobufDecodingError(prost::DecodeError),
|
||||
|
||||
#[cfg(feature = "nymd-client")]
|
||||
#[error("Account {0} does not exist on the chain")]
|
||||
NonExistentAccountError(AccountId),
|
||||
|
||||
#[cfg(feature = "nymd-client")]
|
||||
#[error("There was an issue with the serialization/deserialization - {0}")]
|
||||
SerdeJsonError(serde_json::Error),
|
||||
|
||||
#[cfg(feature = "nymd-client")]
|
||||
#[error("Account {0} is not a valid account address")]
|
||||
MalformedAccountAddress(String),
|
||||
|
||||
#[cfg(feature = "nymd-client")]
|
||||
#[error("Account {0} has an invalid associated public key")]
|
||||
InvalidPublicKey(AccountId),
|
||||
|
||||
#[cfg(feature = "nymd-client")]
|
||||
#[error("Queried contract (code_id: {0}) did not have any code information attached")]
|
||||
NoCodeInformation(ContractCodeId),
|
||||
|
||||
#[cfg(feature = "nymd-client")]
|
||||
#[error("Queried contract (address: {0}) did not have any contract information attached")]
|
||||
NoContractInformation(AccountId),
|
||||
|
||||
#[cfg(feature = "nymd-client")]
|
||||
#[error("Contract contains invalid operations in its history")]
|
||||
InvalidContractHistoryOperation,
|
||||
|
||||
#[cfg(feature = "nymd-client")]
|
||||
#[error("Block has an invalid height (either negative or larger than i64::MAX")]
|
||||
InvalidHeight,
|
||||
|
||||
#[cfg(feature = "nymd-client")]
|
||||
#[error("Failed to compress provided wasm code - {0}")]
|
||||
WasmCompressionError(io::Error),
|
||||
|
||||
#[cfg(feature = "nymd-client")]
|
||||
#[error("Logs returned from the validator were malformed")]
|
||||
MalformedLogString,
|
||||
|
||||
#[cfg(feature = "nymd-client")]
|
||||
#[error(
|
||||
"Error when broadcasting tx {hash} at height {height}. Error occurred during CheckTx phase. Code: {code}; Raw log: {raw_log}"
|
||||
)]
|
||||
BroadcastTxErrorCheckTx {
|
||||
hash: tx::Hash,
|
||||
height: block::Height,
|
||||
code: u32,
|
||||
raw_log: String,
|
||||
},
|
||||
|
||||
#[cfg(feature = "nymd-client")]
|
||||
#[error(
|
||||
"Error when broadcasting tx {hash} at height {height}. Error occurred during DeliverTx phase. Code: {code}; Raw log: {raw_log}"
|
||||
)]
|
||||
BroadcastTxErrorDeliverTx {
|
||||
hash: tx::Hash,
|
||||
height: block::Height,
|
||||
code: u32,
|
||||
raw_log: String,
|
||||
},
|
||||
}
|
||||
|
||||
#[cfg(feature = "nymd-client")]
|
||||
impl From<bip32::Error> for ValidatorClientError {
|
||||
fn from(err: bip32::Error) -> Self {
|
||||
ValidatorClientError::Bip32Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "nymd-client")]
|
||||
impl From<bip39::Error> for ValidatorClientError {
|
||||
fn from(err: bip39::Error) -> Self {
|
||||
ValidatorClientError::Bip39Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "nymd-client")]
|
||||
impl From<rpc::Error> for ValidatorClientError {
|
||||
fn from(err: rpc::Error) -> Self {
|
||||
ValidatorClientError::TendermintError(err)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "nymd-client")]
|
||||
impl From<prost::EncodeError> for ValidatorClientError {
|
||||
fn from(err: prost::EncodeError) -> Self {
|
||||
ValidatorClientError::ProtobufEncodingError(err)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "nymd-client")]
|
||||
impl From<prost::DecodeError> for ValidatorClientError {
|
||||
fn from(err: prost::DecodeError) -> Self {
|
||||
ValidatorClientError::ProtobufDecodingError(err)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "nymd-client")]
|
||||
impl From<serde_json::Error> for ValidatorClientError {
|
||||
fn from(err: serde_json::Error) -> Self {
|
||||
ValidatorClientError::SerdeJsonError(err)
|
||||
}
|
||||
}
|
||||
|
||||
// this is the case of message like
|
||||
|
||||
@@ -16,6 +16,8 @@ use url::Url;
|
||||
|
||||
mod error;
|
||||
mod models;
|
||||
#[cfg(feature = "nymd-client")]
|
||||
pub mod nymd;
|
||||
pub(crate) mod serde_helpers;
|
||||
pub mod validator_api;
|
||||
|
||||
|
||||
@@ -0,0 +1,420 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::nymd::cosmwasm_client::helpers::create_pagination;
|
||||
use crate::nymd::cosmwasm_client::types::{
|
||||
Account, Code, CodeDetails, Contract, ContractCodeHistoryEntry, ContractCodeId,
|
||||
SequenceResponse,
|
||||
};
|
||||
use crate::ValidatorClientError;
|
||||
use async_trait::async_trait;
|
||||
use cosmos_sdk::proto::cosmos::auth::v1beta1::{
|
||||
BaseAccount, QueryAccountRequest, QueryAccountResponse,
|
||||
};
|
||||
use cosmos_sdk::proto::cosmos::bank::v1beta1::{
|
||||
QueryAllBalancesRequest, QueryAllBalancesResponse, QueryBalanceRequest, QueryBalanceResponse,
|
||||
};
|
||||
use cosmos_sdk::proto::cosmwasm::wasm::v1beta1::*;
|
||||
use cosmos_sdk::rpc::endpoint::block::Response as BlockResponse;
|
||||
use cosmos_sdk::rpc::endpoint::broadcast;
|
||||
use cosmos_sdk::rpc::endpoint::tx::Response as TxResponse;
|
||||
use cosmos_sdk::rpc::query::Query;
|
||||
use cosmos_sdk::rpc::{self, HttpClient, Order};
|
||||
use cosmos_sdk::tendermint::abci::Transaction;
|
||||
use cosmos_sdk::tendermint::{abci, block, chain};
|
||||
use cosmos_sdk::{tx, AccountId, Coin, Denom};
|
||||
use prost::Message;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::convert::{TryFrom, TryInto};
|
||||
|
||||
#[async_trait]
|
||||
impl CosmWasmClient for HttpClient {}
|
||||
|
||||
#[async_trait]
|
||||
pub trait CosmWasmClient: rpc::Client {
|
||||
// helper method to remove duplicate code involved in making abci requests with protobuf messages
|
||||
// TODO: perhaps it should have an additional argument to determine whether the response should
|
||||
// require proof?
|
||||
async fn make_abci_query<Req, Res>(
|
||||
&self,
|
||||
path: Option<abci::Path>,
|
||||
req: Req,
|
||||
) -> Result<Res, ValidatorClientError>
|
||||
where
|
||||
Req: Message,
|
||||
Res: Message + Default,
|
||||
{
|
||||
let mut buf = Vec::with_capacity(req.encoded_len());
|
||||
req.encode(&mut buf)?;
|
||||
|
||||
let res = self.abci_query(path, buf, None, false).await?;
|
||||
|
||||
Ok(Res::decode(res.value.as_ref())?)
|
||||
}
|
||||
|
||||
async fn get_chain_id(&self) -> Result<chain::Id, ValidatorClientError> {
|
||||
Ok(self.status().await?.node_info.network)
|
||||
}
|
||||
|
||||
async fn get_height(&self) -> Result<block::Height, ValidatorClientError> {
|
||||
Ok(self.status().await?.sync_info.latest_block_height)
|
||||
}
|
||||
|
||||
// TODO: the return type should probably be changed to a non-proto, type-safe Account alternative
|
||||
async fn get_account(
|
||||
&self,
|
||||
address: &AccountId,
|
||||
) -> Result<Option<Account>, ValidatorClientError> {
|
||||
let path = Some("/cosmos.auth.v1beta1.Query/Account".parse().unwrap());
|
||||
|
||||
let req = QueryAccountRequest {
|
||||
address: address.to_string(),
|
||||
};
|
||||
|
||||
let res = self
|
||||
.make_abci_query::<_, QueryAccountResponse>(path, req)
|
||||
.await?;
|
||||
|
||||
let base_account = res
|
||||
.account
|
||||
.map(|account| BaseAccount::decode(account.value.as_ref()))
|
||||
.transpose()?;
|
||||
|
||||
base_account
|
||||
.map(|base_account| base_account.try_into())
|
||||
.transpose()
|
||||
}
|
||||
|
||||
async fn get_sequence(
|
||||
&self,
|
||||
address: &AccountId,
|
||||
) -> Result<SequenceResponse, ValidatorClientError> {
|
||||
let base_account = self
|
||||
.get_account(address)
|
||||
.await?
|
||||
.ok_or_else(|| ValidatorClientError::NonExistentAccountError(address.clone()))?;
|
||||
Ok(SequenceResponse {
|
||||
account_number: base_account.account_number,
|
||||
sequence: base_account.sequence,
|
||||
})
|
||||
}
|
||||
|
||||
async fn get_block(&self, height: Option<u32>) -> Result<BlockResponse, ValidatorClientError> {
|
||||
match height {
|
||||
Some(height) => self.block(height).await.map_err(|err| err.into()),
|
||||
None => self.latest_block().await.map_err(|err| err.into()),
|
||||
}
|
||||
}
|
||||
|
||||
async fn get_balance(
|
||||
&self,
|
||||
address: &AccountId,
|
||||
search_denom: Denom,
|
||||
) -> Result<Option<Coin>, ValidatorClientError> {
|
||||
let path = Some("/cosmos.bank.v1beta1.Query/Balance".parse().unwrap());
|
||||
|
||||
let req = QueryBalanceRequest {
|
||||
address: address.to_string(),
|
||||
denom: search_denom.to_string(),
|
||||
};
|
||||
|
||||
let res = self
|
||||
.make_abci_query::<_, QueryBalanceResponse>(path, req)
|
||||
.await?;
|
||||
|
||||
res.balance
|
||||
.map(TryFrom::try_from)
|
||||
.transpose()
|
||||
.map_err(|_| ValidatorClientError::SerializationError("Coin".to_owned()))
|
||||
}
|
||||
|
||||
async fn get_all_balances(
|
||||
&self,
|
||||
address: &AccountId,
|
||||
) -> Result<Vec<Coin>, ValidatorClientError> {
|
||||
let path = Some("/cosmos.bank.v1beta1.Query/AllBalances".parse().unwrap());
|
||||
|
||||
let mut raw_balances = Vec::new();
|
||||
let mut pagination = None;
|
||||
|
||||
loop {
|
||||
let req = QueryAllBalancesRequest {
|
||||
address: address.to_string(),
|
||||
pagination,
|
||||
};
|
||||
|
||||
let mut res = self
|
||||
.make_abci_query::<_, QueryAllBalancesResponse>(path.clone(), req)
|
||||
.await?;
|
||||
|
||||
raw_balances.append(&mut res.balances);
|
||||
if let Some(pagination_info) = res.pagination {
|
||||
pagination = Some(create_pagination(pagination_info.next_key))
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
raw_balances
|
||||
.into_iter()
|
||||
.map(TryFrom::try_from)
|
||||
.collect::<Result<_, _>>()
|
||||
.map_err(|_| ValidatorClientError::SerializationError("Coins".to_owned()))
|
||||
}
|
||||
|
||||
async fn get_tx(&self, id: tx::Hash) -> Result<TxResponse, ValidatorClientError> {
|
||||
Ok(self.tx(id, false).await?)
|
||||
}
|
||||
|
||||
async fn search_tx(&self, query: Query) -> Result<Vec<TxResponse>, ValidatorClientError> {
|
||||
// according to https://docs.tendermint.com/master/rpc/#/Info/tx_search
|
||||
// the maximum entries per page is 100 and the default is 30
|
||||
// so let's attempt to use the maximum
|
||||
let per_page = 100;
|
||||
|
||||
let mut results = Vec::new();
|
||||
let mut page = 1;
|
||||
|
||||
loop {
|
||||
let mut res = self
|
||||
.tx_search(query.clone(), false, page, 100, Order::Ascending)
|
||||
.await?;
|
||||
|
||||
results.append(&mut res.txs);
|
||||
// sanity check for if tendermint's maximum per_page was modified -
|
||||
// we don't want to accidentally be stuck in an infinite loop
|
||||
if res.total_count == 0 || res.txs.is_empty() {
|
||||
break;
|
||||
}
|
||||
|
||||
if res.total_count >= per_page {
|
||||
page += 1
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(results)
|
||||
}
|
||||
|
||||
/// Broadcast a transaction, returning immediately.
|
||||
async fn broadcast_tx_async(
|
||||
&self,
|
||||
tx: Transaction,
|
||||
) -> Result<broadcast::tx_async::Response, ValidatorClientError> {
|
||||
Ok(rpc::Client::broadcast_tx_async(self, tx).await?)
|
||||
}
|
||||
|
||||
/// Broadcast a transaction, returning the response from `CheckTx`.
|
||||
async fn broadcast_tx_sync(
|
||||
&self,
|
||||
tx: Transaction,
|
||||
) -> Result<broadcast::tx_sync::Response, ValidatorClientError> {
|
||||
Ok(rpc::Client::broadcast_tx_sync(self, tx).await?)
|
||||
}
|
||||
|
||||
/// Broadcast a transaction, returning the response from `DeliverTx`.
|
||||
async fn broadcast_tx_commit(
|
||||
&self,
|
||||
tx: Transaction,
|
||||
) -> Result<broadcast::tx_commit::Response, ValidatorClientError> {
|
||||
Ok(rpc::Client::broadcast_tx_commit(self, tx).await?)
|
||||
}
|
||||
|
||||
async fn get_codes(&self) -> Result<Vec<Code>, ValidatorClientError> {
|
||||
let path = Some("/cosmwasm.wasm.v1beta1.Query/Codes".parse().unwrap());
|
||||
|
||||
let mut raw_codes = Vec::new();
|
||||
let mut pagination = None;
|
||||
|
||||
loop {
|
||||
let req = QueryCodesRequest { pagination };
|
||||
|
||||
let mut res = self
|
||||
.make_abci_query::<_, QueryCodesResponse>(path.clone(), req)
|
||||
.await?;
|
||||
|
||||
raw_codes.append(&mut res.code_infos);
|
||||
if let Some(pagination_info) = res.pagination {
|
||||
pagination = Some(create_pagination(pagination_info.next_key))
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
raw_codes
|
||||
.into_iter()
|
||||
.map(TryFrom::try_from)
|
||||
.collect::<Result<_, _>>()
|
||||
}
|
||||
|
||||
async fn get_code_details(
|
||||
&self,
|
||||
code_id: ContractCodeId,
|
||||
) -> Result<CodeDetails, ValidatorClientError> {
|
||||
let path = Some("/cosmwasm.wasm.v1beta1.Query/Code".parse().unwrap());
|
||||
|
||||
let req = QueryCodeRequest { code_id };
|
||||
|
||||
let res = self
|
||||
.make_abci_query::<_, QueryCodeResponse>(path, req)
|
||||
.await?;
|
||||
|
||||
if let Some(code_info) = res.code_info {
|
||||
Ok(CodeDetails::new(code_info.try_into()?, res.data))
|
||||
} else {
|
||||
Err(ValidatorClientError::NoCodeInformation(code_id))
|
||||
}
|
||||
}
|
||||
async fn get_contracts(
|
||||
&self,
|
||||
code_id: ContractCodeId,
|
||||
) -> Result<Vec<AccountId>, ValidatorClientError> {
|
||||
let path = Some(
|
||||
"/cosmwasm.wasm.v1beta1.Query/ContractsByCode"
|
||||
.parse()
|
||||
.unwrap(),
|
||||
);
|
||||
|
||||
let mut raw_contracts = Vec::new();
|
||||
let mut pagination = None;
|
||||
|
||||
loop {
|
||||
let req = QueryContractsByCodeRequest {
|
||||
code_id,
|
||||
pagination,
|
||||
};
|
||||
|
||||
let mut res = self
|
||||
.make_abci_query::<_, QueryContractsByCodeResponse>(path.clone(), req)
|
||||
.await?;
|
||||
|
||||
raw_contracts.append(&mut res.contracts);
|
||||
if let Some(pagination_info) = res.pagination {
|
||||
pagination = Some(create_pagination(pagination_info.next_key))
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
raw_contracts
|
||||
.iter()
|
||||
.map(|raw| raw.parse())
|
||||
.collect::<Result<_, _>>()
|
||||
.map_err(|_| {
|
||||
ValidatorClientError::DeserializationError("Contract addresses".to_owned())
|
||||
})
|
||||
}
|
||||
|
||||
async fn get_contract(&self, address: &AccountId) -> Result<Contract, ValidatorClientError> {
|
||||
let path = Some("/cosmwasm.wasm.v1beta1.Query/ContractInfo".parse().unwrap());
|
||||
|
||||
let req = QueryContractInfoRequest {
|
||||
address: address.to_string(),
|
||||
};
|
||||
|
||||
let res = self
|
||||
.make_abci_query::<_, QueryContractInfoResponse>(path, req)
|
||||
.await?;
|
||||
|
||||
let response_address = res.address;
|
||||
if let Some(contract_info) = res.contract_info {
|
||||
let address = response_address
|
||||
.parse()
|
||||
.map_err(|_| ValidatorClientError::MalformedAccountAddress(response_address))?;
|
||||
Ok(Contract::new(address, contract_info.try_into()?))
|
||||
} else {
|
||||
Err(ValidatorClientError::NoContractInformation(address.clone()))
|
||||
}
|
||||
}
|
||||
|
||||
async fn get_contract_code_history(
|
||||
&self,
|
||||
address: &AccountId,
|
||||
) -> Result<Vec<ContractCodeHistoryEntry>, ValidatorClientError> {
|
||||
let path = Some(
|
||||
"/cosmwasm.wasm.v1beta1.Query/ContractHistory"
|
||||
.parse()
|
||||
.unwrap(),
|
||||
);
|
||||
|
||||
let mut raw_entries = Vec::new();
|
||||
let mut pagination = None;
|
||||
|
||||
loop {
|
||||
let req = QueryContractHistoryRequest {
|
||||
address: address.to_string(),
|
||||
pagination,
|
||||
};
|
||||
|
||||
let mut res = self
|
||||
.make_abci_query::<_, QueryContractHistoryResponse>(path.clone(), req)
|
||||
.await?;
|
||||
|
||||
raw_entries.append(&mut res.entries);
|
||||
if let Some(pagination_info) = res.pagination {
|
||||
pagination = Some(create_pagination(pagination_info.next_key))
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
raw_entries
|
||||
.into_iter()
|
||||
.map(TryFrom::try_from)
|
||||
.collect::<Result<_, _>>()
|
||||
}
|
||||
|
||||
async fn query_contract_raw(
|
||||
&self,
|
||||
address: &AccountId,
|
||||
query_data: Vec<u8>,
|
||||
) -> Result<Vec<u8>, ValidatorClientError> {
|
||||
let path = Some(
|
||||
"/cosmwasm.wasm.v1beta1.Query/RawContractState"
|
||||
.parse()
|
||||
.unwrap(),
|
||||
);
|
||||
|
||||
let req = QueryRawContractStateRequest {
|
||||
address: address.to_string(),
|
||||
query_data,
|
||||
};
|
||||
|
||||
let res = self
|
||||
.make_abci_query::<_, QueryRawContractStateResponse>(path, req)
|
||||
.await?;
|
||||
|
||||
Ok(res.data)
|
||||
}
|
||||
|
||||
async fn query_contract_smart<M, T>(
|
||||
&self,
|
||||
address: &AccountId,
|
||||
query_msg: &M,
|
||||
) -> Result<T, ValidatorClientError>
|
||||
where
|
||||
M: ?Sized + Serialize + Sync,
|
||||
for<'a> T: Deserialize<'a>,
|
||||
{
|
||||
let path = Some(
|
||||
"/cosmwasm.wasm.v1beta1.Query/SmartContractState"
|
||||
.parse()
|
||||
.unwrap(),
|
||||
);
|
||||
|
||||
// As per serde documentation:
|
||||
// Serialization can fail if `T`'s implementation of `Serialize` decides to
|
||||
// fail, or if `T` contains a map with non-string keys.
|
||||
let req = QuerySmartContractStateRequest {
|
||||
address: address.to_string(),
|
||||
query_data: serde_json::to_vec(query_msg)?,
|
||||
};
|
||||
|
||||
let res = self
|
||||
.make_abci_query::<_, QuerySmartContractStateResponse>(path, req)
|
||||
.await?;
|
||||
|
||||
Ok(serde_json::from_slice(&res.data)?)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::ValidatorClientError;
|
||||
use cosmos_sdk::proto::cosmos::base::query::v1beta1::PageRequest;
|
||||
use cosmos_sdk::rpc::endpoint::broadcast;
|
||||
use flate2::write::GzEncoder;
|
||||
use flate2::Compression;
|
||||
use std::io::Write;
|
||||
|
||||
pub(crate) trait CheckResponse: Sized {
|
||||
fn check_response(self) -> Result<Self, ValidatorClientError>;
|
||||
}
|
||||
|
||||
impl CheckResponse for broadcast::tx_commit::Response {
|
||||
fn check_response(self) -> Result<Self, ValidatorClientError> {
|
||||
if self.check_tx.code.is_err() {
|
||||
return Err(ValidatorClientError::BroadcastTxErrorCheckTx {
|
||||
hash: self.hash,
|
||||
height: self.height,
|
||||
code: self.check_tx.code.value(),
|
||||
raw_log: self.check_tx.log.value().to_owned(),
|
||||
});
|
||||
}
|
||||
|
||||
if self.deliver_tx.code.is_err() {
|
||||
return Err(ValidatorClientError::BroadcastTxErrorDeliverTx {
|
||||
hash: self.hash,
|
||||
height: self.height,
|
||||
code: self.deliver_tx.code.value(),
|
||||
raw_log: self.deliver_tx.log.value().to_owned(),
|
||||
});
|
||||
}
|
||||
|
||||
Ok(self)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn compress_wasm_code(code: &[u8]) -> Result<Vec<u8>, ValidatorClientError> {
|
||||
// using compression level 9, same as cosmjs, that optimises for size
|
||||
let mut encoder = GzEncoder::new(Vec::new(), Compression::best());
|
||||
encoder
|
||||
.write_all(code)
|
||||
.map_err(ValidatorClientError::WasmCompressionError)?;
|
||||
encoder
|
||||
.finish()
|
||||
.map_err(ValidatorClientError::WasmCompressionError)
|
||||
}
|
||||
|
||||
pub(crate) fn create_pagination(key: Vec<u8>) -> PageRequest {
|
||||
PageRequest {
|
||||
key,
|
||||
offset: 0,
|
||||
limit: 0,
|
||||
count_total: false,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::ValidatorClientError;
|
||||
use cosmos_sdk::tendermint::abci;
|
||||
use itertools::Itertools;
|
||||
use serde::Deserialize;
|
||||
|
||||
// it seems that currently validators just emit stringified events (which are also returned as part of deliverTx response)
|
||||
// as theirs logs
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct Log {
|
||||
#[serde(default)]
|
||||
// weird thing is that the first msg_index seems to always be undefined on the raw logs
|
||||
msg_index: usize,
|
||||
// unless I'm missing something obvious, the "log" type in cosmjs is always an empty string
|
||||
// and launchpad cosmos validator was setting it to what essentially is just the raw version of what
|
||||
// we received (and we don't care about launchpad, we, as the time of writing this, work on the stargate)
|
||||
// log: String,
|
||||
events: Vec<cosmwasm_std::Event>,
|
||||
}
|
||||
|
||||
/// Searches in logs for the first event of the given event type and in that event
|
||||
/// for the first attribute with the given attribute key.
|
||||
pub(crate) fn find_attribute<'a>(
|
||||
logs: &'a [Log],
|
||||
event_type: &str,
|
||||
attribute_key: &str,
|
||||
) -> Option<&'a cosmwasm_std::Attribute> {
|
||||
logs.iter()
|
||||
.flat_map(|log| log.events.iter())
|
||||
.find(|event| event.kind == event_type)?
|
||||
.attributes
|
||||
.iter()
|
||||
.find(|attr| attr.key == attribute_key)
|
||||
}
|
||||
|
||||
// those two functions were separated so that the internal logic could actually be tested
|
||||
fn parse_raw_str_logs(raw: &str) -> Result<Vec<Log>, ValidatorClientError> {
|
||||
let logs: Vec<Log> =
|
||||
serde_json::from_str(raw).map_err(|_| ValidatorClientError::MalformedLogString)?;
|
||||
if logs.len() != logs.iter().unique_by(|log| log.msg_index).count() {
|
||||
// this check is only here because I don't yet fully understand raw log string generation and
|
||||
// the fact the first entry does not seem to have `msg_index` defined on it.
|
||||
return Err(ValidatorClientError::MalformedLogString);
|
||||
}
|
||||
Ok(logs)
|
||||
}
|
||||
|
||||
pub fn parse_raw_logs(raw: abci::Log) -> Result<Vec<Log>, ValidatorClientError> {
|
||||
parse_raw_str_logs(raw.as_ref())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn logs_parsing_with_single_tx() {
|
||||
let raw = r#"[{"events":[{"type":"message","attributes":[{"key":"action","value":"store-code"},{"key":"module","value":"wasm"},{"key":"signer","value":"punk1m4aj8tgc0rqlms3s0c8jf3pcrma5xw2waafzjt"},{"key":"code_id","value":"1"}]}]}]"#;
|
||||
let parsed = parse_raw_str_logs(raw).unwrap();
|
||||
assert_eq!(parsed.len(), 1);
|
||||
assert_eq!(parsed[0].msg_index, 0);
|
||||
assert_eq!(parsed[0].events.len(), 1);
|
||||
assert_eq!(parsed[0].events[0].kind, "message");
|
||||
assert_eq!(parsed[0].events[0].attributes[3].key, "code_id");
|
||||
assert_eq!(parsed[0].events[0].attributes[3].value, "1");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn logs_parsing_with_multiple_txs() {
|
||||
let raw = r#"[{"events":[{"type":"message","attributes":[{"key":"action","value":"store-code"},{"key":"module","value":"wasm"},{"key":"signer","value":"punk1q9n5a3cgw3azegcddr82s0f5nxeel4pup8vxzt"},{"key":"code_id","value":"9"}]}]},{"msg_index":1,"events":[{"type":"message","attributes":[{"key":"action","value":"store-code"},{"key":"module","value":"wasm"},{"key":"signer","value":"punk1q9n5a3cgw3azegcddr82s0f5nxeel4pup8vxzt"},{"key":"code_id","value":"10"}]}]},{"msg_index":2,"events":[{"type":"message","attributes":[{"key":"action","value":"store-code"},{"key":"module","value":"wasm"},{"key":"signer","value":"punk1q9n5a3cgw3azegcddr82s0f5nxeel4pup8vxzt"},{"key":"code_id","value":"11"}]}]}]"#;
|
||||
let parsed = parse_raw_str_logs(raw).unwrap();
|
||||
assert_eq!(parsed.len(), 3);
|
||||
assert_eq!(parsed[0].msg_index, 0);
|
||||
assert_eq!(parsed[1].msg_index, 1);
|
||||
assert_eq!(parsed[2].msg_index, 2);
|
||||
|
||||
assert_eq!(parsed[0].events.len(), 1);
|
||||
assert_eq!(parsed[0].events[0].kind, "message");
|
||||
assert_eq!(parsed[0].events[0].attributes[3].key, "code_id");
|
||||
assert_eq!(parsed[0].events[0].attributes[3].value, "9");
|
||||
|
||||
assert_eq!(parsed[2].events.len(), 1);
|
||||
assert_eq!(parsed[2].events[0].kind, "message");
|
||||
assert_eq!(parsed[2].events[0].attributes[2].key, "signer");
|
||||
assert_eq!(
|
||||
parsed[2].events[0].attributes[2].value,
|
||||
"punk1q9n5a3cgw3azegcddr82s0f5nxeel4pup8vxzt"
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::nymd::wallet::DirectSecp256k1HdWallet;
|
||||
use crate::ValidatorClientError;
|
||||
use cosmos_sdk::rpc::{Error as TendermintRpcError, HttpClient, HttpClientUrl};
|
||||
use std::convert::TryInto;
|
||||
|
||||
pub mod client;
|
||||
mod helpers;
|
||||
pub mod logs;
|
||||
pub mod signing_client;
|
||||
pub mod types;
|
||||
|
||||
pub fn connect<U>(endpoint: U) -> Result<HttpClient, ValidatorClientError>
|
||||
where
|
||||
U: TryInto<HttpClientUrl, Error = TendermintRpcError>,
|
||||
{
|
||||
Ok(HttpClient::new(endpoint)?)
|
||||
}
|
||||
|
||||
// maybe the wallet could be made into a generic, but for now, let's just have this one implementation
|
||||
pub fn connect_with_signer<U>(
|
||||
endpoint: U,
|
||||
signer: DirectSecp256k1HdWallet,
|
||||
) -> Result<signing_client::Client, ValidatorClientError>
|
||||
where
|
||||
U: TryInto<HttpClientUrl, Error = TendermintRpcError>,
|
||||
{
|
||||
signing_client::Client::connect_with_signer(endpoint, signer)
|
||||
}
|
||||
@@ -0,0 +1,485 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::nymd::cosmwasm_client::client::CosmWasmClient;
|
||||
use crate::nymd::cosmwasm_client::helpers::{compress_wasm_code, CheckResponse};
|
||||
use crate::nymd::cosmwasm_client::logs::{self, parse_raw_logs};
|
||||
use crate::nymd::cosmwasm_client::types::*;
|
||||
use crate::nymd::wallet::DirectSecp256k1HdWallet;
|
||||
use crate::ValidatorClientError;
|
||||
use async_trait::async_trait;
|
||||
use cosmos_sdk::bank::MsgSend;
|
||||
use cosmos_sdk::distribution::MsgWithdrawDelegatorReward;
|
||||
use cosmos_sdk::rpc::endpoint::broadcast;
|
||||
use cosmos_sdk::rpc::{Error as TendermintRpcError, HttpClient, HttpClientUrl, SimpleRequest};
|
||||
use cosmos_sdk::staking::{MsgDelegate, MsgUndelegate};
|
||||
use cosmos_sdk::tx::{Fee, Msg, MsgType, SignDoc, SignerInfo};
|
||||
use cosmos_sdk::{cosmwasm, rpc, tx, AccountId, Coin};
|
||||
use serde::Serialize;
|
||||
use sha2::Digest;
|
||||
use sha2::Sha256;
|
||||
use std::convert::TryInto;
|
||||
|
||||
#[async_trait]
|
||||
pub trait SigningCosmWasmClient: CosmWasmClient {
|
||||
fn signer(&self) -> &DirectSecp256k1HdWallet;
|
||||
|
||||
async fn upload(
|
||||
&self,
|
||||
sender_address: &AccountId,
|
||||
wasm_code: Vec<u8>,
|
||||
fee: Fee,
|
||||
memo: impl Into<String> + Send + 'static,
|
||||
mut meta: Option<UploadMeta>,
|
||||
) -> Result<UploadResult, ValidatorClientError> {
|
||||
let compressed = compress_wasm_code(&wasm_code)?;
|
||||
let compressed_size = compressed.len();
|
||||
let compressed_checksum = Sha256::digest(&compressed).to_vec();
|
||||
|
||||
// TODO: what about instantiate_permission?
|
||||
// cosmjs is just ignoring that field...
|
||||
let upload_msg = cosmwasm::MsgStoreCode {
|
||||
sender: sender_address.clone(),
|
||||
wasm_byte_code: compressed,
|
||||
source: meta
|
||||
.as_mut()
|
||||
.map(|meta| meta.source.take())
|
||||
.unwrap_or_default(),
|
||||
builder: meta
|
||||
.as_mut()
|
||||
.map(|meta| meta.builder.take())
|
||||
.unwrap_or_default(),
|
||||
instantiate_permission: Default::default(),
|
||||
}
|
||||
.to_msg()
|
||||
.map_err(|_| ValidatorClientError::SerializationError("MsgStoreCode".to_owned()))?;
|
||||
|
||||
let tx_res = self
|
||||
.sign_and_broadcast_commit(sender_address, vec![upload_msg], fee, memo)
|
||||
.await?
|
||||
.check_response()?;
|
||||
|
||||
let logs = parse_raw_logs(tx_res.deliver_tx.log)?;
|
||||
|
||||
// TODO: should those strings be extracted into some constants?
|
||||
// the reason I think unwrap here is fine is that if the transaction succeeded and those
|
||||
// fields do not exist or code_id is not a number, there's no way we can recover, we're probably connected
|
||||
// to wrong validator or something
|
||||
let code_id = logs::find_attribute(&logs, "message", "code_id")
|
||||
.unwrap()
|
||||
.value
|
||||
.parse()
|
||||
.unwrap();
|
||||
|
||||
Ok(UploadResult {
|
||||
original_size: wasm_code.len(),
|
||||
original_checksum: Sha256::digest(&wasm_code).to_vec(),
|
||||
compressed_size,
|
||||
compressed_checksum,
|
||||
code_id,
|
||||
logs,
|
||||
transaction_hash: tx_res.hash,
|
||||
})
|
||||
}
|
||||
|
||||
// honestly, I don't see a nice way of removing any arguments
|
||||
// perhaps memo could be moved to options like what cosmjs is doing
|
||||
// put personally I'd prefer to leave it there for consistency with
|
||||
// signatures of other methods
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
async fn instantiate<M>(
|
||||
&self,
|
||||
sender_address: &AccountId,
|
||||
code_id: ContractCodeId,
|
||||
msg: &M,
|
||||
label: String,
|
||||
fee: Fee,
|
||||
memo: impl Into<String> + Send + 'static,
|
||||
mut options: Option<InstantiateOptions>,
|
||||
) -> Result<InstantiateResult, ValidatorClientError>
|
||||
where
|
||||
M: ?Sized + Serialize + Sync,
|
||||
{
|
||||
let init_msg = cosmwasm::MsgInstantiateContract {
|
||||
sender: sender_address.clone(),
|
||||
admin: options
|
||||
.as_mut()
|
||||
.map(|options| options.admin.take())
|
||||
.flatten(),
|
||||
code_id,
|
||||
// now this is a weird one. the protobuf files say this field is optional,
|
||||
// but if you omit it, the initialisation will fail CheckTx
|
||||
label: Some(label),
|
||||
init_msg: serde_json::to_vec(msg)?,
|
||||
funds: options.map(|options| options.funds).unwrap_or_default(),
|
||||
}
|
||||
.to_msg()
|
||||
.map_err(|_| {
|
||||
ValidatorClientError::SerializationError("MsgInstantiateContract".to_owned())
|
||||
})?;
|
||||
|
||||
let tx_res = self
|
||||
.sign_and_broadcast_commit(sender_address, vec![init_msg], fee, memo)
|
||||
.await?
|
||||
.check_response()?;
|
||||
|
||||
let logs = parse_raw_logs(tx_res.deliver_tx.log)?;
|
||||
|
||||
// TODO: should those strings be extracted into some constants?
|
||||
// the reason I think unwrap here is fine is that if the transaction succeeded and those
|
||||
// fields do not exist or address is malformed, there's no way we can recover, we're probably connected
|
||||
// to wrong validator or something
|
||||
let contract_address = logs::find_attribute(&logs, "message", "contract_address")
|
||||
.unwrap()
|
||||
.value
|
||||
.parse()
|
||||
.unwrap();
|
||||
|
||||
Ok(InstantiateResult {
|
||||
contract_address,
|
||||
logs,
|
||||
transaction_hash: tx_res.hash,
|
||||
})
|
||||
}
|
||||
|
||||
async fn update_admin(
|
||||
&self,
|
||||
sender_address: &AccountId,
|
||||
contract_address: &AccountId,
|
||||
new_admin: &AccountId,
|
||||
fee: Fee,
|
||||
memo: impl Into<String> + Send + 'static,
|
||||
) -> Result<ChangeAdminResult, ValidatorClientError> {
|
||||
let change_admin_msg = cosmwasm::MsgUpdateAdmin {
|
||||
sender: sender_address.clone(),
|
||||
new_admin: new_admin.clone(),
|
||||
contract: contract_address.clone(),
|
||||
}
|
||||
.to_msg()
|
||||
.map_err(|_| ValidatorClientError::SerializationError("MsgUpdateAdmin".to_owned()))?;
|
||||
|
||||
let tx_res = self
|
||||
.sign_and_broadcast_commit(sender_address, vec![change_admin_msg], fee, memo)
|
||||
.await?
|
||||
.check_response()?;
|
||||
|
||||
Ok(ChangeAdminResult {
|
||||
logs: parse_raw_logs(tx_res.deliver_tx.log)?,
|
||||
transaction_hash: tx_res.hash,
|
||||
})
|
||||
}
|
||||
|
||||
async fn clear_admin(
|
||||
&self,
|
||||
sender_address: &AccountId,
|
||||
contract_address: &AccountId,
|
||||
fee: Fee,
|
||||
memo: impl Into<String> + Send + 'static,
|
||||
) -> Result<ChangeAdminResult, ValidatorClientError> {
|
||||
let change_admin_msg = cosmwasm::MsgClearAdmin {
|
||||
sender: sender_address.clone(),
|
||||
contract: contract_address.clone(),
|
||||
}
|
||||
.to_msg()
|
||||
.map_err(|_| ValidatorClientError::SerializationError("MsgClearAdmin".to_owned()))?;
|
||||
|
||||
let tx_res = self
|
||||
.sign_and_broadcast_commit(sender_address, vec![change_admin_msg], fee, memo)
|
||||
.await?
|
||||
.check_response()?;
|
||||
|
||||
Ok(ChangeAdminResult {
|
||||
logs: parse_raw_logs(tx_res.deliver_tx.log)?,
|
||||
transaction_hash: tx_res.hash,
|
||||
})
|
||||
}
|
||||
|
||||
async fn migrate<M>(
|
||||
&self,
|
||||
sender_address: &AccountId,
|
||||
contract_address: &AccountId,
|
||||
code_id: u64,
|
||||
fee: Fee,
|
||||
msg: &M,
|
||||
memo: impl Into<String> + Send + 'static,
|
||||
) -> Result<MigrateResult, ValidatorClientError>
|
||||
where
|
||||
M: ?Sized + Serialize + Sync,
|
||||
{
|
||||
let migrate_msg = cosmwasm::MsgMigrateContract {
|
||||
sender: sender_address.clone(),
|
||||
contract: contract_address.clone(),
|
||||
code_id,
|
||||
migrate_msg: serde_json::to_vec(msg)?,
|
||||
}
|
||||
.to_msg()
|
||||
.map_err(|_| ValidatorClientError::SerializationError("MsgMigrateContract".to_owned()))?;
|
||||
|
||||
let tx_res = self
|
||||
.sign_and_broadcast_commit(sender_address, vec![migrate_msg], fee, memo)
|
||||
.await?
|
||||
.check_response()?;
|
||||
|
||||
Ok(MigrateResult {
|
||||
logs: parse_raw_logs(tx_res.deliver_tx.log)?,
|
||||
transaction_hash: tx_res.hash,
|
||||
})
|
||||
}
|
||||
|
||||
async fn execute<M>(
|
||||
&self,
|
||||
sender_address: &AccountId,
|
||||
contract_address: &AccountId,
|
||||
msg: &M,
|
||||
fee: Fee,
|
||||
memo: impl Into<String> + Send + 'static,
|
||||
funds: Option<Vec<Coin>>,
|
||||
) -> Result<ExecuteResult, ValidatorClientError>
|
||||
where
|
||||
M: ?Sized + Serialize + Sync,
|
||||
{
|
||||
let execute_msg = cosmwasm::MsgExecuteContract {
|
||||
sender: sender_address.clone(),
|
||||
contract: contract_address.clone(),
|
||||
msg: serde_json::to_vec(msg)?,
|
||||
funds: funds.unwrap_or_default(),
|
||||
}
|
||||
.to_msg()
|
||||
.map_err(|_| ValidatorClientError::SerializationError("MsgExecuteContract".to_owned()))?;
|
||||
|
||||
let tx_res = self
|
||||
.sign_and_broadcast_commit(sender_address, vec![execute_msg], fee, memo)
|
||||
.await?
|
||||
.check_response()?;
|
||||
|
||||
Ok(ExecuteResult {
|
||||
logs: parse_raw_logs(tx_res.deliver_tx.log)?,
|
||||
transaction_hash: tx_res.hash,
|
||||
})
|
||||
}
|
||||
|
||||
async fn send_tokens(
|
||||
&self,
|
||||
sender_address: &AccountId,
|
||||
recipient_address: &AccountId,
|
||||
amount: Vec<Coin>,
|
||||
fee: Fee,
|
||||
memo: impl Into<String> + Send + 'static,
|
||||
) -> Result<broadcast::tx_commit::Response, ValidatorClientError> {
|
||||
let send_msg = MsgSend {
|
||||
from_address: sender_address.clone(),
|
||||
to_address: recipient_address.clone(),
|
||||
amount,
|
||||
}
|
||||
.to_msg()
|
||||
.map_err(|_| ValidatorClientError::SerializationError("MsgSend".to_owned()))?;
|
||||
|
||||
self.sign_and_broadcast_commit(sender_address, vec![send_msg], fee, memo)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn delegate_tokens(
|
||||
&self,
|
||||
delegator_address: &AccountId,
|
||||
validator_address: &AccountId,
|
||||
amount: Coin,
|
||||
fee: Fee,
|
||||
memo: impl Into<String> + Send + 'static,
|
||||
) -> Result<broadcast::tx_commit::Response, ValidatorClientError> {
|
||||
let delegate_msg = MsgDelegate {
|
||||
delegator_address: delegator_address.to_owned(),
|
||||
validator_address: validator_address.to_owned(),
|
||||
amount: Some(amount),
|
||||
}
|
||||
.to_msg()
|
||||
.map_err(|_| ValidatorClientError::SerializationError("MsgDelegate".to_owned()))?;
|
||||
|
||||
self.sign_and_broadcast_commit(delegator_address, vec![delegate_msg], fee, memo)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn undelegate_tokens(
|
||||
&self,
|
||||
delegator_address: &AccountId,
|
||||
validator_address: &AccountId,
|
||||
amount: Coin,
|
||||
fee: Fee,
|
||||
memo: impl Into<String> + Send + 'static,
|
||||
) -> Result<broadcast::tx_commit::Response, ValidatorClientError> {
|
||||
let undelegate_msg = MsgUndelegate {
|
||||
delegator_address: delegator_address.to_owned(),
|
||||
validator_address: validator_address.to_owned(),
|
||||
amount: Some(amount),
|
||||
}
|
||||
.to_msg()
|
||||
.map_err(|_| ValidatorClientError::SerializationError("MsgUndelegate".to_owned()))?;
|
||||
|
||||
self.sign_and_broadcast_commit(delegator_address, vec![undelegate_msg], fee, memo)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn withdraw_rewards(
|
||||
&self,
|
||||
delegator_address: &AccountId,
|
||||
validator_address: &AccountId,
|
||||
fee: Fee,
|
||||
memo: impl Into<String> + Send + 'static,
|
||||
) -> Result<broadcast::tx_commit::Response, ValidatorClientError> {
|
||||
let withdraw_msg = MsgWithdrawDelegatorReward {
|
||||
delegator_address: delegator_address.to_owned(),
|
||||
validator_address: validator_address.to_owned(),
|
||||
}
|
||||
.to_msg()
|
||||
.map_err(|_| {
|
||||
ValidatorClientError::SerializationError("MsgWithdrawDelegatorReward".to_owned())
|
||||
})?;
|
||||
|
||||
self.sign_and_broadcast_commit(delegator_address, vec![withdraw_msg], fee, memo)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Broadcast a transaction, returning immediately.
|
||||
async fn sign_and_broadcast_async(
|
||||
&self,
|
||||
signer_address: &AccountId,
|
||||
messages: Vec<Msg>,
|
||||
fee: Fee,
|
||||
memo: impl Into<String> + Send + 'static,
|
||||
) -> Result<broadcast::tx_async::Response, ValidatorClientError> {
|
||||
let tx_raw = self.sign(signer_address, messages, fee, memo).await?;
|
||||
let tx_bytes = tx_raw
|
||||
.to_bytes()
|
||||
.map_err(|_| ValidatorClientError::SerializationError("Tx".to_owned()))?;
|
||||
|
||||
CosmWasmClient::broadcast_tx_async(self, tx_bytes.into()).await
|
||||
}
|
||||
|
||||
/// Broadcast a transaction, returning the response from `CheckTx`.
|
||||
async fn sign_and_broadcast_sync(
|
||||
&self,
|
||||
signer_address: &AccountId,
|
||||
messages: Vec<Msg>,
|
||||
fee: Fee,
|
||||
memo: impl Into<String> + Send + 'static,
|
||||
) -> Result<broadcast::tx_sync::Response, ValidatorClientError> {
|
||||
let tx_raw = self.sign(signer_address, messages, fee, memo).await?;
|
||||
let tx_bytes = tx_raw
|
||||
.to_bytes()
|
||||
.map_err(|_| ValidatorClientError::SerializationError("Tx".to_owned()))?;
|
||||
|
||||
CosmWasmClient::broadcast_tx_sync(self, tx_bytes.into()).await
|
||||
}
|
||||
|
||||
/// Broadcast a transaction, returning the response from `DeliverTx`.
|
||||
async fn sign_and_broadcast_commit(
|
||||
&self,
|
||||
signer_address: &AccountId,
|
||||
messages: Vec<Msg>,
|
||||
fee: Fee,
|
||||
memo: impl Into<String> + Send + 'static,
|
||||
) -> Result<broadcast::tx_commit::Response, ValidatorClientError> {
|
||||
let tx_raw = self.sign(signer_address, messages, fee, memo).await?;
|
||||
let tx_bytes = tx_raw
|
||||
.to_bytes()
|
||||
.map_err(|_| ValidatorClientError::SerializationError("Tx".to_owned()))?;
|
||||
|
||||
CosmWasmClient::broadcast_tx_commit(self, tx_bytes.into()).await
|
||||
}
|
||||
|
||||
fn sign_direct(
|
||||
&self,
|
||||
signer_address: &AccountId,
|
||||
messages: Vec<Msg>,
|
||||
fee: Fee,
|
||||
memo: impl Into<String> + Send + 'static,
|
||||
signer_data: SignerData,
|
||||
) -> Result<tx::Raw, ValidatorClientError> {
|
||||
let signer_accounts = self.signer().try_derive_accounts()?;
|
||||
let account_from_signer = signer_accounts
|
||||
.iter()
|
||||
.find(|account| &account.address == signer_address)
|
||||
.ok_or_else(|| ValidatorClientError::SigningAccountNotFound(signer_address.clone()))?;
|
||||
|
||||
// TODO: WTF HOW IS TIMEOUT_HEIGHT SUPPOSED TO GET DETERMINED?
|
||||
// IT DOESNT EXIST IN COSMJS!!
|
||||
// try to set to 0
|
||||
let timeout_height = 0u32;
|
||||
|
||||
let tx_body = tx::Body::new(messages, memo, timeout_height);
|
||||
let signer_info =
|
||||
SignerInfo::single_direct(Some(account_from_signer.public_key), signer_data.sequence);
|
||||
let auth_info = signer_info.auth_info(fee);
|
||||
|
||||
// ideally I'd prefer to have the entire error put into the ValidatorClientError::SigningFailure
|
||||
// but I'm super hesitant to trying to downcast the eyre::Report to cosmos_sdk::error::Error
|
||||
let sign_doc = SignDoc::new(
|
||||
&tx_body,
|
||||
&auth_info,
|
||||
&signer_data.chain_id,
|
||||
signer_data.account_number,
|
||||
)
|
||||
.map_err(|_| ValidatorClientError::SigningFailure)?;
|
||||
|
||||
self.signer()
|
||||
.sign_direct_with_account(account_from_signer, sign_doc)
|
||||
}
|
||||
|
||||
async fn sign(
|
||||
&self,
|
||||
signer_address: &AccountId,
|
||||
messages: Vec<Msg>,
|
||||
fee: Fee,
|
||||
memo: impl Into<String> + Send + 'static,
|
||||
) -> Result<tx::Raw, ValidatorClientError> {
|
||||
// TODO: Future optimisation: rather than grabbing current account_number and sequence
|
||||
// on every sign request -> just keep them cached on the struct and increment as required
|
||||
let sequence_response = self.get_sequence(signer_address).await?;
|
||||
let chain_id = self.get_chain_id().await?;
|
||||
|
||||
let signer_data = SignerData {
|
||||
account_number: sequence_response.account_number,
|
||||
sequence: sequence_response.sequence,
|
||||
chain_id,
|
||||
};
|
||||
|
||||
self.sign_direct(signer_address, messages, fee, memo, signer_data)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Client {
|
||||
rpc_client: HttpClient,
|
||||
signer: DirectSecp256k1HdWallet,
|
||||
}
|
||||
|
||||
impl Client {
|
||||
pub fn connect_with_signer<U>(
|
||||
endpoint: U,
|
||||
signer: DirectSecp256k1HdWallet,
|
||||
) -> Result<Self, ValidatorClientError>
|
||||
where
|
||||
U: TryInto<HttpClientUrl, Error = TendermintRpcError>,
|
||||
{
|
||||
let rpc_client = HttpClient::new(endpoint)?;
|
||||
Ok(Client { rpc_client, signer })
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl rpc::Client for Client {
|
||||
async fn perform<R>(&self, request: R) -> rpc::Result<R::Response>
|
||||
where
|
||||
R: SimpleRequest,
|
||||
{
|
||||
self.rpc_client.perform(request).await
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl CosmWasmClient for Client {}
|
||||
|
||||
#[async_trait]
|
||||
impl SigningCosmWasmClient for Client {
|
||||
fn signer(&self) -> &DirectSecp256k1HdWallet {
|
||||
&self.signer
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,344 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// TODO: There's a significant argument to pull those out of the package and make a PR on https://github.com/cosmos/cosmos-rust/
|
||||
|
||||
use crate::nymd::cosmwasm_client::logs::Log;
|
||||
use crate::ValidatorClientError;
|
||||
use cosmos_sdk::crypto::PublicKey;
|
||||
use cosmos_sdk::proto::cosmos::auth::v1beta1::BaseAccount;
|
||||
use cosmos_sdk::proto::cosmwasm::wasm::v1beta1::{
|
||||
CodeInfoResponse, ContractCodeHistoryEntry as ProtoContractCodeHistoryEntry,
|
||||
ContractCodeHistoryOperationType, ContractInfo as ProtoContractInfo,
|
||||
};
|
||||
use cosmos_sdk::tendermint::chain;
|
||||
use cosmos_sdk::tx::{AccountNumber, SequenceNumber};
|
||||
use cosmos_sdk::{tx, AccountId, Coin};
|
||||
use serde::Serialize;
|
||||
use std::convert::TryFrom;
|
||||
|
||||
pub type ContractCodeId = u64;
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct EmptyMsg {}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct SequenceResponse {
|
||||
pub account_number: AccountNumber,
|
||||
pub sequence: SequenceNumber,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Account {
|
||||
/// Bech32 account address
|
||||
pub address: AccountId,
|
||||
pub pubkey: Option<PublicKey>,
|
||||
pub account_number: AccountNumber,
|
||||
pub sequence: SequenceNumber,
|
||||
}
|
||||
|
||||
impl TryFrom<BaseAccount> for Account {
|
||||
type Error = ValidatorClientError;
|
||||
|
||||
fn try_from(value: BaseAccount) -> Result<Self, Self::Error> {
|
||||
let address: AccountId = value
|
||||
.address
|
||||
.parse()
|
||||
.map_err(|_| ValidatorClientError::MalformedAccountAddress(value.address.clone()))?;
|
||||
|
||||
let pubkey = value
|
||||
.pub_key
|
||||
.map(PublicKey::try_from)
|
||||
.transpose()
|
||||
.map_err(|_| ValidatorClientError::InvalidPublicKey(address.clone()))?;
|
||||
|
||||
Ok(Account {
|
||||
address,
|
||||
pubkey,
|
||||
account_number: value.account_number,
|
||||
sequence: value.sequence,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Code {
|
||||
pub code_id: ContractCodeId,
|
||||
|
||||
/// Bech32 account address
|
||||
pub creator: AccountId,
|
||||
|
||||
/// sha256 hash of the code stored
|
||||
pub data_hash: Vec<u8>,
|
||||
|
||||
/// An URL to a .tar.gz archive of the source code of the contract,
|
||||
/// which can be used to reproducibly build the Wasm bytecode.
|
||||
///
|
||||
/// @see https://github.com/CosmWasm/cosmwasm-verify
|
||||
pub source: Option<String>,
|
||||
|
||||
/// A docker image (including version) to reproducibly build the Wasm bytecode from the source code.
|
||||
///
|
||||
/// @example ```cosmwasm/rust-optimizer:0.8.0```
|
||||
/// @see https://github.com/CosmWasm/cosmwasm-verify
|
||||
pub builder: Option<String>,
|
||||
}
|
||||
|
||||
impl TryFrom<CodeInfoResponse> for Code {
|
||||
type Error = ValidatorClientError;
|
||||
|
||||
fn try_from(value: CodeInfoResponse) -> Result<Self, Self::Error> {
|
||||
let CodeInfoResponse {
|
||||
code_id,
|
||||
creator,
|
||||
data_hash,
|
||||
source,
|
||||
builder,
|
||||
} = value;
|
||||
|
||||
let creator = creator
|
||||
.parse()
|
||||
.map_err(|_| ValidatorClientError::MalformedAccountAddress(creator))?;
|
||||
|
||||
let source = if source.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(source)
|
||||
};
|
||||
let builder = if builder.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(builder)
|
||||
};
|
||||
|
||||
Ok(Code {
|
||||
code_id,
|
||||
creator,
|
||||
data_hash,
|
||||
source,
|
||||
builder,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct CodeDetails {
|
||||
pub code_info: Code,
|
||||
|
||||
/// The original wasm bytes
|
||||
pub data: Vec<u8>,
|
||||
}
|
||||
|
||||
impl CodeDetails {
|
||||
pub fn new(code_info: Code, data: Vec<u8>) -> Self {
|
||||
CodeDetails { code_info, data }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct ContractInfo {
|
||||
code_id: ContractCodeId,
|
||||
creator: AccountId,
|
||||
admin: Option<AccountId>,
|
||||
label: String,
|
||||
}
|
||||
|
||||
impl TryFrom<ProtoContractInfo> for ContractInfo {
|
||||
type Error = ValidatorClientError;
|
||||
|
||||
fn try_from(value: ProtoContractInfo) -> Result<Self, Self::Error> {
|
||||
let ProtoContractInfo {
|
||||
code_id,
|
||||
creator,
|
||||
admin,
|
||||
label,
|
||||
..
|
||||
} = value;
|
||||
|
||||
let admin = if admin.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(
|
||||
admin
|
||||
.parse()
|
||||
.map_err(|_| ValidatorClientError::MalformedAccountAddress(admin))?,
|
||||
)
|
||||
};
|
||||
|
||||
Ok(ContractInfo {
|
||||
code_id,
|
||||
creator: creator
|
||||
.parse()
|
||||
.map_err(|_| ValidatorClientError::MalformedAccountAddress(creator))?,
|
||||
admin,
|
||||
label,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Contract {
|
||||
pub address: AccountId,
|
||||
|
||||
pub code_id: ContractCodeId,
|
||||
|
||||
/// Bech32 account address
|
||||
pub creator: AccountId,
|
||||
|
||||
/// Bech32-encoded admin address
|
||||
pub admin: Option<AccountId>,
|
||||
|
||||
pub label: String,
|
||||
}
|
||||
|
||||
impl Contract {
|
||||
pub(crate) fn new(address: AccountId, contract_info: ContractInfo) -> Self {
|
||||
Contract {
|
||||
address,
|
||||
code_id: contract_info.code_id,
|
||||
creator: contract_info.creator,
|
||||
admin: contract_info.admin,
|
||||
label: contract_info.label,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum ContractCodeHistoryEntryOperation {
|
||||
Init,
|
||||
Genesis,
|
||||
Migrate,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ContractCodeHistoryEntry {
|
||||
/// The source of this history entry
|
||||
pub operation: ContractCodeHistoryEntryOperation,
|
||||
pub code_id: ContractCodeId,
|
||||
pub msg_json: String,
|
||||
}
|
||||
|
||||
impl TryFrom<ProtoContractCodeHistoryEntry> for ContractCodeHistoryEntry {
|
||||
type Error = ValidatorClientError;
|
||||
|
||||
fn try_from(value: ProtoContractCodeHistoryEntry) -> Result<Self, Self::Error> {
|
||||
let operation = match ContractCodeHistoryOperationType::from_i32(value.operation)
|
||||
.ok_or(ValidatorClientError::InvalidContractHistoryOperation)?
|
||||
{
|
||||
ContractCodeHistoryOperationType::Unspecified => {
|
||||
return Err(ValidatorClientError::InvalidContractHistoryOperation)
|
||||
}
|
||||
ContractCodeHistoryOperationType::Init => ContractCodeHistoryEntryOperation::Init,
|
||||
ContractCodeHistoryOperationType::Genesis => ContractCodeHistoryEntryOperation::Genesis,
|
||||
ContractCodeHistoryOperationType::Migrate => ContractCodeHistoryEntryOperation::Migrate,
|
||||
};
|
||||
|
||||
Ok(ContractCodeHistoryEntry {
|
||||
operation,
|
||||
code_id: value.code_id,
|
||||
msg_json: String::from_utf8(value.msg).map_err(|_| {
|
||||
ValidatorClientError::DeserializationError("Contract history msg".to_owned())
|
||||
})?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// ##############################################################################
|
||||
// types specific to the signing client (perhaps they should go to separate file)
|
||||
// ##############################################################################
|
||||
|
||||
/// Signing information for a single signer that is not included in the transaction.
|
||||
#[derive(Debug)]
|
||||
pub struct SignerData {
|
||||
pub account_number: AccountNumber,
|
||||
pub sequence: SequenceNumber,
|
||||
pub chain_id: chain::Id,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct UploadMeta {
|
||||
/// An URL to a .tar.gz archive of the source code of the contract,
|
||||
/// which can be used to reproducibly build the Wasm bytecode.
|
||||
///
|
||||
/// @see https://github.com/CosmWasm/cosmwasm-verify
|
||||
pub source: Option<String>,
|
||||
|
||||
/// A docker image (including version) to reproducibly build the Wasm bytecode from the source code.
|
||||
///
|
||||
/// @example ```cosmwasm/rust-optimizer:0.8.0```
|
||||
/// @see https://github.com/CosmWasm/cosmwasm-verify
|
||||
pub builder: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct UploadResult {
|
||||
/// Size of the original wasm code in bytes
|
||||
pub original_size: usize,
|
||||
|
||||
/// A hex encoded sha256 checksum of the original wasm code (that is stored on chain)
|
||||
pub original_checksum: Vec<u8>,
|
||||
|
||||
/// Size of the compressed wasm code in bytes
|
||||
pub compressed_size: usize,
|
||||
|
||||
/// A sha256 checksum of the compressed wasm code (that is stored in the transaction)
|
||||
pub compressed_checksum: Vec<u8>,
|
||||
|
||||
/// The ID of the code assigned by the chain
|
||||
pub code_id: ContractCodeId,
|
||||
|
||||
pub logs: Vec<Log>,
|
||||
|
||||
/// Transaction hash (might be used as transaction ID)
|
||||
pub transaction_hash: tx::Hash,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct InstantiateOptions {
|
||||
/// The funds that are transferred from the sender to the newly created contract.
|
||||
/// The funds are transferred as part of the message execution after the contract address is
|
||||
/// created and before the instantiation message is executed by the contract.
|
||||
///
|
||||
/// Only native tokens are supported.
|
||||
pub funds: Vec<Coin>,
|
||||
|
||||
/// A bech32 encoded address of an admin account.
|
||||
/// Caution: an admin has the privilege to upgrade a contract.
|
||||
/// If this is not desired, do not set this value.
|
||||
pub admin: Option<AccountId>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct InstantiateResult {
|
||||
/// The address of the newly instantiated contract
|
||||
pub contract_address: AccountId,
|
||||
|
||||
pub logs: Vec<Log>,
|
||||
|
||||
/// Transaction hash (might be used as transaction ID)
|
||||
pub transaction_hash: tx::Hash,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ChangeAdminResult {
|
||||
pub logs: Vec<Log>,
|
||||
|
||||
/// Transaction hash (might be used as transaction ID)
|
||||
pub transaction_hash: tx::Hash,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct MigrateResult {
|
||||
pub logs: Vec<Log>,
|
||||
|
||||
/// Transaction hash (might be used as transaction ID)
|
||||
pub transaction_hash: tx::Hash,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ExecuteResult {
|
||||
pub logs: Vec<Log>,
|
||||
|
||||
/// Transaction hash (might be used as transaction ID)
|
||||
pub transaction_hash: tx::Hash,
|
||||
}
|
||||
@@ -0,0 +1,226 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::models::QueryRequest;
|
||||
use crate::nymd::cosmwasm_client::client::CosmWasmClient;
|
||||
use crate::nymd::cosmwasm_client::signing_client;
|
||||
use crate::nymd::cosmwasm_client::signing_client::SigningCosmWasmClient;
|
||||
use crate::nymd::cosmwasm_client::types::ExecuteResult;
|
||||
use crate::nymd::wallet::DirectSecp256k1HdWallet;
|
||||
use crate::ValidatorClientError;
|
||||
use cosmos_sdk::rpc::{Error as TendermintRpcError, HttpClient, HttpClientUrl};
|
||||
use cosmos_sdk::tx::Fee;
|
||||
use cosmos_sdk::Coin as CosmosCoin;
|
||||
use cosmos_sdk::{AccountId, Denom};
|
||||
use cosmwasm_std::Coin;
|
||||
use mixnet_contract::LayerDistribution;
|
||||
use std::convert::TryInto;
|
||||
|
||||
pub mod cosmwasm_client;
|
||||
pub mod wallet;
|
||||
|
||||
pub struct NymdClient<C> {
|
||||
client: C,
|
||||
contract_address: AccountId,
|
||||
client_address: Option<Vec<AccountId>>,
|
||||
}
|
||||
|
||||
impl NymdClient<HttpClient> {
|
||||
pub fn connect<U>(
|
||||
endpoint: U,
|
||||
contract_address: AccountId,
|
||||
) -> Result<NymdClient<HttpClient>, ValidatorClientError>
|
||||
where
|
||||
U: TryInto<HttpClientUrl, Error = TendermintRpcError>,
|
||||
{
|
||||
Ok(NymdClient {
|
||||
client: HttpClient::new(endpoint)?,
|
||||
contract_address,
|
||||
client_address: None,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl NymdClient<signing_client::Client> {
|
||||
// maybe the wallet could be made into a generic, but for now, let's just have this one implementation
|
||||
pub fn connect_with_signer<U>(
|
||||
endpoint: U,
|
||||
contract_address: AccountId,
|
||||
signer: DirectSecp256k1HdWallet,
|
||||
) -> Result<NymdClient<signing_client::Client>, ValidatorClientError>
|
||||
where
|
||||
U: TryInto<HttpClientUrl, Error = TendermintRpcError>,
|
||||
{
|
||||
let client_address = signer
|
||||
.try_derive_accounts()?
|
||||
.into_iter()
|
||||
.map(|account| account.address)
|
||||
.collect();
|
||||
|
||||
Ok(NymdClient {
|
||||
client: signing_client::Client::connect_with_signer(endpoint, signer)?,
|
||||
contract_address,
|
||||
client_address: Some(client_address),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn connect_with_mnemonic<U>(
|
||||
endpoint: U,
|
||||
contract_address: AccountId,
|
||||
mnemonic: bip39::Mnemonic,
|
||||
) -> Result<NymdClient<signing_client::Client>, ValidatorClientError>
|
||||
where
|
||||
U: TryInto<HttpClientUrl, Error = TendermintRpcError>,
|
||||
{
|
||||
let wallet = DirectSecp256k1HdWallet::from_mnemonic(mnemonic)?;
|
||||
let client_address = wallet
|
||||
.try_derive_accounts()?
|
||||
.into_iter()
|
||||
.map(|account| account.address)
|
||||
.collect();
|
||||
|
||||
Ok(NymdClient {
|
||||
client: signing_client::Client::connect_with_signer(endpoint, wallet)?,
|
||||
contract_address,
|
||||
client_address: Some(client_address),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<C> NymdClient<C> {
|
||||
pub fn address(&self) -> &AccountId
|
||||
where
|
||||
C: SigningCosmWasmClient,
|
||||
{
|
||||
// if this is a signing client (as required by the trait bound), it must have the address set
|
||||
&self.client_address.as_ref().unwrap()[0]
|
||||
}
|
||||
|
||||
// now the question is as follows: will denom always be in the format of `u{prefix}`?
|
||||
fn denom(&self) -> Denom {
|
||||
format!("u{}", self.contract_address.prefix())
|
||||
.parse()
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
// just some example API (that will be expanded on in another PR) that those generics allow us to make:
|
||||
|
||||
// this requires signing
|
||||
pub async fn bond_mixnode(
|
||||
&self,
|
||||
mixnode: MixNode,
|
||||
bond: Coin,
|
||||
) -> Result<ExecuteResult, ValidatorClientError>
|
||||
where
|
||||
C: SigningCosmWasmClient + Sync,
|
||||
{
|
||||
// TODO: will need to create some nice fee table, for now, for test sake, just use hardcoded value
|
||||
let fee_amount = CosmosCoin {
|
||||
amount: 100_0000u64.into(),
|
||||
denom: self.denom(),
|
||||
};
|
||||
let gas = 100_0000;
|
||||
let fee = Fee::from_amount_and_gas(fee_amount, gas);
|
||||
|
||||
let req = ExecuteMsg::BondMixnode { mix_node: mixnode };
|
||||
|
||||
self.client
|
||||
.execute(
|
||||
self.address(),
|
||||
&self.contract_address,
|
||||
&req,
|
||||
fee,
|
||||
"Bonding mixnode from rust!",
|
||||
Some(vec![cosmwasm_coin_to_cosmos_coin(bond)]),
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
// this is just a query
|
||||
pub async fn get_layer_distribution(&self) -> Result<LayerDistribution, ValidatorClientError>
|
||||
where
|
||||
C: CosmWasmClient + Sync,
|
||||
{
|
||||
let request = QueryRequest::LayerDistribution {};
|
||||
self.client
|
||||
.query_contract_smart(&self.contract_address, &request)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
// this will be extracted from the smart contract into the common crate later.
|
||||
// right now it's just to show how it will look later
|
||||
#[derive(serde::Serialize)]
|
||||
pub struct MixNode {
|
||||
pub host: String,
|
||||
pub mix_port: u16,
|
||||
pub verloc_port: u16,
|
||||
pub http_api_port: u16,
|
||||
pub sphinx_key: String,
|
||||
pub identity_key: String,
|
||||
pub version: String,
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum ExecuteMsg {
|
||||
BondMixnode { mix_node: MixNode },
|
||||
}
|
||||
|
||||
// this should go to helpers
|
||||
fn cosmwasm_coin_to_cosmos_coin(coin: Coin) -> CosmosCoin {
|
||||
CosmosCoin {
|
||||
denom: coin.denom.parse().unwrap(),
|
||||
// this might be a bit iffy, cosmwasm coin stores value as u128, while cosmos does it as u64
|
||||
amount: (coin.amount.u128() as u64).into(),
|
||||
}
|
||||
}
|
||||
|
||||
// #[cfg(test)]
|
||||
// mod tests {
|
||||
// use super::*;
|
||||
// use cosmwasm_std::coin;
|
||||
//
|
||||
// #[tokio::test]
|
||||
// async fn test_bond() {
|
||||
// let validator = "http://127.0.0.1:26657";
|
||||
// let contract = "punk1uft3mmgha04vr23lx08fydpjym2003xuy9cnga"
|
||||
// .parse::<AccountId>()
|
||||
// .unwrap();
|
||||
// (this is just mnemonic for validator running on my local machine, so feel free to 'steal' it)
|
||||
// let mnemonic = "claim border flee add vehicle crack romance assault fold wide flag year cousin false junk analyst parent eagle act visual tongue weasel basket impulse";
|
||||
//
|
||||
// let client =
|
||||
// NymdClient::connect_with_mnemonic(validator, contract, mnemonic.parse().unwrap())
|
||||
// .unwrap();
|
||||
//
|
||||
// let mix = MixNode {
|
||||
// host: "1.1.1.1".to_string(),
|
||||
// mix_port: 1789,
|
||||
// verloc_port: 1790,
|
||||
// http_api_port: 8080,
|
||||
// sphinx_key: "sphinxkey".to_string(),
|
||||
// identity_key: "identitykey".to_string(),
|
||||
// version: "0.11.0".to_string(),
|
||||
// };
|
||||
//
|
||||
// let result = client
|
||||
// .bond_mixnode(mix, coin(100_000000, client.denom().as_ref()))
|
||||
// .await
|
||||
// .unwrap();
|
||||
// println!("{:#?}", result)
|
||||
// }
|
||||
//
|
||||
// #[tokio::test]
|
||||
// async fn test_get_layers() {
|
||||
// let validator = "https://testnet-milhon-validator1.nymtech.net";
|
||||
// let contract = "punk10pyejy66429refv3g35g2t7am0was7yalwrzen"
|
||||
// .parse::<AccountId>()
|
||||
// .unwrap();
|
||||
//
|
||||
// let client = NymdClient::connect(validator, contract).unwrap();
|
||||
// let layers = client.get_layer_distribution().await.unwrap();
|
||||
//
|
||||
// println!("layers: {:#?}", layers);
|
||||
// }
|
||||
// }
|
||||
@@ -0,0 +1,213 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::ValidatorClientError;
|
||||
use config::defaults;
|
||||
use cosmos_sdk::bip32::{DerivationPath, XPrv};
|
||||
use cosmos_sdk::crypto::secp256k1::SigningKey;
|
||||
use cosmos_sdk::crypto::PublicKey;
|
||||
use cosmos_sdk::tx::SignDoc;
|
||||
use cosmos_sdk::{tx, AccountId};
|
||||
|
||||
/// Derivation information required to derive a keypair and an address from a mnemonic.
|
||||
struct Secp256k1Derivation {
|
||||
hd_path: DerivationPath,
|
||||
prefix: String,
|
||||
}
|
||||
|
||||
pub struct AccountData {
|
||||
pub(crate) address: AccountId,
|
||||
|
||||
pub(crate) public_key: PublicKey,
|
||||
|
||||
pub(crate) private_key: SigningKey,
|
||||
}
|
||||
|
||||
type Secp256k1Keypair = (SigningKey, PublicKey);
|
||||
|
||||
pub struct DirectSecp256k1HdWallet {
|
||||
/// Base secret
|
||||
secret: bip39::Mnemonic,
|
||||
|
||||
/// BIP39 seed
|
||||
seed: [u8; 64],
|
||||
|
||||
// An unfortunate result of immature rust async story is that async traits (only available in the separate package)
|
||||
// can't yet figure out everything and if we stored our derived account data on the struct,
|
||||
// that would include the secret key which is a dyn EcdsaSigner and hence not Sync making the wallet
|
||||
// not Sync and if used on the signing client in an async trait, it wouldn't be Send
|
||||
/// Derivation instructions
|
||||
accounts: Vec<Secp256k1Derivation>,
|
||||
}
|
||||
|
||||
impl DirectSecp256k1HdWallet {
|
||||
pub fn builder() -> DirectSecp256k1HdWalletBuilder {
|
||||
DirectSecp256k1HdWalletBuilder::default()
|
||||
}
|
||||
|
||||
/// Restores a wallet from the given BIP39 mnemonic using default options.
|
||||
pub fn from_mnemonic(mnemonic: bip39::Mnemonic) -> Result<Self, ValidatorClientError> {
|
||||
DirectSecp256k1HdWalletBuilder::new().build(mnemonic)
|
||||
}
|
||||
|
||||
pub fn generate(word_count: usize) -> Result<Self, ValidatorClientError> {
|
||||
let mneomonic = bip39::Mnemonic::generate(word_count)?;
|
||||
Self::from_mnemonic(mneomonic)
|
||||
}
|
||||
|
||||
fn derive_keypair(
|
||||
&self,
|
||||
hd_path: &DerivationPath,
|
||||
) -> Result<Secp256k1Keypair, ValidatorClientError> {
|
||||
let extended_private_key = XPrv::derive_from_path(&self.seed, hd_path)?;
|
||||
|
||||
let private_key: SigningKey = extended_private_key.into();
|
||||
let public_key = private_key.public_key();
|
||||
|
||||
Ok((private_key, public_key))
|
||||
}
|
||||
|
||||
pub fn try_derive_accounts(&self) -> Result<Vec<AccountData>, ValidatorClientError> {
|
||||
let mut accounts = Vec::with_capacity(self.accounts.len());
|
||||
for derivation_info in &self.accounts {
|
||||
let keypair = self.derive_keypair(&derivation_info.hd_path)?;
|
||||
|
||||
// it seems this can only fail if the provided account prefix is invalid
|
||||
let address = keypair
|
||||
.1
|
||||
.account_id(&derivation_info.prefix)
|
||||
.map_err(|_| ValidatorClientError::AccountDerivationError)?;
|
||||
|
||||
accounts.push(AccountData {
|
||||
address,
|
||||
public_key: keypair.1,
|
||||
private_key: keypair.0,
|
||||
})
|
||||
}
|
||||
|
||||
Ok(accounts)
|
||||
}
|
||||
|
||||
pub fn mnemonic(&self) -> String {
|
||||
self.secret.to_string()
|
||||
}
|
||||
|
||||
pub fn sign_direct_with_account(
|
||||
&self,
|
||||
signer: &AccountData,
|
||||
sign_doc: SignDoc,
|
||||
) -> Result<tx::Raw, ValidatorClientError> {
|
||||
// ideally I'd prefer to have the entire error put into the ValidatorClientError::SigningFailure
|
||||
// but I'm super hesitant to trying to downcast the eyre::Report to cosmos_sdk::error::Error
|
||||
sign_doc
|
||||
.sign(&signer.private_key)
|
||||
.map_err(|_| ValidatorClientError::SigningFailure)
|
||||
}
|
||||
|
||||
pub fn sign_direct(
|
||||
&self,
|
||||
signer_address: &AccountId,
|
||||
sign_doc: SignDoc,
|
||||
) -> Result<tx::Raw, ValidatorClientError> {
|
||||
// I hate deriving accounts at every sign here so much : (
|
||||
let accounts = self.try_derive_accounts()?;
|
||||
let account = accounts
|
||||
.iter()
|
||||
.find(|account| &account.address == signer_address)
|
||||
.ok_or_else(|| ValidatorClientError::SigningAccountNotFound(signer_address.clone()))?;
|
||||
|
||||
self.sign_direct_with_account(account, sign_doc)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DirectSecp256k1HdWalletBuilder {
|
||||
/// The password to use when deriving a BIP39 seed from a mnemonic.
|
||||
bip39_password: String,
|
||||
|
||||
/// The BIP-32/SLIP-10 derivation paths
|
||||
hd_paths: Vec<DerivationPath>,
|
||||
|
||||
/// The bech32 address prefix (human readable part)
|
||||
prefix: String,
|
||||
}
|
||||
|
||||
impl Default for DirectSecp256k1HdWalletBuilder {
|
||||
fn default() -> Self {
|
||||
DirectSecp256k1HdWalletBuilder {
|
||||
bip39_password: String::new(),
|
||||
hd_paths: vec![defaults::COSMOS_DERIVATION_PATH.parse().unwrap()],
|
||||
prefix: defaults::BECH32_PREFIX.to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl DirectSecp256k1HdWalletBuilder {
|
||||
pub fn new() -> Self {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
pub fn with_bip39_password<S: Into<String>>(mut self, password: S) -> Self {
|
||||
self.bip39_password = password.into();
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_hd_path(mut self, path: DerivationPath) -> Self {
|
||||
self.hd_paths.push(path);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_hd_paths(mut self, hd_paths: Vec<DerivationPath>) -> Self {
|
||||
self.hd_paths = hd_paths;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_prefix<S: Into<String>>(mut self, prefix: S) -> Self {
|
||||
self.prefix = prefix.into();
|
||||
self
|
||||
}
|
||||
|
||||
pub fn build(
|
||||
self,
|
||||
mnemonic: bip39::Mnemonic,
|
||||
) -> Result<DirectSecp256k1HdWallet, ValidatorClientError> {
|
||||
let seed = mnemonic.to_seed(&self.bip39_password);
|
||||
let prefix = self.prefix;
|
||||
let accounts = self
|
||||
.hd_paths
|
||||
.into_iter()
|
||||
.map(|hd_path| Secp256k1Derivation {
|
||||
hd_path,
|
||||
prefix: prefix.clone(),
|
||||
})
|
||||
.collect();
|
||||
|
||||
Ok(DirectSecp256k1HdWallet {
|
||||
accounts,
|
||||
seed,
|
||||
secret: mnemonic,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn generating_account_addresses() {
|
||||
// test vectors produced from our js wallet
|
||||
let mnemonic_address = vec![
|
||||
("crush minute paddle tobacco message debate cabin peace bar jacket execute twenty winner view sure mask popular couch penalty fragile demise fresh pizza stove", "punk1jw6mp7d5xqc7w6xm79lha27glmd0vdt32a3fj2"),
|
||||
("acquire rebel spot skin gun such erupt pull swear must define ill chief turtle today flower chunk truth battle claw rigid detail gym feel", "punk1h5hgn94nsq4kh99rjj794hr5h5q6yfm22mcqqn"),
|
||||
("step income throw wheat mobile ship wave drink pool sudden upset jaguar bar globe rifle spice frost bless glimpse size regular carry aspect ball", "punk17n9flp6jflljg6fp05dsy07wcprf2uuujse962")
|
||||
];
|
||||
|
||||
for (mnemonic, address) in mnemonic_address.into_iter() {
|
||||
let wallet = DirectSecp256k1HdWallet::from_mnemonic(mnemonic.parse().unwrap()).unwrap();
|
||||
assert_eq!(
|
||||
wallet.try_derive_accounts().unwrap()[0].address,
|
||||
address.parse().unwrap()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,8 @@ pub const DEFAULT_VALIDATOR_REST_ENDPOINTS: &[&str] = &[
|
||||
];
|
||||
pub const DEFAULT_MIXNET_CONTRACT_ADDRESS: &str = "punk10pyejy66429refv3g35g2t7am0was7yalwrzen";
|
||||
pub const NETWORK_MONITOR_ADDRESS: &str = "punk1v9qauwdq5terag6uvfsdytcs2d0sdmfdy7hgk3";
|
||||
/// Defaults Cosmos Hub/ATOM path
|
||||
pub const COSMOS_DERIVATION_PATH: &str = "m/44'/118'/0'/0/0";
|
||||
pub const BECH32_PREFIX: &str = "punk";
|
||||
pub const DENOM: &str = "upunk";
|
||||
|
||||
|
||||
@@ -7,7 +7,10 @@ edition = "2018"
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
cosmwasm-std = { version = "0.14.1" }
|
||||
# this branch is identical to 0.14.1 with addition of updated k256 dependency required to help poor cargo choose correct version
|
||||
cosmwasm-std = { git = "https://github.com/jstuczyn/cosmwasm", branch="0.14.1-updatedk256" }
|
||||
#cosmwasm-std = { version = "0.14.1" }
|
||||
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_repr = "0.1"
|
||||
schemars = "0.8"
|
||||
|
||||
Generated
+83
-70
@@ -8,17 +8,6 @@ version = "0.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd"
|
||||
|
||||
[[package]]
|
||||
name = "bitvec"
|
||||
version = "0.18.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "98fcd36dda4e17b7d7abc64cb549bf0201f4ab71e00700c798ca7e62ed3761fa"
|
||||
dependencies = [
|
||||
"funty",
|
||||
"radium",
|
||||
"wyz",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "block-buffer"
|
||||
version = "0.7.3"
|
||||
@@ -79,28 +68,26 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "const-oid"
|
||||
version = "0.4.5"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9f6b64db6932c7e49332728e3a6bd82c6b7e16016607d20923b537c3bc4c0d5f"
|
||||
checksum = "44c32f031ea41b4291d695026c023b95d59db2d8a2c7640800ed56bc8f510f22"
|
||||
|
||||
[[package]]
|
||||
name = "cosmwasm-crypto"
|
||||
version = "0.14.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "038089cdadfe61e4e85617d91cecec2c49f828db8e5986bb85e29d0b29c59b77"
|
||||
source = "git+https://github.com/jstuczyn/cosmwasm?branch=0.14.1-updatedk256#308781cd5f33b0e996176c6b500793d3648c0f7b"
|
||||
dependencies = [
|
||||
"digest 0.9.0",
|
||||
"ed25519-zebra",
|
||||
"k256",
|
||||
"rand_core",
|
||||
"rand_core 0.5.1",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cosmwasm-derive"
|
||||
version = "0.14.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "628bf3da935843795a7f52678262ef06398878b0cf4c89b69612d8525ea27b84"
|
||||
source = "git+https://github.com/jstuczyn/cosmwasm?branch=0.14.1-updatedk256#308781cd5f33b0e996176c6b500793d3648c0f7b"
|
||||
dependencies = [
|
||||
"syn",
|
||||
]
|
||||
@@ -118,8 +105,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "cosmwasm-std"
|
||||
version = "0.14.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "22de097cf4f7e7f575f51a22de7260932756ccfe499e005f98244a3043470e48"
|
||||
source = "git+https://github.com/jstuczyn/cosmwasm?branch=0.14.1-updatedk256#308781cd5f33b0e996176c6b500793d3648c0f7b"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"cosmwasm-crypto",
|
||||
@@ -134,8 +120,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "cosmwasm-storage"
|
||||
version = "0.14.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f4abf200c2651e123595b18edf67df8702d19127b487d8b3e115dcc6c2ac0e0e"
|
||||
source = "git+https://github.com/jstuczyn/cosmwasm?branch=0.14.1-updatedk256#308781cd5f33b0e996176c6b500793d3648c0f7b"
|
||||
dependencies = [
|
||||
"cosmwasm-std",
|
||||
"serde",
|
||||
@@ -157,10 +142,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7"
|
||||
|
||||
[[package]]
|
||||
name = "crypto-mac"
|
||||
version = "0.10.0"
|
||||
name = "crypto-bigint"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4857fd85a0c34b3c3297875b747c1e02e06b6a0ea32dd892d8192b9ce0813ea6"
|
||||
checksum = "b32a398eb1ccfbe7e4f452bc749c44d38dd732e9a253f19da224c416f00ee7f4"
|
||||
dependencies = [
|
||||
"generic-array 0.14.4",
|
||||
"rand_core 0.6.3",
|
||||
"subtle",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crypto-mac"
|
||||
version = "0.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b1d1a86f49236c215f271d40892d5fc950490551400b02ef360692c29815c714"
|
||||
dependencies = [
|
||||
"generic-array 0.14.4",
|
||||
"subtle",
|
||||
@@ -174,16 +171,16 @@ checksum = "639891fde0dbea823fc3d798a0fdf9d2f9440a42d64a78ab3488b0ca025117b3"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"digest 0.9.0",
|
||||
"rand_core",
|
||||
"rand_core 0.5.1",
|
||||
"subtle",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "der"
|
||||
version = "0.1.0"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "51f59c66c30bb7445c8320a5f9233e437e3572368099f25532a59054328899b4"
|
||||
checksum = "49f215f706081a44cb702c71c39a52c05da637822e9c1645a50b7202689e982d"
|
||||
dependencies = [
|
||||
"const-oid",
|
||||
]
|
||||
@@ -214,10 +211,11 @@ checksum = "ee2626afccd7561a06cf1367e2950c4718ea04565e20fb5029b6c7d8ad09abcf"
|
||||
|
||||
[[package]]
|
||||
name = "ecdsa"
|
||||
version = "0.10.2"
|
||||
version = "0.12.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "41fbdb4ff710acb4db8ca29f93b897529ea6d6a45626d5183b47e012aa6ae7e4"
|
||||
checksum = "713c32426287891008edb98f8b5c6abb2130aa043c93a818728fcda78606f274"
|
||||
dependencies = [
|
||||
"der",
|
||||
"elliptic-curve",
|
||||
"hmac",
|
||||
"signature",
|
||||
@@ -231,7 +229,7 @@ checksum = "0a128b76af6dd4b427e34a6fd43dc78dbfe73672ec41ff615a2414c1a0ad0409"
|
||||
dependencies = [
|
||||
"curve25519-dalek",
|
||||
"hex",
|
||||
"rand_core",
|
||||
"rand_core 0.5.1",
|
||||
"serde",
|
||||
"sha2",
|
||||
"thiserror",
|
||||
@@ -239,18 +237,16 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "elliptic-curve"
|
||||
version = "0.8.5"
|
||||
version = "0.10.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f2db227e61a43a34915680bdda462ec0e212095518020a88a1f91acd16092c39"
|
||||
checksum = "83e5c176479da93a0983f0a6fdc3c1b8e7d5be0d7fe3fe05a99f15b96582b9a8"
|
||||
dependencies = [
|
||||
"bitvec",
|
||||
"digest 0.9.0",
|
||||
"crypto-bigint",
|
||||
"ff",
|
||||
"funty",
|
||||
"generic-array 0.14.4",
|
||||
"group",
|
||||
"pkcs8",
|
||||
"rand_core",
|
||||
"rand_core 0.6.3",
|
||||
"subtle",
|
||||
"zeroize",
|
||||
]
|
||||
@@ -263,21 +259,14 @@ checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed"
|
||||
|
||||
[[package]]
|
||||
name = "ff"
|
||||
version = "0.8.0"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "01646e077d4ebda82b73f1bca002ea1e91561a77df2431a9e79729bcc31950ef"
|
||||
checksum = "63eec06c61e487eecf0f7e6e6372e596a81922c28d33e645d6983ca6493a1af0"
|
||||
dependencies = [
|
||||
"bitvec",
|
||||
"rand_core",
|
||||
"rand_core 0.6.3",
|
||||
"subtle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "funty"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fed34cd105917e91daa4da6b3728c47b068749d6a62c59811f06ed2ac71d9da7"
|
||||
|
||||
[[package]]
|
||||
name = "generic-array"
|
||||
version = "0.12.4"
|
||||
@@ -305,17 +294,28 @@ checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"wasi",
|
||||
"wasi 0.9.0+wasi-snapshot-preview1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"wasi 0.10.2+wasi-snapshot-preview1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "group"
|
||||
version = "0.8.0"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cc11f9f5fbf1943b48ae7c2bf6846e7d827a512d1be4f23af708f5ca5d01dde1"
|
||||
checksum = "1c363a5301b8f153d80747126a04b3c82073b9fe3130571a9d170cacdeaf7912"
|
||||
dependencies = [
|
||||
"ff",
|
||||
"rand_core",
|
||||
"rand_core 0.6.3",
|
||||
"subtle",
|
||||
]
|
||||
|
||||
@@ -341,9 +341,9 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
|
||||
|
||||
[[package]]
|
||||
name = "hmac"
|
||||
version = "0.10.1"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c1441c6b1e930e2817404b5046f1f989899143a12bf92de603b69f4e0aee1e15"
|
||||
checksum = "2a2a2320eb7ec0ebe8da8f744d7812d9fc4cb4d09344ac01898dbcb6a20ae69b"
|
||||
dependencies = [
|
||||
"crypto-mac",
|
||||
"digest 0.9.0",
|
||||
@@ -373,9 +373,9 @@ checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736"
|
||||
|
||||
[[package]]
|
||||
name = "k256"
|
||||
version = "0.7.3"
|
||||
version = "0.9.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4476a0808212a9e81ce802eb1a0cfc60e73aea296553bacc0fac7e1268bc572a"
|
||||
checksum = "008b0281ca8032567c9711cd48631781c15228301860a39b32deb28d63125e46"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"ecdsa",
|
||||
@@ -485,11 +485,12 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "pkcs8"
|
||||
version = "0.3.3"
|
||||
version = "0.7.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b4839a901843f3942576e65857f0ebf2e190ef7024d3c62a94099ba3f819ad1d"
|
||||
checksum = "fbee84ed13e44dd82689fa18348a49934fa79cc774a344c42fc9b301c71b140a"
|
||||
dependencies = [
|
||||
"der",
|
||||
"spki",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -516,19 +517,22 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "radium"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "def50a86306165861203e7f84ecffbbdfdea79f0e51039b33de1e952358c47ac"
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
"getrandom 0.1.16",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.6.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7"
|
||||
dependencies = [
|
||||
"getrandom 0.2.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -650,12 +654,21 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "signature"
|
||||
version = "1.2.2"
|
||||
version = "1.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "29f060a7d147e33490ec10da418795238fd7545bba241504d6b31a409f2e6210"
|
||||
checksum = "c19772be3c4dd2ceaacf03cb41d5885f2a02c4d8804884918e3a258480803335"
|
||||
dependencies = [
|
||||
"digest 0.9.0",
|
||||
"rand_core",
|
||||
"rand_core 0.6.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "spki"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "987637c5ae6b3121aba9d513f869bd2bff11c4cc086c22473befd6649c0bd521"
|
||||
dependencies = [
|
||||
"der",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -753,10 +766,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
|
||||
|
||||
[[package]]
|
||||
name = "wyz"
|
||||
version = "0.2.0"
|
||||
name = "wasi"
|
||||
version = "0.10.2+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "85e60b0d1b5f99db2556934e21937020776a5d31520bf169e851ac44e6420214"
|
||||
checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6"
|
||||
|
||||
[[package]]
|
||||
name = "zeroize"
|
||||
|
||||
@@ -35,8 +35,14 @@ backtraces = ["cosmwasm-std/backtraces"]
|
||||
[dependencies]
|
||||
mixnet-contract = { path = "../../common/mixnet-contract" }
|
||||
config = { path = "../../common/config"}
|
||||
cosmwasm-std = { version = "0.14.1", features = ["iterator"] }
|
||||
cosmwasm-storage = { version = "0.14.1", features = ["iterator"] }
|
||||
|
||||
# this branch is identical to 0.14.1 with addition of updated k256 dependency required to help poor cargo choose correct version
|
||||
cosmwasm-std = { git = "https://github.com/jstuczyn/cosmwasm", branch="0.14.1-updatedk256", features = ["iterator"] }
|
||||
cosmwasm-storage = { git = "https://github.com/jstuczyn/cosmwasm", branch="0.14.1-updatedk256", features = ["iterator"] }
|
||||
|
||||
#cosmwasm-std = { version = "0.14.1", features = ["iterator"] }
|
||||
#cosmwasm-storage = { version = "0.14.1", features = ["iterator"] }
|
||||
|
||||
schemars = "0.8"
|
||||
serde = { version = "1.0.103", default-features = false, features = ["derive"] }
|
||||
thiserror = { version = "1.0.23" }
|
||||
|
||||
Reference in New Issue
Block a user