From eb3a699425abd9c0a5a7eb9fd925c988971bd1a7 Mon Sep 17 00:00:00 2001 From: Tomasz Kurcz Date: Mon, 11 Jul 2022 10:56:11 +0200 Subject: [PATCH 1/9] schema-derive: introduce the `msg` macro --- packages/schema-derive/src/lib.rs | 15 ++++- packages/schema-derive/src/msg.rs | 107 ++++++++++++++++++++++++++++++ packages/schema/src/lib.rs | 1 + 3 files changed, 122 insertions(+), 1 deletion(-) create mode 100644 packages/schema-derive/src/msg.rs diff --git a/packages/schema-derive/src/lib.rs b/packages/schema-derive/src/lib.rs index 61b8321567..3d00645335 100644 --- a/packages/schema-derive/src/lib.rs +++ b/packages/schema-derive/src/lib.rs @@ -1,8 +1,9 @@ mod generate_api; +mod msg; 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 { @@ -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 msg( + _attr: proc_macro::TokenStream, + input: proc_macro::TokenStream, +) -> proc_macro::TokenStream { + let input = parse_macro_input!(input as DeriveInput); + + let expanded = msg::msg_impl(input).into_token_stream(); + + proc_macro::TokenStream::from(expanded) +} diff --git a/packages/schema-derive/src/msg.rs b/packages/schema-derive/src/msg.rs new file mode 100644 index 0000000000..353e031059 --- /dev/null +++ b/packages/schema-derive/src/msg.rs @@ -0,0 +1,107 @@ +use syn::{parse_quote, DeriveInput}; + +pub fn msg_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 = msg_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 = msg_impl(parse_quote! { + pub enum SudoMsg { + StealFunds { + recipient: String, + amount: Vec, + }, + } + }); + + 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, + }, + } + }; + + assert_eq!(expanded, expected); + } + + #[test] + #[should_panic(expected = "unions are not supported")] + fn unions() { + msg_impl(parse_quote! { + pub union SudoMsg { + x: u32, + y: u32, + } + }); + } +} diff --git a/packages/schema/src/lib.rs b/packages/schema/src/lib.rs index 254c219523..89ad5da5aa 100644 --- a/packages/schema/src/lib.rs +++ b/packages/schema/src/lib.rs @@ -36,6 +36,7 @@ pub use remove::remove_schemas; /// }.render(); /// ``` pub use cosmwasm_schema_derive::generate_api; +pub use cosmwasm_schema_derive::msg; /// Takes care of generating the interface description file for a contract. The body describes /// the message types included and allows setting contract name and version overrides. /// From a1ed27fda6fba96f017739322877415c6d8c3bad Mon Sep 17 00:00:00 2001 From: Tomasz Kurcz Date: Mon, 11 Jul 2022 11:10:35 +0200 Subject: [PATCH 2/9] hackatom: migrate to the `msg` macro --- contracts/hackatom/src/msg.rs | 29 ++++++++++------------------- 1 file changed, 10 insertions(+), 19 deletions(-) diff --git a/contracts/hackatom/src/msg.rs b/contracts/hackatom/src/msg.rs index 11b49916d7..746dc52974 100644 --- a/contracts/hackatom/src/msg.rs +++ b/contracts/hackatom/src/msg.rs @@ -1,11 +1,8 @@ -use cosmwasm_schema::QueryResponses; -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; +use cosmwasm_schema::{msg, QueryResponses}; use cosmwasm_std::{AllBalanceResponse, Binary, Coin}; -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -#[serde(deny_unknown_fields)] +#[msg] pub struct InstantiateMsg { pub verifier: String, pub beneficiary: String, @@ -18,8 +15,7 @@ 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)] +#[msg] pub struct MigrateMsg { pub verifier: String, } @@ -27,8 +23,7 @@ pub struct MigrateMsg { /// 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")] +#[msg] pub enum SudoMsg { StealFunds { recipient: String, @@ -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")] +#[msg] pub enum ExecuteMsg { /// Releasing all funds in the contract to the beneficiary. This is the only "proper" action of this demo contract. Release {}, @@ -67,8 +61,8 @@ pub enum ExecuteMsg { UserErrorsInApiCalls {}, } -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema, QueryResponses)] -#[serde(deny_unknown_fields, rename_all = "snake_case")] +#[msg] +#[derive(QueryResponses)] pub enum QueryMsg { /// returns a human-readable representation of the verifier /// use to ensure query path works in integration tests @@ -88,21 +82,18 @@ pub enum QueryMsg { GetInt {}, } -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -#[serde(deny_unknown_fields)] +#[msg] pub struct VerifierResponse { pub verifier: String, } -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -#[serde(deny_unknown_fields)] +#[msg] 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)] +#[msg] pub struct IntResponse { pub int: u32, } From f40b7be09f0350cf7c45a4828e10ecae1fbd3a68 Mon Sep 17 00:00:00 2001 From: Tomasz Kurcz Date: Tue, 12 Jul 2022 11:29:54 +0200 Subject: [PATCH 3/9] MIGRATING entry for new schema gen --- MIGRATING.md | 82 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) diff --git a/MIGRATING.md b/MIGRATING.md index 335b034ad8..4fe226c631 100644 --- a/MIGRATING.md +++ b/MIGRATING.md @@ -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 -> ? + +- 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. + + 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 types. 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 >= From 8cf39a5985fa4dcf202f1bbf1f7aa7fb3e26aa6c Mon Sep 17 00:00:00 2001 From: Tomasz Kurcz Date: Tue, 12 Jul 2022 11:36:10 +0200 Subject: [PATCH 4/9] schema: rename #[msg] to #[cw_serde] --- contracts/hackatom/src/msg.rs | 18 +++++++++--------- .../schema-derive/src/{msg.rs => cw_serde.rs} | 8 ++++---- packages/schema-derive/src/lib.rs | 6 +++--- packages/schema/src/lib.rs | 2 +- 4 files changed, 17 insertions(+), 17 deletions(-) rename packages/schema-derive/src/{msg.rs => cw_serde.rs} (92%) diff --git a/contracts/hackatom/src/msg.rs b/contracts/hackatom/src/msg.rs index 746dc52974..7d06618f10 100644 --- a/contracts/hackatom/src/msg.rs +++ b/contracts/hackatom/src/msg.rs @@ -1,8 +1,8 @@ -use cosmwasm_schema::{msg, QueryResponses}; +use cosmwasm_schema::{cw_serde, QueryResponses}; use cosmwasm_std::{AllBalanceResponse, Binary, Coin}; -#[msg] +#[cw_serde] pub struct InstantiateMsg { pub verifier: String, pub beneficiary: String, @@ -15,7 +15,7 @@ pub struct InstantiateMsg { /// /// Note that the contract doesn't enforce permissions here, this is done /// by blockchain logic (in the future by blockchain governance) -#[msg] +#[cw_serde] pub struct MigrateMsg { pub verifier: String, } @@ -23,7 +23,7 @@ pub struct MigrateMsg { /// 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 -#[msg] +#[cw_serde] pub enum SudoMsg { StealFunds { recipient: String, @@ -33,7 +33,7 @@ pub enum SudoMsg { // failure modes to help test wasmd, based on this comment // https://github.com/cosmwasm/wasmd/issues/8#issuecomment-576146751 -#[msg] +#[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 {}, @@ -61,7 +61,7 @@ pub enum ExecuteMsg { UserErrorsInApiCalls {}, } -#[msg] +#[cw_serde] #[derive(QueryResponses)] pub enum QueryMsg { /// returns a human-readable representation of the verifier @@ -82,18 +82,18 @@ pub enum QueryMsg { GetInt {}, } -#[msg] +#[cw_serde] pub struct VerifierResponse { pub verifier: String, } -#[msg] +#[cw_serde] pub struct RecurseResponse { /// hashed is the result of running sha256 "work+1" times on the contract's human address pub hashed: Binary, } -#[msg] +#[cw_serde] pub struct IntResponse { pub int: u32, } diff --git a/packages/schema-derive/src/msg.rs b/packages/schema-derive/src/cw_serde.rs similarity index 92% rename from packages/schema-derive/src/msg.rs rename to packages/schema-derive/src/cw_serde.rs index 353e031059..98e430dad1 100644 --- a/packages/schema-derive/src/msg.rs +++ b/packages/schema-derive/src/cw_serde.rs @@ -1,6 +1,6 @@ use syn::{parse_quote, DeriveInput}; -pub fn msg_impl(input: DeriveInput) -> DeriveInput { +pub fn cw_serde_impl(input: DeriveInput) -> DeriveInput { match input.data { syn::Data::Struct(_) => parse_quote! { #[derive( @@ -36,7 +36,7 @@ mod tests { #[test] fn structs() { - let expanded = msg_impl(parse_quote! { + let expanded = cw_serde_impl(parse_quote! { pub struct InstantiateMsg { pub verifier: String, pub beneficiary: String, @@ -64,7 +64,7 @@ mod tests { #[test] fn enums() { - let expanded = msg_impl(parse_quote! { + let expanded = cw_serde_impl(parse_quote! { pub enum SudoMsg { StealFunds { recipient: String, @@ -97,7 +97,7 @@ mod tests { #[test] #[should_panic(expected = "unions are not supported")] fn unions() { - msg_impl(parse_quote! { + cw_serde_impl(parse_quote! { pub union SudoMsg { x: u32, y: u32, diff --git a/packages/schema-derive/src/lib.rs b/packages/schema-derive/src/lib.rs index 3d00645335..4bb0f65182 100644 --- a/packages/schema-derive/src/lib.rs +++ b/packages/schema-derive/src/lib.rs @@ -1,5 +1,5 @@ mod generate_api; -mod msg; +mod cw_serde; mod query_responses; use quote::ToTokens; @@ -33,13 +33,13 @@ pub fn generate_api(input: proc_macro::TokenStream) -> proc_macro::TokenStream { } #[proc_macro_attribute] -pub fn msg( +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 = msg::msg_impl(input).into_token_stream(); + let expanded = cw_serde::cw_serde_impl(input).into_token_stream(); proc_macro::TokenStream::from(expanded) } diff --git a/packages/schema/src/lib.rs b/packages/schema/src/lib.rs index 89ad5da5aa..e30070cc58 100644 --- a/packages/schema/src/lib.rs +++ b/packages/schema/src/lib.rs @@ -10,6 +10,7 @@ pub use query_response::QueryResponses; pub use remove::remove_schemas; // Re-exports +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. /// @@ -36,7 +37,6 @@ pub use remove::remove_schemas; /// }.render(); /// ``` pub use cosmwasm_schema_derive::generate_api; -pub use cosmwasm_schema_derive::msg; /// Takes care of generating the interface description file for a contract. The body describes /// the message types included and allows setting contract name and version overrides. /// From 1e05e7eeac406b4e9f15e0700f8997a7baf0c7f6 Mon Sep 17 00:00:00 2001 From: Tomasz Kurcz Date: Tue, 12 Jul 2022 11:47:28 +0200 Subject: [PATCH 5/9] schema: add documentation for `cw_serde` --- packages/schema/src/lib.rs | 38 ++++++++++++++++++++++++++++++-------- 1 file changed, 30 insertions(+), 8 deletions(-) diff --git a/packages/schema/src/lib.rs b/packages/schema/src/lib.rs index e30070cc58..09001b9b06 100644 --- a/packages/schema/src/lib.rs +++ b/packages/schema/src/lib.rs @@ -10,6 +10,30 @@ 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)] +/// 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. @@ -21,13 +45,12 @@ pub use cosmwasm_schema_derive::cw_serde; /// /// # 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! { @@ -53,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! { From 35ab55dcad5d2d5c6bccdc5de53485ba5c99342f Mon Sep 17 00:00:00 2001 From: Tomasz Kurcz Date: Tue, 12 Jul 2022 12:02:05 +0200 Subject: [PATCH 6/9] MIGRATING.md: typo --- MIGRATING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MIGRATING.md b/MIGRATING.md index 4fe226c631..6b554a9842 100644 --- a/MIGRATING.md +++ b/MIGRATING.md @@ -49,7 +49,7 @@ major releases of `cosmwasm`. Note that you can also view the ``` Derive `cosmwasm_schema::QueryResponses` for your `QueryMsg` type and annotate - each query with its return types. This lets the interface description file + 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. From d32a94b68bfcc98e36d5cbe187bddd34e3c17a50 Mon Sep 17 00:00:00 2001 From: Tomasz Kurcz Date: Tue, 12 Jul 2022 12:03:53 +0200 Subject: [PATCH 7/9] schema-derive: cargo fmt --- packages/schema-derive/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/schema-derive/src/lib.rs b/packages/schema-derive/src/lib.rs index 4bb0f65182..94e9d831ec 100644 --- a/packages/schema-derive/src/lib.rs +++ b/packages/schema-derive/src/lib.rs @@ -1,5 +1,5 @@ -mod generate_api; mod cw_serde; +mod generate_api; mod query_responses; use quote::ToTokens; From aeec6173b15516de4f9cbfaa7581ce205466f975 Mon Sep 17 00:00:00 2001 From: Tomasz Kurcz Date: Tue, 12 Jul 2022 15:13:16 +0200 Subject: [PATCH 8/9] Update MIGRATING.md with new version number Co-authored-by: Simon Warta <2603011+webmaster128@users.noreply.github.com> --- MIGRATING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MIGRATING.md b/MIGRATING.md index 6b554a9842..36ae992883 100644 --- a/MIGRATING.md +++ b/MIGRATING.md @@ -4,7 +4,7 @@ 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.0.0 -> 1.1.0 - There are changes to how we generate schemas, resulting in less boilerplace maintenance for smart contract devs. The changes are backwards-compatible - From e117fea05869bf1cd46018a810fc01b67e4142d2 Mon Sep 17 00:00:00 2001 From: Tomasz Kurcz Date: Tue, 12 Jul 2022 15:16:40 +0200 Subject: [PATCH 9/9] MIGRATING.md update --- MIGRATING.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/MIGRATING.md b/MIGRATING.md index 36ae992883..7adbf8bf85 100644 --- a/MIGRATING.md +++ b/MIGRATING.md @@ -7,9 +7,8 @@ major releases of `cosmwasm`. Note that you can also view the ## 1.0.0 -> 1.1.0 - 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. + 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`. @@ -86,6 +85,10 @@ major releases of `cosmwasm`. Note that you can also view the } ``` +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 >=