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:
Jędrzej Stuczyński
2021-08-06 16:31:57 +01:00
committed by GitHub
parent d643df74b3
commit ebeac73f30
17 changed files with 2909 additions and 172 deletions
Generated
+763 -96
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -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()
)
}
}
}
+2
View File
@@ -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";
+4 -1
View File
@@ -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"
+83 -70
View File
@@ -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"
+8 -2
View File
@@ -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" }