Skip to content

Commit

Permalink
Merge pull request #1345 from CosmWasm/cosmwasm-msg-attr-macro
Browse files Browse the repository at this point in the history
Implement `msg` macro to simplify configuring serialization
  • Loading branch information
uint authored Jul 12, 2022
2 parents 0a31f55 + e117fea commit e807a62
Show file tree
Hide file tree
Showing 5 changed files with 247 additions and 28 deletions.
85 changes: 85 additions & 0 deletions MIGRATING.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,91 @@ 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 -> 1.1.0

- There are changes to how we generate schemas, resulting in less boilerplace
maintenance for smart contract devs. Old contracts will continue working for a
while, but it's highly recommended to migrate now.

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,
}
}
```

This changes the format of the schemas generated by the contract. They're now in
one structured, unified file (parseable by machines) rather than a bunch of
arbitrary ones.

## 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

0 comments on commit e807a62

Please sign in to comment.