Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement msg macro to simplify configuring serialization #1345

Merged
merged 9 commits into from
Jul 12, 2022
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 82 additions & 0 deletions MIGRATING.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,88 @@ This guide explains what is needed to upgrade contracts when migrating over
major releases of `cosmwasm`. Note that you can also view the
[complete CHANGELOG](./CHANGELOG.md) to understand the differences.

## 1.0.0 -> ?
uint marked this conversation as resolved.
Show resolved Hide resolved

- There are changes to how we generate schemas, resulting in less boilerplace
maintenance for smart contract devs. The changes are backwards-compatible -
old contracts will continue working for a while, but it's highly recommended
to migrate now.
uint marked this conversation as resolved.
Show resolved Hide resolved

Your contract should have a `cosmwasm_schema` dependency in its `Cargo.toml`
file. Move it from `dev-dependencies`to regular `dependencies`.

```diff
[dependencies]
+ cosmwasm-schema = { version = "1.0.0" }
cosmwasm-std = { version = "1.0.0", features = ["stargate"] }
cw-storage-plus = { path = "../../packages/storage-plus", version = "0.10.0" }
schemars = "0.8.1"
serde = { version = "1.0.103", default-features = false, features = ["derive"] }
thiserror = { version = "1.0.23" }

[dev-dependencies]
- cosmwasm-schema = { version = "1.0.0" }
```

Types you send to the contract and receive back are annotated with a bunch of
derives and sometimes `serde` annotations. Remove all those attributes and
replace them with `#[cosmwasm_schema::cw_serde]`.

```diff
+ use cosmwasm_schema::{cw_serde, QueryResponses};

// *snip*

- #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
- #[serde(deny_unknown_fields, rename_all = "snake_case")]
+ #[cw_serde]
pub enum ExecuteMsg {
Release {},
Argon2 {
mem_cost: u32,
time_cost: u32,
},
}
```

Derive `cosmwasm_schema::QueryResponses` for your `QueryMsg` type and annotate
each query with its return type. This lets the interface description file
(schema) generation know what return types to include - and therefore, any
clients relying on the generated schemas will also know how to interpret
response data from your contract.

```diff
#[cw_serde]
+ #[derive(QueryResponses)]
pub enum QueryMsg {
+ #[returns(VerifierResponse)]
Verifier {},
+ #[returns(Uint128)]
Balance { address: String },
}
```

The boilerplate in `examples/schema.rs` is also replaced with a macro
invocation. Just give it all the types sent to the contract's entrypoints.
Skip the ones that are not present in the contract - the only mandatory field
is `instantiate`.

```rust
use cosmwasm_schema::write_api;

use hackatom::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg, SudoMsg};

fn main() {
write_api! {
instantiate: InstantiateMsg,
query: QueryMsg,
execute: ExecuteMsg,
sudo: SudoMsg,
migrate: MigrateMsg,
}
}
```

## 1.0.0-beta -> 1.0.0

- The minimum Rust supported version is 1.56.1. Verify your Rust version is >=
Expand Down
29 changes: 10 additions & 19 deletions contracts/hackatom/src/msg.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
use cosmwasm_schema::QueryResponses;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use cosmwasm_schema::{cw_serde, QueryResponses};

use cosmwasm_std::{AllBalanceResponse, Binary, Coin};

#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
#[serde(deny_unknown_fields)]
#[cw_serde]
pub struct InstantiateMsg {
pub verifier: String,
pub beneficiary: String,
Expand All @@ -18,17 +15,15 @@ pub struct InstantiateMsg {
///
/// Note that the contract doesn't enforce permissions here, this is done
/// by blockchain logic (in the future by blockchain governance)
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
#[serde(deny_unknown_fields)]
#[cw_serde]
pub struct MigrateMsg {
pub verifier: String,
}

/// SudoMsg is only exposed for internal Cosmos SDK modules to call.
/// This is showing how we can expose "admin" functionality than can not be called by
/// external users or contracts, but only trusted (native/Go) code in the blockchain
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
#[serde(deny_unknown_fields, rename_all = "snake_case")]
#[cw_serde]
pub enum SudoMsg {
StealFunds {
recipient: String,
Expand All @@ -38,8 +33,7 @@ pub enum SudoMsg {

// failure modes to help test wasmd, based on this comment
// https://github.com/cosmwasm/wasmd/issues/8#issuecomment-576146751
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
#[serde(deny_unknown_fields, rename_all = "snake_case")]
#[cw_serde]
pub enum ExecuteMsg {
/// Releasing all funds in the contract to the beneficiary. This is the only "proper" action of this demo contract.
Release {},
Expand Down Expand Up @@ -67,8 +61,8 @@ pub enum ExecuteMsg {
UserErrorsInApiCalls {},
}

#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema, QueryResponses)]
#[serde(deny_unknown_fields, rename_all = "snake_case")]
#[cw_serde]
#[derive(QueryResponses)]
pub enum QueryMsg {
/// returns a human-readable representation of the verifier
/// use to ensure query path works in integration tests
Expand All @@ -88,21 +82,18 @@ pub enum QueryMsg {
GetInt {},
}

#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
#[serde(deny_unknown_fields)]
#[cw_serde]
pub struct VerifierResponse {
pub verifier: String,
}

#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
#[serde(deny_unknown_fields)]
#[cw_serde]
pub struct RecurseResponse {
/// hashed is the result of running sha256 "work+1" times on the contract's human address
pub hashed: Binary,
}

#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
#[serde(deny_unknown_fields)]
#[cw_serde]
pub struct IntResponse {
pub int: u32,
}
107 changes: 107 additions & 0 deletions packages/schema-derive/src/cw_serde.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
use syn::{parse_quote, DeriveInput};

pub fn cw_serde_impl(input: DeriveInput) -> DeriveInput {
match input.data {
syn::Data::Struct(_) => parse_quote! {
#[derive(
serde::Serialize,
serde::Deserialize,
Clone,
Debug,
PartialEq,
schemars::JsonSchema
)]
#[serde(deny_unknown_fields)]
#input
},
syn::Data::Enum(_) => parse_quote! {
#[derive(
serde::Serialize,
serde::Deserialize,
Clone,
Debug,
PartialEq,
schemars::JsonSchema
)]
#[serde(deny_unknown_fields, rename_all = "snake_case")]
#input
},
syn::Data::Union(_) => panic!("unions are not supported"),
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn structs() {
let expanded = cw_serde_impl(parse_quote! {
pub struct InstantiateMsg {
pub verifier: String,
pub beneficiary: String,
}
});

let expected = parse_quote! {
#[derive(
serde::Serialize,
serde::Deserialize,
Clone,
Debug,
PartialEq,
schemars::JsonSchema
)]
#[serde(deny_unknown_fields)]
pub struct InstantiateMsg {
pub verifier: String,
pub beneficiary: String,
}
};

assert_eq!(expanded, expected);
}

#[test]
fn enums() {
let expanded = cw_serde_impl(parse_quote! {
pub enum SudoMsg {
StealFunds {
recipient: String,
amount: Vec<Coin>,
},
}
});

let expected = parse_quote! {
#[derive(
serde::Serialize,
serde::Deserialize,
Clone,
Debug,
PartialEq,
schemars::JsonSchema
)]
#[serde(deny_unknown_fields, rename_all = "snake_case")]
pub enum SudoMsg {
StealFunds {
recipient: String,
amount: Vec<Coin>,
},
}
};

assert_eq!(expanded, expected);
}

#[test]
#[should_panic(expected = "unions are not supported")]
fn unions() {
cw_serde_impl(parse_quote! {
pub union SudoMsg {
x: u32,
y: u32,
}
});
}
}
15 changes: 14 additions & 1 deletion packages/schema-derive/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
mod cw_serde;
mod generate_api;
mod query_responses;

use quote::ToTokens;
use syn::{parse_macro_input, ItemEnum};
use syn::{parse_macro_input, DeriveInput, ItemEnum};

#[proc_macro_derive(QueryResponses, attributes(returns))]
pub fn query_responses_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
Expand Down Expand Up @@ -30,3 +31,15 @@ pub fn generate_api(input: proc_macro::TokenStream) -> proc_macro::TokenStream {

proc_macro::TokenStream::from(expanded)
}

#[proc_macro_attribute]
pub fn cw_serde(
_attr: proc_macro::TokenStream,
input: proc_macro::TokenStream,
) -> proc_macro::TokenStream {
let input = parse_macro_input!(input as DeriveInput);

let expanded = cw_serde::cw_serde_impl(input).into_token_stream();

proc_macro::TokenStream::from(expanded)
}
39 changes: 31 additions & 8 deletions packages/schema/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,31 @@ pub use query_response::QueryResponses;
pub use remove::remove_schemas;

// Re-exports
/// An attribute macro that annotates types with things they need to be properly (de)serialized
/// for use in CosmWasm contract messages and/or responses, and also for schema generation.
///
/// This derives things like `serde::Serialize` or `schemars::JsonSchema`, makes sure
/// variants are `snake_case` in the resulting JSON, and so forth.
///
/// # Example
/// ```
/// use cosmwasm_schema::{cw_serde, QueryResponses};
///
/// #[cw_serde]
/// pub struct InstantiateMsg {
/// owner: String,
/// }
///
/// #[cw_serde]
/// #[derive(QueryResponses)]
/// pub enum QueryMsg {
/// #[returns(Vec<String>)]
/// Denoms {},
/// #[returns(String)]
/// AccountName { account: String },
/// }
/// ```
pub use cosmwasm_schema_derive::cw_serde;
/// Generates an [`Api`](crate::Api) for the contract. The body describes the message
/// types exported in the schema and allows setting contract name and version overrides.
///
Expand All @@ -20,13 +45,12 @@ pub use remove::remove_schemas;
///
/// # Example
/// ```
/// use cosmwasm_schema::{generate_api};
/// use schemars::{JsonSchema};
/// use cosmwasm_schema::{cw_serde, generate_api};
///
/// #[derive(JsonSchema)]
/// #[cw_serde]
/// struct InstantiateMsg;
///
/// #[derive(JsonSchema)]
/// #[cw_serde]
/// struct MigrateMsg;
///
/// let api = generate_api! {
Expand All @@ -52,13 +76,12 @@ pub use cosmwasm_schema_derive::generate_api;
///
/// # Example
/// ```
/// use cosmwasm_schema::{write_api};
/// use schemars::{JsonSchema};
/// use cosmwasm_schema::{cw_serde, write_api};
///
/// #[derive(JsonSchema)]
/// #[cw_serde]
/// struct InstantiateMsg;
///
/// #[derive(JsonSchema)]
/// #[cw_serde]
/// struct MigrateMsg;
///
/// write_api! {
Expand Down