Skip to content

Commit

Permalink
feat(nibiru-std): implement NibiruStargateQuery as an extension of pr…
Browse files Browse the repository at this point in the history
…ost::Name

feat(nibiru-std): implement NibiruStargateQuery as an extension of prost::Name
  • Loading branch information
Unique-Divine authored Oct 22, 2023
2 parents a4e71b1 + 8b6f466 commit 41ccaec
Show file tree
Hide file tree
Showing 11 changed files with 257 additions and 44 deletions.
29 changes: 17 additions & 12 deletions .github/workflows/rust-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,11 @@ jobs:
rust-test:
runs-on: ubuntu-latest
steps:
- name: Checkout branch
- name: "Checkout branch"
uses: actions/checkout@v4
with:
submodules: true
- name: Set up Rust caches
- name: "Set up Rust caches"
uses: actions/cache@v3
id: rust-cache
with:
Expand All @@ -32,7 +32,7 @@ jobs:
~/.cargo/git/db/
target/
key: ${{ runner.os }}-cargo-${{ hashFiles('Cargo.lock') }}
- name: Run tests
- name: "Run tests"
# Test all packages
run: cargo test --all --verbose
# Test single package (e.g. contracts/whitelist)
Expand All @@ -41,11 +41,11 @@ jobs:
rust-build:
runs-on: ubuntu-latest
steps:
- name: Checkout branch
- name: "Checkout branch"
uses: actions/checkout@v4
with:
submodules: true
- name: Set up Rust caches
- name: "Set up Rust caches"
uses: actions/cache@v3
id: rust-cache
with:
Expand All @@ -56,7 +56,7 @@ jobs:
~/.cargo/git/db/
target/
key: ${{ runner.os }}-cargo-${{ hashFiles('Cargo.lock') }}
- name: Compile all Rust code in workspace
- name: "Compile all Rust code in workspace"
run: cargo build

coverage:
Expand All @@ -67,7 +67,7 @@ jobs:
- uses: actions/checkout@v4
with:
submodules: true
- name: Set up Rust caches
- name: "Set up Rust caches"
uses: actions/cache@v3
id: rust-cache
with:
Expand All @@ -78,18 +78,23 @@ jobs:
~/.cargo/git/db/
target/
key: ${{ runner.os }}-cargo-${{ hashFiles('Cargo.lock') }}
- name: Install stable
- name: "Install stable rust-toolchain"
uses: dtolnay/rust-toolchain@stable
with:
components: llvm-tools-preview
- name: cargo install cargo-llvm-cov
- name: "cargo install cargo-llvm-cov"
uses: taiki-e/install-action@cargo-llvm-cov
- name: cargo generate-lockfile
- name: "cargo generate-lockfile"
if: hashFiles('Cargo.lock') == ''
run: cargo generate-lockfile
- name: cargo llvm-cov

- name: "Install just"
# casey/just: https://just.systems/man/en/chapter_6.html
# taiki-e/install-action: https://github.com/taiki-e/install-action
uses: taiki-e/install-action@just
- name: "cargo llvm-cov"
# Outputs the code coverage in a format that codecov.io can understand
run: cargo llvm-cov --lcov --output-path lcov.info
run: just test-coverage
# run: cargo llvm-cov --locked --all-features --lcov --output-path lcov.info
- name: Upload to codecov.io
uses: codecov/codecov-action@v3
Expand Down
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion contracts/bindings-perp/src/msg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -229,4 +229,4 @@ impl CustomQuery for QueryMsg {}
#[cw_serde]
pub struct SudoersQueryResponse {
pub sudoers: Sudoers,
}
}
4 changes: 2 additions & 2 deletions contracts/nibi-stargate/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ Table of Contents
```json
{
"mint": {
"coin": { "amount": "[amount]", "denom": "tc/[contract-addr]/[subdenom]" },
"coin": { "amount": "[amount]", "denom": "tf/[contract-addr]/[subdenom]" },
"mint_to": "[mint-to-addr]"
}
}
Expand All @@ -45,7 +45,7 @@ Table of Contents
```json
{
"burn": {
"coin": { "amount": "[amount]", "denom": "tc/[contract-addr]/[subdenom]" },
"coin": { "amount": "[amount]", "denom": "tf/[contract-addr]/[subdenom]" },
"burn_from": "[burn-from-addr]"
}
}
Expand Down
8 changes: 8 additions & 0 deletions justfile
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@ setup:
just -l
install:
# https://crates.io/crates/clippy
rustup component add clippy
# https://crates.io/crates/cargo-llvm-cov
cargo install cargo-llvm-cov

wasm-all:
bash scripts/wasm-out.sh
Expand Down Expand Up @@ -61,6 +64,11 @@ test *pkg:
test-all:
cargo test

# Test everything and output coverage report.
test-coverage:
cargo llvm-cov --lcov --output-path lcov.info \
--ignore-filename-regex .*buf\/[^\/]+\.rs$

# Format, lint, and test
tidy:
just fmt
Expand Down
1 change: 1 addition & 0 deletions nibiru-std/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ serde = "1.0.188"
anyhow = "1"
thiserror = "1.0.49"
nibiru-macro = { path = "../packages/macro" }
serde_json = "1.0"

# cargo run --bin script-name
# [[bin]]
Expand Down
12 changes: 12 additions & 0 deletions nibiru-std/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,25 @@ use thiserror::Error;

use cosmwasm_std as cw;

/// Shorthand for an empty anyhow::Result. Useful for idiomatic tests.
#[cfg(test)]
pub type TestResult = anyhow::Result<()>;

pub type NibiruResult<T> = Result<T, NibiruError>;

#[derive(Error, Debug, PartialEq)]
pub enum NibiruError {
#[error("{0}")]
CwStd(#[from] cw::StdError),

#[error("no prost::Name implementation for type {}, where prost::Name.type_url() is needed.", type_name)]
NoTypeUrl { type_name: String },

#[error("prost::Name::type_url {} does not correspond to a QueryRequest::Stargate path.", type_url)]
ProstNameisNotQuery { type_url: String },

#[error("prost::Name::type_url {} does not correspond to a CosmosMsg::Stargate type_url.", type_url)]
ProstNameisNotMsg { type_url: String },
}

impl From<NibiruError> for cw::StdError {
Expand Down
2 changes: 2 additions & 0 deletions nibiru-std/src/proto/mod.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
mod traits;
mod type_url_cosmos;
mod type_url_nibiru;

pub use traits::*;
pub use type_url_cosmos::*;
pub use type_url_nibiru::*;

pub mod cosmos {
Expand Down
66 changes: 62 additions & 4 deletions nibiru-std/src/proto/traits.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
//! nibiru-std::proto - traits.rs : Implements extensions for prost::Message
//! types for easy conversion to types needed for CosmWasm smart contracts.
use cosmwasm_std::{Binary, CosmosMsg};
use cosmwasm_std::{Binary, CosmosMsg, QueryRequest};

use crate::errors::{NibiruError, NibiruResult};

pub trait NibiruProstMsg: prost::Message {
/// Serialize this protobuf message as a byte vector
Expand Down Expand Up @@ -60,10 +62,66 @@ where
}
}

/// Workaround for backwards type_url implementation.
/// This is fixed in a future version of prost but isn't yet available in
/// v0.12.1. See https://github.com/tokio-rs/prost/pull/923
/// The "type URL" in the context of protobuf is used with a feature
/// called "Any", a type that allows one to serialize and embed proto
/// message (prost::Message) objects without as opaque values without having
/// to predefine the type in the original message declaration.
///
/// For example, a protobuf definition like:
/// ```proto
/// message CustomProtoMsg { string name = 1; }
/// ```
/// might have a type URL like "googleapis.com/package.name.CustomProtoMsg".
/// Usage of `Any` with type URLs enables dynamic message composition and
/// flexibility.
///
/// We use these type URLs in CosmWasm and the Cosmos-SDK to classify
/// gRPC messages for transactions and queries because Tendermint ABCI
/// messages are protobuf objects.
fn type_url(&self) -> String {
format!("/{}.{}", Self::PACKAGE, Self::NAME)
}
}

pub trait NibiruStargateQuery: prost::Message + prost::Name {
#![allow(clippy::wrong_self_convention)]
fn into_stargate_query(
&self,
) -> NibiruResult<QueryRequest<cosmwasm_std::Empty>>;

fn path(&self) -> String;
}

impl<M> NibiruStargateQuery for M
where
M: prost::Message + prost::Name,
{
/// Returns the `prost::Message` as a `QueryRequest::Stargate` object.
/// Errors if the `prost::Name::type_url` does not indicate the type is a
/// query.
fn into_stargate_query(
&self,
) -> NibiruResult<QueryRequest<cosmwasm_std::Empty>> {
if !self.type_url().contains("Query") {
return Err(NibiruError::ProstNameisNotQuery {
type_url: self.type_url(),
});
}
Ok(QueryRequest::Stargate {
path: self.path(),
data: self.to_binary(),
})
}

/// Fully qualified gRPC service path used for routing.
/// Ex.: "/cosmos.bank.v1beta1.Query/SupplyOf"
fn path(&self) -> String {
let service_name = format!(
"Query/{}",
Self::NAME
.trim_start_matches("Query")
.trim_end_matches("Request")
);
format!("/{}.{}", Self::PACKAGE, service_name)
}
}
79 changes: 79 additions & 0 deletions nibiru-std/src/proto/type_url_cosmos.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
//! Implements the prost::Name trait for cosmos protobuf types, which defines
//! the prost::Message.type_url function needed for CosmWasm smart contracts.
use prost::Name;

use crate::proto::cosmos;

const PACKAGE_BANK: &str = "cosmos.bank.v1beta1";

impl Name for cosmos::bank::v1beta1::QuerySupplyOfRequest {
const NAME: &'static str = "QuerySupplyOfRequest";
const PACKAGE: &'static str = PACKAGE_BANK;
}

impl Name for cosmos::bank::v1beta1::QueryBalanceRequest {
const NAME: &'static str = "QueryBalanceRequest";
const PACKAGE: &'static str = PACKAGE_BANK;
}

impl Name for cosmos::bank::v1beta1::QueryAllBalancesRequest {
const NAME: &'static str = "QueryAllBalancesRequest";
const PACKAGE: &'static str = PACKAGE_BANK;
}

impl Name for cosmos::bank::v1beta1::QueryDenomMetadataRequest {
const NAME: &'static str = "QueryDenomMetadataRequest";
const PACKAGE: &'static str = PACKAGE_BANK;
}

#[cfg(test)]
mod tests {

use cosmwasm_std::{Empty, QueryRequest};

use crate::{
errors::{NibiruResult, TestResult},
proto::{cosmos, NibiruStargateQuery},
};

#[test]
fn stargate_query_conversion() -> TestResult {
let test_cases: Vec<(&str, NibiruResult<QueryRequest<Empty>>)> = vec![
(
"/cosmos.bank.v1beta1.Query/SupplyOf",
cosmos::bank::v1beta1::QuerySupplyOfRequest {
denom: String::from("some_denom"),
}
.into_stargate_query(),
),
(
"/cosmos.bank.v1beta1.Query/Balance",
cosmos::bank::v1beta1::QueryBalanceRequest {
address: String::from("some_address"),
denom: String::from("some_denom"),
}
.into_stargate_query(),
),
(
"/cosmos.bank.v1beta1.Query/DenomMetadata",
cosmos::bank::v1beta1::QueryDenomMetadataRequest {
denom: String::from("some_denom"),
}
.into_stargate_query(),
),
];

for test_case in test_cases {
let test_case_path = test_case.0;
let pb_query = test_case.1?;
if let QueryRequest::Stargate { path, data: _data } = &pb_query {
assert_eq!(test_case_path, path)
} else {
panic!("failed test on case: {:#?} ", test_case_path)
}
}

Ok(())
}
}
Loading

0 comments on commit 41ccaec

Please sign in to comment.