From 1be774cd68decc25fc68ff6178a144bf92975320 Mon Sep 17 00:00:00 2001 From: Tomasz Kurcz Date: Mon, 27 Jun 2022 10:27:38 +0200 Subject: [PATCH 01/41] schema: add a way to tie queries to responses --- Cargo.lock | 1 + contracts/hackatom/Cargo.toml | 2 +- contracts/hackatom/examples/schema.rs | 17 ++++------------- contracts/hackatom/src/msg.rs | 21 +++++++++++++++++++-- packages/schema/src/idl.rs | 4 ++-- packages/schema/src/lib.rs | 2 ++ packages/schema/src/query_response.rs | 7 +++++++ packages/schema/tests/idl.rs | 8 ++------ 8 files changed, 38 insertions(+), 24 deletions(-) create mode 100644 packages/schema/src/query_response.rs diff --git a/Cargo.lock b/Cargo.lock index 0a2d07d386..ab0f95aae3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -853,6 +853,7 @@ dependencies = [ name = "hackatom" version = "0.0.0" dependencies = [ + "cosmwasm-schema", "cosmwasm-std", "rust-argon2", "schemars", diff --git a/contracts/hackatom/Cargo.toml b/contracts/hackatom/Cargo.toml index 2b91ed1308..b5eb85528d 100644 --- a/contracts/hackatom/Cargo.toml +++ b/contracts/hackatom/Cargo.toml @@ -31,6 +31,7 @@ cranelift = ["cosmwasm-vm/cranelift"] backtraces = ["cosmwasm-std/backtraces", "cosmwasm-vm/backtraces"] [dependencies] +cosmwasm-schema = { path = "../../packages/schema" } cosmwasm-std = { path = "../../packages/std", default-features = false, features = ["abort"] } rust-argon2 = "0.8" schemars = "0.8.1" @@ -39,6 +40,5 @@ sha2 = "0.9.1" thiserror = "1.0" [dev-dependencies] -cosmwasm-schema = { path = "../../packages/schema" } cosmwasm-storage = { path = "../../packages/storage", default-features = false } cosmwasm-vm = { path = "../../packages/vm", default-features = false } diff --git a/contracts/hackatom/examples/schema.rs b/contracts/hackatom/examples/schema.rs index 245f5be2c9..b5752f7966 100644 --- a/contracts/hackatom/examples/schema.rs +++ b/contracts/hackatom/examples/schema.rs @@ -1,14 +1,10 @@ -use std::collections::BTreeMap; use std::env::current_dir; use std::fs::{create_dir_all, write}; -use cosmwasm_schema::{export_schema, remove_schemas, schema_for, Api}; -use cosmwasm_std::{AllBalanceResponse, BalanceResponse}; +use cosmwasm_schema::{export_schema, remove_schemas, schema_for, Api, QueryResponses}; +use cosmwasm_std::BalanceResponse; -use hackatom::msg::{ - ExecuteMsg, InstantiateMsg, IntResponse, MigrateMsg, QueryMsg, RecurseResponse, SudoMsg, - VerifierResponse, -}; +use hackatom::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg, SudoMsg, VerifierResponse}; use hackatom::state::State; fn main() { @@ -42,12 +38,7 @@ fn main() { query: Some(schema_for!(QueryMsg)), migrate: Some(schema_for!(MigrateMsg)), sudo: Some(schema_for!(SudoMsg)), - responses: BTreeMap::from([ - ("verifier".to_string(), schema_for!(VerifierResponse)), - ("other_balance".to_string(), schema_for!(AllBalanceResponse)), - ("recurse".to_string(), schema_for!(RecurseResponse)), - ("get_int".to_string(), schema_for!(IntResponse)), - ]), + responses: QueryMsg::query_responses(), } .render(); let json = api.to_string().unwrap(); diff --git a/contracts/hackatom/src/msg.rs b/contracts/hackatom/src/msg.rs index 560483bb74..fdab9a6462 100644 --- a/contracts/hackatom/src/msg.rs +++ b/contracts/hackatom/src/msg.rs @@ -1,7 +1,10 @@ -use schemars::JsonSchema; +use std::collections::BTreeMap; + +use cosmwasm_schema::{schema_for, QueryResponses}; +use schemars::{schema::RootSchema, JsonSchema}; use serde::{Deserialize, Serialize}; -use cosmwasm_std::{Binary, Coin}; +use cosmwasm_std::{AllBalanceResponse, Binary, Coin}; #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] #[serde(deny_unknown_fields)] @@ -83,6 +86,20 @@ pub enum QueryMsg { GetInt {}, } +// TODO: this should really be generated by a nice derive macro +impl QueryResponses for QueryMsg { + fn query_responses() -> BTreeMap<&'static str, RootSchema> { + [ + ("verifier", schema_for!(VerifierResponse)), + ("other_balance", schema_for!(AllBalanceResponse)), + ("recurse", schema_for!(RecurseResponse)), + ("get_int", schema_for!(IntResponse)), + ] + .into_iter() + .collect() + } +} + #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] #[serde(deny_unknown_fields)] pub struct VerifierResponse { diff --git a/packages/schema/src/idl.rs b/packages/schema/src/idl.rs index 470217954b..879c37374d 100644 --- a/packages/schema/src/idl.rs +++ b/packages/schema/src/idl.rs @@ -22,7 +22,7 @@ pub struct Api { pub migrate: Option, pub sudo: Option, /// A mapping of query variants to response types - pub responses: BTreeMap, + pub responses: BTreeMap<&'static str, RootSchema>, } impl Api { @@ -78,7 +78,7 @@ pub struct JsonApi { query: Option, migrate: Option, sudo: Option, - responses: BTreeMap, + responses: BTreeMap<&'static str, RootSchema>, } impl JsonApi { diff --git a/packages/schema/src/lib.rs b/packages/schema/src/lib.rs index dda1abc717..7d4ef180e7 100644 --- a/packages/schema/src/lib.rs +++ b/packages/schema/src/lib.rs @@ -1,10 +1,12 @@ mod casing; mod export; mod idl; +mod query_response; mod remove; pub use export::{export_schema, export_schema_with_title}; pub use idl::{Api, IDL_VERSION}; +pub use query_response::QueryResponses; pub use remove::remove_schemas; // Re-exports diff --git a/packages/schema/src/query_response.rs b/packages/schema/src/query_response.rs new file mode 100644 index 0000000000..02fecabb1d --- /dev/null +++ b/packages/schema/src/query_response.rs @@ -0,0 +1,7 @@ +use std::collections::BTreeMap; + +use schemars::schema::RootSchema; + +pub trait QueryResponses { + fn query_responses() -> BTreeMap<&'static str, RootSchema>; +} diff --git a/packages/schema/tests/idl.rs b/packages/schema/tests/idl.rs index 71253ef4b3..bde5d8842d 100644 --- a/packages/schema/tests/idl.rs +++ b/packages/schema/tests/idl.rs @@ -47,9 +47,7 @@ fn test_basic_structure() { query: Some(schema_for!(QueryMsg)), migrate: Some(schema_for!(MigrateMsg)), sudo: Some(schema_for!(SudoMsg)), - responses: [("balance".to_string(), schema_for!(u128))] - .into_iter() - .collect(), + responses: [("balance", schema_for!(u128))].into_iter().collect(), } .render() .to_string() @@ -91,9 +89,7 @@ fn test_query_responses() { query: Some(schema_for!(QueryMsg)), migrate: None, sudo: None, - responses: [("balance".to_string(), schema_for!(u128))] - .into_iter() - .collect(), + responses: [("balance", schema_for!(u128))].into_iter().collect(), } .render() .to_string() From f793c2416fd153fe5e88ee978df0373e62ce2565 Mon Sep 17 00:00:00 2001 From: Tomasz Kurcz Date: Mon, 27 Jun 2022 12:37:54 +0200 Subject: [PATCH 02/41] schema: derive macro for QueryResponses --- Cargo.lock | 11 ++++ contracts/hackatom/Cargo.lock | 10 ++++ contracts/hackatom/schema/hackatom.json | 79 +------------------------ contracts/hackatom/src/msg.rs | 26 +++----- packages/schema-derive/Cargo.toml | 19 ++++++ packages/schema-derive/src/lib.rs | 42 +++++++++++++ packages/schema/Cargo.toml | 1 + packages/schema/src/query_response.rs | 2 + 8 files changed, 93 insertions(+), 97 deletions(-) create mode 100644 packages/schema-derive/Cargo.toml create mode 100644 packages/schema-derive/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index ab0f95aae3..81c809f507 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -287,6 +287,7 @@ name = "cosmwasm-schema" version = "1.0.0" dependencies = [ "anyhow", + "cosmwasm-schema-derive", "schemars", "semver", "serde", @@ -295,6 +296,16 @@ dependencies = [ "thiserror", ] +[[package]] +name = "cosmwasm-schema-derive" +version = "1.0.0" +dependencies = [ + "cosmwasm-schema", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "cosmwasm-std" version = "1.0.0" diff --git a/contracts/hackatom/Cargo.lock b/contracts/hackatom/Cargo.lock index d63bb8ba38..e9b7339596 100644 --- a/contracts/hackatom/Cargo.lock +++ b/contracts/hackatom/Cargo.lock @@ -190,12 +190,22 @@ dependencies = [ name = "cosmwasm-schema" version = "1.0.0" dependencies = [ + "cosmwasm-schema-derive", "schemars", "serde", "serde_json", "thiserror", ] +[[package]] +name = "cosmwasm-schema-derive" +version = "1.0.0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "cosmwasm-std" version = "1.0.0" diff --git a/contracts/hackatom/schema/hackatom.json b/contracts/hackatom/schema/hackatom.json index f498fe07fc..c37cf958a3 100644 --- a/contracts/hackatom/schema/hackatom.json +++ b/contracts/hackatom/schema/hackatom.json @@ -338,7 +338,7 @@ } }, "responses": { - "get_int": { + "v.ident": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "IntResponse", "type": "object", @@ -353,83 +353,6 @@ } }, "additionalProperties": false - }, - "other_balance": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "AllBalanceResponse", - "type": "object", - "required": [ - "amount" - ], - "properties": { - "amount": { - "description": "Returns all non-zero coins held by this account.", - "type": "array", - "items": { - "$ref": "#/definitions/Coin" - } - } - }, - "definitions": { - "Coin": { - "type": "object", - "required": [ - "amount", - "denom" - ], - "properties": { - "amount": { - "$ref": "#/definitions/Uint128" - }, - "denom": { - "type": "string" - } - } - }, - "Uint128": { - "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", - "type": "string" - } - } - }, - "recurse": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "RecurseResponse", - "type": "object", - "required": [ - "hashed" - ], - "properties": { - "hashed": { - "description": "hashed is the result of running sha256 \"work+1\" times on the contract's human address", - "allOf": [ - { - "$ref": "#/definitions/Binary" - } - ] - } - }, - "additionalProperties": false, - "definitions": { - "Binary": { - "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec", - "type": "string" - } - } - }, - "verifier": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "VerifierResponse", - "type": "object", - "required": [ - "verifier" - ], - "properties": { - "verifier": { - "type": "string" - } - }, - "additionalProperties": false } } } diff --git a/contracts/hackatom/src/msg.rs b/contracts/hackatom/src/msg.rs index fdab9a6462..11b49916d7 100644 --- a/contracts/hackatom/src/msg.rs +++ b/contracts/hackatom/src/msg.rs @@ -1,7 +1,5 @@ -use std::collections::BTreeMap; - -use cosmwasm_schema::{schema_for, QueryResponses}; -use schemars::{schema::RootSchema, JsonSchema}; +use cosmwasm_schema::QueryResponses; +use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use cosmwasm_std::{AllBalanceResponse, Binary, Coin}; @@ -69,37 +67,27 @@ pub enum ExecuteMsg { UserErrorsInApiCalls {}, } -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema, QueryResponses)] #[serde(deny_unknown_fields, rename_all = "snake_case")] pub enum QueryMsg { /// returns a human-readable representation of the verifier /// use to ensure query path works in integration tests + #[returns(VerifierResponse)] Verifier {}, /// This returns cosmwasm_std::AllBalanceResponse to demo use of the querier + #[returns(AllBalanceResponse)] OtherBalance { address: String }, /// Recurse will execute a query into itself up to depth-times and return /// Each step of the recursion may perform some extra work to test gas metering /// (`work` rounds of sha256 on contract). /// Now that we have Env, we can auto-calculate the address to recurse into + #[returns(RecurseResponse)] Recurse { depth: u32, work: u32 }, /// GetInt returns a hardcoded u32 value + #[returns(IntResponse)] GetInt {}, } -// TODO: this should really be generated by a nice derive macro -impl QueryResponses for QueryMsg { - fn query_responses() -> BTreeMap<&'static str, RootSchema> { - [ - ("verifier", schema_for!(VerifierResponse)), - ("other_balance", schema_for!(AllBalanceResponse)), - ("recurse", schema_for!(RecurseResponse)), - ("get_int", schema_for!(IntResponse)), - ] - .into_iter() - .collect() - } -} - #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] #[serde(deny_unknown_fields)] pub struct VerifierResponse { diff --git a/packages/schema-derive/Cargo.toml b/packages/schema-derive/Cargo.toml new file mode 100644 index 0000000000..585deca231 --- /dev/null +++ b/packages/schema-derive/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "cosmwasm-schema-derive" +version = "1.0.0" +author = "Tomasz Kurcz " +edition = "2021" +description = "Derive macros for cosmwasm-schema" +repository = "https://github.com/CosmWasm/cosmwasm/tree/main/packages/schema" +license = "Apache-2.0" + +[dependencies] +proc-macro2 = "1" +quote = "1" +syn = { version = "1", features = ["full", "printing"] } + +[dev-dependencies] +cosmwasm-schema = { version = "=1.0.0", path = "../schema" } + +[lib] +proc-macro = true diff --git a/packages/schema-derive/src/lib.rs b/packages/schema-derive/src/lib.rs new file mode 100644 index 0000000000..cb059d4e57 --- /dev/null +++ b/packages/schema-derive/src/lib.rs @@ -0,0 +1,42 @@ +use proc_macro2::TokenStream; +use quote::quote; +use syn::{parse_macro_input, ItemEnum, Type, Variant}; + +/// Extract the query -> response mapping out of an enum variant. +fn parse_query(v: Variant) -> TokenStream { + let query = stringify!(v.ident); + let response_ty: Type = v + .attrs + .iter() + .find(|a| a.path.get_ident().unwrap() == "returns") + .unwrap() + .parse_args() + .unwrap(); + + quote! { + (#query, cosmwasm_schema::schema_for!(#response_ty)) + } +} + +#[proc_macro_derive(QueryResponses, attributes(returns))] +pub fn query_responses_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream { + let input = parse_macro_input!(input as ItemEnum); + let ident = input.ident; + + let responses = input.variants.into_iter().map(parse_query); + + let expanded = quote! { + #[automatically_derived] + impl cosmwasm_schema::QueryResponses for #ident { + fn query_responses() -> std::collections::BTreeMap<&'static str, schemars::schema::RootSchema> { + [ + #( #responses, )* + ] + .into_iter() + .collect() + } + } + }; + + proc_macro::TokenStream::from(expanded) +} diff --git a/packages/schema/Cargo.toml b/packages/schema/Cargo.toml index f26cf38d97..ed43403888 100644 --- a/packages/schema/Cargo.toml +++ b/packages/schema/Cargo.toml @@ -8,6 +8,7 @@ repository = "https://github.com/CosmWasm/cosmwasm/tree/main/packages/schema" license = "Apache-2.0" [dependencies] +cosmwasm-schema-derive = { version = "=1.0.0", path = "../schema-derive" } schemars = "0.8.1" serde = "1.0" serde_json = "1.0" diff --git a/packages/schema/src/query_response.rs b/packages/schema/src/query_response.rs index 02fecabb1d..c2c6ccd26c 100644 --- a/packages/schema/src/query_response.rs +++ b/packages/schema/src/query_response.rs @@ -2,6 +2,8 @@ use std::collections::BTreeMap; use schemars::schema::RootSchema; +pub use cosmwasm_schema_derive::QueryResponses; + pub trait QueryResponses { fn query_responses() -> BTreeMap<&'static str, RootSchema>; } From 21b3ef383e285acf76c612a04c9de5b1e0ad4c6f Mon Sep 17 00:00:00 2001 From: Tomasz Kurcz Date: Mon, 27 Jun 2022 12:39:20 +0200 Subject: [PATCH 03/41] schema-derive: don't gen trait when compiling to wasm --- packages/schema-derive/src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/schema-derive/src/lib.rs b/packages/schema-derive/src/lib.rs index cb059d4e57..e1dc993d3e 100644 --- a/packages/schema-derive/src/lib.rs +++ b/packages/schema-derive/src/lib.rs @@ -27,6 +27,7 @@ pub fn query_responses_derive(input: proc_macro::TokenStream) -> proc_macro::Tok let expanded = quote! { #[automatically_derived] + #[cfg(not(target_arch = "wasm32"))] impl cosmwasm_schema::QueryResponses for #ident { fn query_responses() -> std::collections::BTreeMap<&'static str, schemars::schema::RootSchema> { [ From 27579ed533348df215d7587a3ca8c73cd15d328e Mon Sep 17 00:00:00 2001 From: Tomasz Kurcz Date: Mon, 27 Jun 2022 12:47:13 +0200 Subject: [PATCH 04/41] schema-derive: fix query names in api file --- contracts/hackatom/schema/hackatom.json | 79 ++++++++++++++++++++++++- packages/schema-derive/src/lib.rs | 13 +++- 2 files changed, 90 insertions(+), 2 deletions(-) diff --git a/contracts/hackatom/schema/hackatom.json b/contracts/hackatom/schema/hackatom.json index c37cf958a3..f498fe07fc 100644 --- a/contracts/hackatom/schema/hackatom.json +++ b/contracts/hackatom/schema/hackatom.json @@ -338,7 +338,7 @@ } }, "responses": { - "v.ident": { + "get_int": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "IntResponse", "type": "object", @@ -353,6 +353,83 @@ } }, "additionalProperties": false + }, + "other_balance": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "AllBalanceResponse", + "type": "object", + "required": [ + "amount" + ], + "properties": { + "amount": { + "description": "Returns all non-zero coins held by this account.", + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + } + }, + "definitions": { + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } + }, + "recurse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "RecurseResponse", + "type": "object", + "required": [ + "hashed" + ], + "properties": { + "hashed": { + "description": "hashed is the result of running sha256 \"work+1\" times on the contract's human address", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + } + }, + "additionalProperties": false, + "definitions": { + "Binary": { + "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec", + "type": "string" + } + } + }, + "verifier": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "VerifierResponse", + "type": "object", + "required": [ + "verifier" + ], + "properties": { + "verifier": { + "type": "string" + } + }, + "additionalProperties": false } } } diff --git a/packages/schema-derive/src/lib.rs b/packages/schema-derive/src/lib.rs index e1dc993d3e..b2cd1feec0 100644 --- a/packages/schema-derive/src/lib.rs +++ b/packages/schema-derive/src/lib.rs @@ -4,7 +4,7 @@ use syn::{parse_macro_input, ItemEnum, Type, Variant}; /// Extract the query -> response mapping out of an enum variant. fn parse_query(v: Variant) -> TokenStream { - let query = stringify!(v.ident); + let query = to_snake_case(&v.ident.to_string()); let response_ty: Type = v .attrs .iter() @@ -18,6 +18,17 @@ fn parse_query(v: Variant) -> TokenStream { } } +fn to_snake_case(input: &str) -> String { + let mut snake = String::new(); + for (i, ch) in input.char_indices() { + if i > 0 && ch.is_uppercase() { + snake.push('_'); + } + snake.push(ch.to_ascii_lowercase()); + } + snake +} + #[proc_macro_derive(QueryResponses, attributes(returns))] pub fn query_responses_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream { let input = parse_macro_input!(input as ItemEnum); From 1612747e77e2ec0db149383d481bc7dac179de4f Mon Sep 17 00:00:00 2001 From: Tomasz Kurcz Date: Mon, 27 Jun 2022 12:52:35 +0200 Subject: [PATCH 05/41] Update contracts --- contracts/burner/Cargo.lock | 10 ++++++++++ contracts/crypto-verify/Cargo.lock | 10 ++++++++++ contracts/floaty/Cargo.lock | 10 ++++++++++ contracts/ibc-reflect-send/Cargo.lock | 10 ++++++++++ contracts/ibc-reflect/Cargo.lock | 10 ++++++++++ contracts/queue/Cargo.lock | 10 ++++++++++ contracts/reflect/Cargo.lock | 10 ++++++++++ contracts/staking/Cargo.lock | 10 ++++++++++ 8 files changed, 80 insertions(+) diff --git a/contracts/burner/Cargo.lock b/contracts/burner/Cargo.lock index 5ac61e77f3..c7b5cf9480 100644 --- a/contracts/burner/Cargo.lock +++ b/contracts/burner/Cargo.lock @@ -172,12 +172,22 @@ dependencies = [ name = "cosmwasm-schema" version = "1.0.0" dependencies = [ + "cosmwasm-schema-derive", "schemars", "serde", "serde_json", "thiserror", ] +[[package]] +name = "cosmwasm-schema-derive" +version = "1.0.0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "cosmwasm-std" version = "1.0.0" diff --git a/contracts/crypto-verify/Cargo.lock b/contracts/crypto-verify/Cargo.lock index a90eb3d7ee..5c1fede9c2 100644 --- a/contracts/crypto-verify/Cargo.lock +++ b/contracts/crypto-verify/Cargo.lock @@ -174,12 +174,22 @@ dependencies = [ name = "cosmwasm-schema" version = "1.0.0" dependencies = [ + "cosmwasm-schema-derive", "schemars", "serde", "serde_json", "thiserror", ] +[[package]] +name = "cosmwasm-schema-derive" +version = "1.0.0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "cosmwasm-std" version = "1.0.0" diff --git a/contracts/floaty/Cargo.lock b/contracts/floaty/Cargo.lock index 7e8c82cdc3..834452bdf6 100644 --- a/contracts/floaty/Cargo.lock +++ b/contracts/floaty/Cargo.lock @@ -161,12 +161,22 @@ dependencies = [ name = "cosmwasm-schema" version = "1.0.0" dependencies = [ + "cosmwasm-schema-derive", "schemars", "serde", "serde_json", "thiserror", ] +[[package]] +name = "cosmwasm-schema-derive" +version = "1.0.0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "cosmwasm-std" version = "1.0.0" diff --git a/contracts/ibc-reflect-send/Cargo.lock b/contracts/ibc-reflect-send/Cargo.lock index aeca46ae94..5b9b188ac9 100644 --- a/contracts/ibc-reflect-send/Cargo.lock +++ b/contracts/ibc-reflect-send/Cargo.lock @@ -161,12 +161,22 @@ dependencies = [ name = "cosmwasm-schema" version = "1.0.0" dependencies = [ + "cosmwasm-schema-derive", "schemars", "serde", "serde_json", "thiserror", ] +[[package]] +name = "cosmwasm-schema-derive" +version = "1.0.0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "cosmwasm-std" version = "1.0.0" diff --git a/contracts/ibc-reflect/Cargo.lock b/contracts/ibc-reflect/Cargo.lock index 808e6aa35a..90a3618300 100644 --- a/contracts/ibc-reflect/Cargo.lock +++ b/contracts/ibc-reflect/Cargo.lock @@ -161,12 +161,22 @@ dependencies = [ name = "cosmwasm-schema" version = "1.0.0" dependencies = [ + "cosmwasm-schema-derive", "schemars", "serde", "serde_json", "thiserror", ] +[[package]] +name = "cosmwasm-schema-derive" +version = "1.0.0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "cosmwasm-std" version = "1.0.0" diff --git a/contracts/queue/Cargo.lock b/contracts/queue/Cargo.lock index f056c426d9..bd2edf8b08 100644 --- a/contracts/queue/Cargo.lock +++ b/contracts/queue/Cargo.lock @@ -161,12 +161,22 @@ dependencies = [ name = "cosmwasm-schema" version = "1.0.0" dependencies = [ + "cosmwasm-schema-derive", "schemars", "serde", "serde_json", "thiserror", ] +[[package]] +name = "cosmwasm-schema-derive" +version = "1.0.0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "cosmwasm-std" version = "1.0.0" diff --git a/contracts/reflect/Cargo.lock b/contracts/reflect/Cargo.lock index 2b91e890fa..8739c04c78 100644 --- a/contracts/reflect/Cargo.lock +++ b/contracts/reflect/Cargo.lock @@ -161,12 +161,22 @@ dependencies = [ name = "cosmwasm-schema" version = "1.0.0" dependencies = [ + "cosmwasm-schema-derive", "schemars", "serde", "serde_json", "thiserror", ] +[[package]] +name = "cosmwasm-schema-derive" +version = "1.0.0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "cosmwasm-std" version = "1.0.0" diff --git a/contracts/staking/Cargo.lock b/contracts/staking/Cargo.lock index c1ce84f2fd..200549bb67 100644 --- a/contracts/staking/Cargo.lock +++ b/contracts/staking/Cargo.lock @@ -161,12 +161,22 @@ dependencies = [ name = "cosmwasm-schema" version = "1.0.0" dependencies = [ + "cosmwasm-schema-derive", "schemars", "serde", "serde_json", "thiserror", ] +[[package]] +name = "cosmwasm-schema-derive" +version = "1.0.0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "cosmwasm-std" version = "1.0.0" From 94d1513d6bf0e87e5b1350708a6e687da1ded64e Mon Sep 17 00:00:00 2001 From: Tomasz Kurcz Date: Mon, 27 Jun 2022 15:38:58 +0200 Subject: [PATCH 06/41] update codecov.yaml with schema-derive --- codecov.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/codecov.yml b/codecov.yml index 0351ef073b..d873594098 100644 --- a/codecov.yml +++ b/codecov.yml @@ -25,6 +25,7 @@ flags: cosmwasm-schema: paths: - packages/schema/ + - packages/schema-derive/ cosmwasm-std: paths: - packages/std/ From 82d06d90a6a5c35180e23565f7a02edd4078c71d Mon Sep 17 00:00:00 2001 From: Tomasz Kurcz Date: Mon, 27 Jun 2022 15:39:38 +0200 Subject: [PATCH 07/41] schema-derive: remove unnecessary dep --- Cargo.lock | 1 - packages/schema-derive/Cargo.toml | 3 --- 2 files changed, 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 81c809f507..ef3d6b1290 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -300,7 +300,6 @@ dependencies = [ name = "cosmwasm-schema-derive" version = "1.0.0" dependencies = [ - "cosmwasm-schema", "proc-macro2", "quote", "syn", diff --git a/packages/schema-derive/Cargo.toml b/packages/schema-derive/Cargo.toml index 585deca231..a14e8abcfa 100644 --- a/packages/schema-derive/Cargo.toml +++ b/packages/schema-derive/Cargo.toml @@ -12,8 +12,5 @@ proc-macro2 = "1" quote = "1" syn = { version = "1", features = ["full", "printing"] } -[dev-dependencies] -cosmwasm-schema = { version = "=1.0.0", path = "../schema" } - [lib] proc-macro = true From e279d8a480218bc46f048826b7dba3f1dab2b32b Mon Sep 17 00:00:00 2001 From: Tomasz Kurcz Date: Mon, 27 Jun 2022 15:39:47 +0200 Subject: [PATCH 08/41] circleci: build schema-derive --- .circleci/config.yml | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/.circleci/config.yml b/.circleci/config.yml index 7fbee3bedb..0b4e3aaa25 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -10,6 +10,7 @@ workflows: - arm64 - package_crypto - package_schema + - package_schema_derive - package_std - package_storage - package_vm @@ -186,6 +187,33 @@ jobs: - target/debug/deps key: cargocache-v2-package_schema-rust:1.56.1-{{ checksum "Cargo.lock" }} + package_schema_derive: + docker: + - image: rust:1.56.1 + steps: + - checkout + - run: + name: Version information + command: rustc --version; cargo --version; rustup --version; rustup target list --installed + - restore_cache: + keys: + - cargocache-v2-package_schema_derive-rust:1.56.1-{{ checksum "Cargo.lock" }} + - run: + name: Build + working_directory: ~/project/packages/schema-derive + command: cargo build --locked + - run: + name: Run tests + working_directory: ~/project/packages/schema-derive + command: cargo test --locked + - save_cache: + paths: + - /usr/local/cargo/registry + - target/debug/.fingerprint + - target/debug/build + - target/debug/deps + key: cargocache-v2-package_schema_derive-rust:1.56.1-{{ checksum "Cargo.lock" }} + package_std: docker: - image: rust:1.56.1 From 1a66e1703034a5dbf3e13aa2ff90e4173ae1958f Mon Sep 17 00:00:00 2001 From: Tomasz Kurcz Date: Mon, 27 Jun 2022 15:54:08 +0200 Subject: [PATCH 09/41] schema-derive: add some tests --- packages/schema-derive/src/lib.rs | 37 +++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/packages/schema-derive/src/lib.rs b/packages/schema-derive/src/lib.rs index b2cd1feec0..010e3c138b 100644 --- a/packages/schema-derive/src/lib.rs +++ b/packages/schema-derive/src/lib.rs @@ -19,6 +19,7 @@ fn parse_query(v: Variant) -> TokenStream { } fn to_snake_case(input: &str) -> String { + // this was stolen from serde for consistent behavior let mut snake = String::new(); for (i, ch) in input.char_indices() { if i > 0 && ch.is_uppercase() { @@ -52,3 +53,39 @@ pub fn query_responses_derive(input: proc_macro::TokenStream) -> proc_macro::Tok proc_macro::TokenStream::from(expanded) } + +#[cfg(tests)] +mod tests { + use syn::parse_quote; + + use super::*; + + #[test] + fn parse_query() { + let variant = parse_quote! { + #[returns(Foo)] + GetFoo {}, + }; + + assert_eq!( + parse_query(variant), + quote! { ("get_foo", cosmwasm_schema::schema_for!(Foo)) } + ); + + let variant = parse_quote! { + #[returns(some_crate::Foo)] + GetFoo {}, + }; + + assert_eq!( + parse_query(variant), + quote! { ("get_foo", cosmwasm_schema::schema_for!(some_crate::Foo)) } + ); + } + + #[test] + fn to_snake_case() { + assert_eq!(to_snake_case("SnakeCase"), "snake_case"); + assert_eq!(to_snake_case("Wasm123AndCo"), "wasm123_and_co"); + } +} From a5605f6b453d7b2ead7d50718d9fedad471d226b Mon Sep 17 00:00:00 2001 From: Tomasz Kurcz Date: Wed, 29 Jun 2022 15:27:31 +0200 Subject: [PATCH 10/41] schema-derive: style Co-authored-by: Simon Warta <2603011+webmaster128@users.noreply.github.com> --- packages/schema-derive/src/lib.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/schema-derive/src/lib.rs b/packages/schema-derive/src/lib.rs index 010e3c138b..98c92d1004 100644 --- a/packages/schema-derive/src/lib.rs +++ b/packages/schema-derive/src/lib.rs @@ -42,11 +42,9 @@ pub fn query_responses_derive(input: proc_macro::TokenStream) -> proc_macro::Tok #[cfg(not(target_arch = "wasm32"))] impl cosmwasm_schema::QueryResponses for #ident { fn query_responses() -> std::collections::BTreeMap<&'static str, schemars::schema::RootSchema> { - [ + std::collections::BTreeMap::from([ #( #responses, )* - ] - .into_iter() - .collect() + ]) } } }; From a955db025fd8ed724bc37302a9ee840f72ac6f01 Mon Sep 17 00:00:00 2001 From: Tomasz Kurcz Date: Wed, 29 Jun 2022 15:43:18 +0200 Subject: [PATCH 11/41] schema: owned strings only for simplicity --- packages/schema-derive/src/lib.rs | 4 ++-- packages/schema/src/idl.rs | 4 ++-- packages/schema/src/query_response.rs | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/schema-derive/src/lib.rs b/packages/schema-derive/src/lib.rs index 98c92d1004..197a60a0ce 100644 --- a/packages/schema-derive/src/lib.rs +++ b/packages/schema-derive/src/lib.rs @@ -14,7 +14,7 @@ fn parse_query(v: Variant) -> TokenStream { .unwrap(); quote! { - (#query, cosmwasm_schema::schema_for!(#response_ty)) + (#query.to_string(), cosmwasm_schema::schema_for!(#response_ty)) } } @@ -41,7 +41,7 @@ pub fn query_responses_derive(input: proc_macro::TokenStream) -> proc_macro::Tok #[automatically_derived] #[cfg(not(target_arch = "wasm32"))] impl cosmwasm_schema::QueryResponses for #ident { - fn query_responses() -> std::collections::BTreeMap<&'static str, schemars::schema::RootSchema> { + fn query_responses() -> std::collections::BTreeMap { std::collections::BTreeMap::from([ #( #responses, )* ]) diff --git a/packages/schema/src/idl.rs b/packages/schema/src/idl.rs index 879c37374d..470217954b 100644 --- a/packages/schema/src/idl.rs +++ b/packages/schema/src/idl.rs @@ -22,7 +22,7 @@ pub struct Api { pub migrate: Option, pub sudo: Option, /// A mapping of query variants to response types - pub responses: BTreeMap<&'static str, RootSchema>, + pub responses: BTreeMap, } impl Api { @@ -78,7 +78,7 @@ pub struct JsonApi { query: Option, migrate: Option, sudo: Option, - responses: BTreeMap<&'static str, RootSchema>, + responses: BTreeMap, } impl JsonApi { diff --git a/packages/schema/src/query_response.rs b/packages/schema/src/query_response.rs index c2c6ccd26c..482b490b94 100644 --- a/packages/schema/src/query_response.rs +++ b/packages/schema/src/query_response.rs @@ -5,5 +5,5 @@ use schemars::schema::RootSchema; pub use cosmwasm_schema_derive::QueryResponses; pub trait QueryResponses { - fn query_responses() -> BTreeMap<&'static str, RootSchema>; + fn query_responses() -> BTreeMap; } From b495a6ca314d61a86c97b419bd1b17f8e8867cad Mon Sep 17 00:00:00 2001 From: Tomasz Kurcz Date: Wed, 29 Jun 2022 15:45:54 +0200 Subject: [PATCH 12/41] schema: rename query_responses -> response_schemas --- contracts/hackatom/examples/schema.rs | 2 +- packages/schema-derive/src/lib.rs | 2 +- packages/schema/src/query_response.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/contracts/hackatom/examples/schema.rs b/contracts/hackatom/examples/schema.rs index b5752f7966..0cdde9a978 100644 --- a/contracts/hackatom/examples/schema.rs +++ b/contracts/hackatom/examples/schema.rs @@ -38,7 +38,7 @@ fn main() { query: Some(schema_for!(QueryMsg)), migrate: Some(schema_for!(MigrateMsg)), sudo: Some(schema_for!(SudoMsg)), - responses: QueryMsg::query_responses(), + responses: QueryMsg::response_schemas(), } .render(); let json = api.to_string().unwrap(); diff --git a/packages/schema-derive/src/lib.rs b/packages/schema-derive/src/lib.rs index 197a60a0ce..73723fc282 100644 --- a/packages/schema-derive/src/lib.rs +++ b/packages/schema-derive/src/lib.rs @@ -41,7 +41,7 @@ pub fn query_responses_derive(input: proc_macro::TokenStream) -> proc_macro::Tok #[automatically_derived] #[cfg(not(target_arch = "wasm32"))] impl cosmwasm_schema::QueryResponses for #ident { - fn query_responses() -> std::collections::BTreeMap { + fn response_schemas() -> std::collections::BTreeMap { std::collections::BTreeMap::from([ #( #responses, )* ]) diff --git a/packages/schema/src/query_response.rs b/packages/schema/src/query_response.rs index 482b490b94..cb585af1dc 100644 --- a/packages/schema/src/query_response.rs +++ b/packages/schema/src/query_response.rs @@ -5,5 +5,5 @@ use schemars::schema::RootSchema; pub use cosmwasm_schema_derive::QueryResponses; pub trait QueryResponses { - fn query_responses() -> BTreeMap; + fn response_schemas() -> BTreeMap; } From 4a3caa007648e723163dfbe9fd33a6d3e0a66e47 Mon Sep 17 00:00:00 2001 From: Tomasz Kurcz Date: Wed, 29 Jun 2022 15:54:55 +0200 Subject: [PATCH 13/41] schema: update IDL tests --- packages/schema/tests/idl.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/schema/tests/idl.rs b/packages/schema/tests/idl.rs index bde5d8842d..d2897fbe99 100644 --- a/packages/schema/tests/idl.rs +++ b/packages/schema/tests/idl.rs @@ -1,4 +1,4 @@ -use std::collections::HashMap; +use std::collections::{BTreeMap, HashMap}; use cosmwasm_schema::{schema_for, Api, IDL_VERSION}; use schemars::JsonSchema; @@ -47,7 +47,7 @@ fn test_basic_structure() { query: Some(schema_for!(QueryMsg)), migrate: Some(schema_for!(MigrateMsg)), sudo: Some(schema_for!(SudoMsg)), - responses: [("balance", schema_for!(u128))].into_iter().collect(), + responses: BTreeMap::from([("balance".to_string(), schema_for!(u128))]), } .render() .to_string() @@ -89,7 +89,7 @@ fn test_query_responses() { query: Some(schema_for!(QueryMsg)), migrate: None, sudo: None, - responses: [("balance", schema_for!(u128))].into_iter().collect(), + responses: BTreeMap::from([("balance".to_string(), schema_for!(u128))]), } .render() .to_string() From 17b6308e119813e77367eff4dbd98585eee308db Mon Sep 17 00:00:00 2001 From: Tomasz Kurcz Date: Wed, 29 Jun 2022 18:46:00 +0200 Subject: [PATCH 14/41] schema-derive: more tests --- Cargo.lock | 14 +++- contracts/hackatom/Cargo.lock | 14 +++- packages/schema-derive/Cargo.toml | 3 +- packages/schema-derive/src/lib.rs | 112 +++++++++++++++++++++++++----- 4 files changed, 124 insertions(+), 19 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ef3d6b1290..1eb2c8009f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -302,6 +302,7 @@ version = "1.0.0" dependencies = [ "proc-macro2", "quote", + "serde_derive_internals 0.26.0", "syn", ] @@ -1539,7 +1540,7 @@ checksum = "41ae4dce13e8614c46ac3c38ef1c0d668b101df6ac39817aebdaa26642ddae9b" dependencies = [ "proc-macro2", "quote", - "serde_derive_internals", + "serde_derive_internals 0.25.0", "syn", ] @@ -1633,6 +1634,17 @@ dependencies = [ "syn", ] +[[package]] +name = "serde_derive_internals" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85bf8229e7920a9f636479437026331ce11aa132b4dde37d121944a44d6e5f3c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "serde_json" version = "1.0.81" diff --git a/contracts/hackatom/Cargo.lock b/contracts/hackatom/Cargo.lock index e9b7339596..94108cf448 100644 --- a/contracts/hackatom/Cargo.lock +++ b/contracts/hackatom/Cargo.lock @@ -203,6 +203,7 @@ version = "1.0.0" dependencies = [ "proc-macro2", "quote", + "serde_derive_internals 0.26.0", "syn", ] @@ -1236,7 +1237,7 @@ checksum = "4a9ea2a613fe4cd7118b2bb101a25d8ae6192e1975179b67b2f17afd11e70ac8" dependencies = [ "proc-macro2", "quote", - "serde_derive_internals", + "serde_derive_internals 0.25.0", "syn", ] @@ -1314,6 +1315,17 @@ dependencies = [ "syn", ] +[[package]] +name = "serde_derive_internals" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85bf8229e7920a9f636479437026331ce11aa132b4dde37d121944a44d6e5f3c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "serde_json" version = "1.0.64" diff --git a/packages/schema-derive/Cargo.toml b/packages/schema-derive/Cargo.toml index a14e8abcfa..8c7359e9cf 100644 --- a/packages/schema-derive/Cargo.toml +++ b/packages/schema-derive/Cargo.toml @@ -10,7 +10,8 @@ license = "Apache-2.0" [dependencies] proc-macro2 = "1" quote = "1" -syn = { version = "1", features = ["full", "printing"] } +serde_derive_internals = "0.26.0" +syn = { version = "1", features = ["full", "printing", "extra-traits"] } [lib] proc-macro = true diff --git a/packages/schema-derive/src/lib.rs b/packages/schema-derive/src/lib.rs index 73723fc282..a6b66af3a8 100644 --- a/packages/schema-derive/src/lib.rs +++ b/packages/schema-derive/src/lib.rs @@ -1,9 +1,8 @@ -use proc_macro2::TokenStream; -use quote::quote; -use syn::{parse_macro_input, ItemEnum, Type, Variant}; +use quote::ToTokens; +use syn::{parse_macro_input, parse_quote, ExprTuple, ItemEnum, ItemImpl, Type, Variant}; /// Extract the query -> response mapping out of an enum variant. -fn parse_query(v: Variant) -> TokenStream { +fn parse_query(v: Variant) -> ExprTuple { let query = to_snake_case(&v.ident.to_string()); let response_ty: Type = v .attrs @@ -13,7 +12,7 @@ fn parse_query(v: Variant) -> TokenStream { .parse_args() .unwrap(); - quote! { + parse_quote! { (#query.to_string(), cosmwasm_schema::schema_for!(#response_ty)) } } @@ -33,11 +32,18 @@ fn to_snake_case(input: &str) -> String { #[proc_macro_derive(QueryResponses, attributes(returns))] pub fn query_responses_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream { let input = parse_macro_input!(input as ItemEnum); + + let expanded = query_responses_derive_impl(input).into_token_stream(); + + proc_macro::TokenStream::from(expanded) +} + +fn query_responses_derive_impl(input: ItemEnum) -> ItemImpl { let ident = input.ident; let responses = input.variants.into_iter().map(parse_query); - let expanded = quote! { + parse_quote! { #[automatically_derived] #[cfg(not(target_arch = "wasm32"))] impl cosmwasm_schema::QueryResponses for #ident { @@ -47,43 +53,117 @@ pub fn query_responses_derive(input: proc_macro::TokenStream) -> proc_macro::Tok ]) } } - }; - - proc_macro::TokenStream::from(expanded) + } } -#[cfg(tests)] +#[cfg(test)] mod tests { use syn::parse_quote; use super::*; #[test] - fn parse_query() { + fn happy_path() { + let input: ItemEnum = parse_quote! { + #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema, QueryResponses)] + #[serde(rename_all = "snake_case")] + pub enum QueryMsg { + #[returns(SomeType)] + Balance {}, + #[returns(some_crate::AnotherType)] + Supply {}, + } + }; + + assert_eq!( + query_responses_derive_impl(input), + parse_quote! { + #[automatically_derived] + #[cfg(not(target_arch = "wasm32"))] + impl cosmwasm_schema::QueryResponses for QueryMsg { + fn response_schemas() -> std::collections::BTreeMap { + std::collections::BTreeMap::from([ + ("balance".to_string(), cosmwasm_schema::schema_for!(SomeType)), + ("supply".to_string(), cosmwasm_schema::schema_for!(some_crate::AnotherType)), + ]) + } + } + } + ); + } + + #[test] + fn parse_query_works() { let variant = parse_quote! { #[returns(Foo)] - GetFoo {}, + GetFoo {} }; assert_eq!( parse_query(variant), - quote! { ("get_foo", cosmwasm_schema::schema_for!(Foo)) } + parse_quote! { + ("get_foo".to_string(), cosmwasm_schema::schema_for!(Foo)) + } ); let variant = parse_quote! { #[returns(some_crate::Foo)] - GetFoo {}, + GetFoo {} }; assert_eq!( parse_query(variant), - quote! { ("get_foo", cosmwasm_schema::schema_for!(some_crate::Foo)) } + parse_quote! { ("get_foo".to_string(), cosmwasm_schema::schema_for!(some_crate::Foo)) } ); } #[test] - fn to_snake_case() { + fn to_snake_case_works() { assert_eq!(to_snake_case("SnakeCase"), "snake_case"); assert_eq!(to_snake_case("Wasm123AndCo"), "wasm123_and_co"); } + + #[test] + #[should_panic] + fn panic_if_queries_are_not_snake_case1() { + let input: ItemEnum = parse_quote! { + #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema, QueryResponses)] + pub enum QueryMsg { + #[returns(SomeType)] + Balance {}, + } + }; + + query_responses_derive_impl(input); + } + + #[test] + #[should_panic] + fn panic_if_queries_are_not_snake_case2() { + let input: ItemEnum = parse_quote! { + #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema, QueryResponses)] + #[serde(deny_unknown_fields)] + pub enum QueryMsg { + #[returns(SomeType)] + Balance {}, + } + }; + + query_responses_derive_impl(input); + } + + #[test] + #[should_panic] + fn panic_if_queries_are_not_snake_case3() { + let input: ItemEnum = parse_quote! { + #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema, QueryResponses)] + #[serde(rename_all = "kebab-case")] + pub enum QueryMsg { + #[returns(SomeType)] + Balance {}, + } + }; + + query_responses_derive_impl(input); + } } From 7faefd3d060530ce4c61bf1a6a18dcbc3e51f1f1 Mon Sep 17 00:00:00 2001 From: Tomasz Kurcz Date: Wed, 29 Jun 2022 19:35:51 +0200 Subject: [PATCH 15/41] schema-derive: verify `rename_all = "snake_case"` is used --- Cargo.lock | 3 +-- packages/schema-derive/Cargo.toml | 2 +- packages/schema-derive/src/lib.rs | 33 +++++++++++++++++++++++++++---- 3 files changed, 31 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1eb2c8009f..d5051d1744 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1637,8 +1637,7 @@ dependencies = [ [[package]] name = "serde_derive_internals" version = "0.26.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85bf8229e7920a9f636479437026331ce11aa132b4dde37d121944a44d6e5f3c" +source = "git+https://github.com/uint/serde?branch=expose_rename_rules#ea26d667329a0f95e64311c3433a72ec70e36710" dependencies = [ "proc-macro2", "quote", diff --git a/packages/schema-derive/Cargo.toml b/packages/schema-derive/Cargo.toml index 8c7359e9cf..7df8c10beb 100644 --- a/packages/schema-derive/Cargo.toml +++ b/packages/schema-derive/Cargo.toml @@ -10,7 +10,7 @@ license = "Apache-2.0" [dependencies] proc-macro2 = "1" quote = "1" -serde_derive_internals = "0.26.0" +serde_derive_internals = { git = "https://github.com/uint/serde", branch = "expose_rename_rules" } syn = { version = "1", features = ["full", "printing", "extra-traits"] } [lib] diff --git a/packages/schema-derive/src/lib.rs b/packages/schema-derive/src/lib.rs index a6b66af3a8..af2f82c2d8 100644 --- a/packages/schema-derive/src/lib.rs +++ b/packages/schema-derive/src/lib.rs @@ -1,5 +1,8 @@ use quote::ToTokens; -use syn::{parse_macro_input, parse_quote, ExprTuple, ItemEnum, ItemImpl, Type, Variant}; +use serde_derive_internals::attr::RenameRule; +use syn::{ + parse_macro_input, parse_quote, DeriveInput, ExprTuple, ItemEnum, ItemImpl, Type, Variant, +}; /// Extract the query -> response mapping out of an enum variant. fn parse_query(v: Variant) -> ExprTuple { @@ -29,6 +32,18 @@ fn to_snake_case(input: &str) -> String { snake } +fn verify_serde_snake_case(input: ItemEnum) -> bool { + let ctx = serde_derive_internals::Ctxt::new(); + let attr = serde_derive_internals::attr::Container::from_ast(&ctx, &DeriveInput::from(input)); + ctx.check().unwrap(); + + let rename_all_rules = attr.rename_all_rules(); + + [rename_all_rules.serialize(), rename_all_rules.deserialize()] + .iter() + .all(|rule| **rule == RenameRule::SnakeCase) +} + #[proc_macro_derive(QueryResponses, attributes(returns))] pub fn query_responses_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream { let input = parse_macro_input!(input as ItemEnum); @@ -39,6 +54,10 @@ pub fn query_responses_derive(input: proc_macro::TokenStream) -> proc_macro::Tok } fn query_responses_derive_impl(input: ItemEnum) -> ItemImpl { + if !verify_serde_snake_case(input.clone()) { + panic!("queries need to be serialized as snake_case using #[serde(rename_all = \"snake_case\")]"); + } + let ident = input.ident; let responses = input.variants.into_iter().map(parse_query); @@ -124,7 +143,9 @@ mod tests { } #[test] - #[should_panic] + #[should_panic( + expected = "queries need to be serialized as snake_case using #[serde(rename_all = \"snake_case\")]" + )] fn panic_if_queries_are_not_snake_case1() { let input: ItemEnum = parse_quote! { #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema, QueryResponses)] @@ -138,7 +159,9 @@ mod tests { } #[test] - #[should_panic] + #[should_panic( + expected = "queries need to be serialized as snake_case using #[serde(rename_all = \"snake_case\")]" + )] fn panic_if_queries_are_not_snake_case2() { let input: ItemEnum = parse_quote! { #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema, QueryResponses)] @@ -153,7 +176,9 @@ mod tests { } #[test] - #[should_panic] + #[should_panic( + expected = "queries need to be serialized as snake_case using #[serde(rename_all = \"snake_case\")]" + )] fn panic_if_queries_are_not_snake_case3() { let input: ItemEnum = parse_quote! { #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema, QueryResponses)] From aebf8591a2696529e4697827ca8c66d91ed94c09 Mon Sep 17 00:00:00 2001 From: Tomasz Kurcz Date: Wed, 29 Jun 2022 22:16:42 +0200 Subject: [PATCH 16/41] Revert "schema-derive: verify `rename_all = "snake_case"` is used" This reverts commit 7faefd3d060530ce4c61bf1a6a18dcbc3e51f1f1. --- Cargo.lock | 3 ++- packages/schema-derive/Cargo.toml | 2 +- packages/schema-derive/src/lib.rs | 33 ++++--------------------------- 3 files changed, 7 insertions(+), 31 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d5051d1744..1eb2c8009f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1637,7 +1637,8 @@ dependencies = [ [[package]] name = "serde_derive_internals" version = "0.26.0" -source = "git+https://github.com/uint/serde?branch=expose_rename_rules#ea26d667329a0f95e64311c3433a72ec70e36710" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85bf8229e7920a9f636479437026331ce11aa132b4dde37d121944a44d6e5f3c" dependencies = [ "proc-macro2", "quote", diff --git a/packages/schema-derive/Cargo.toml b/packages/schema-derive/Cargo.toml index 7df8c10beb..8c7359e9cf 100644 --- a/packages/schema-derive/Cargo.toml +++ b/packages/schema-derive/Cargo.toml @@ -10,7 +10,7 @@ license = "Apache-2.0" [dependencies] proc-macro2 = "1" quote = "1" -serde_derive_internals = { git = "https://github.com/uint/serde", branch = "expose_rename_rules" } +serde_derive_internals = "0.26.0" syn = { version = "1", features = ["full", "printing", "extra-traits"] } [lib] diff --git a/packages/schema-derive/src/lib.rs b/packages/schema-derive/src/lib.rs index af2f82c2d8..a6b66af3a8 100644 --- a/packages/schema-derive/src/lib.rs +++ b/packages/schema-derive/src/lib.rs @@ -1,8 +1,5 @@ use quote::ToTokens; -use serde_derive_internals::attr::RenameRule; -use syn::{ - parse_macro_input, parse_quote, DeriveInput, ExprTuple, ItemEnum, ItemImpl, Type, Variant, -}; +use syn::{parse_macro_input, parse_quote, ExprTuple, ItemEnum, ItemImpl, Type, Variant}; /// Extract the query -> response mapping out of an enum variant. fn parse_query(v: Variant) -> ExprTuple { @@ -32,18 +29,6 @@ fn to_snake_case(input: &str) -> String { snake } -fn verify_serde_snake_case(input: ItemEnum) -> bool { - let ctx = serde_derive_internals::Ctxt::new(); - let attr = serde_derive_internals::attr::Container::from_ast(&ctx, &DeriveInput::from(input)); - ctx.check().unwrap(); - - let rename_all_rules = attr.rename_all_rules(); - - [rename_all_rules.serialize(), rename_all_rules.deserialize()] - .iter() - .all(|rule| **rule == RenameRule::SnakeCase) -} - #[proc_macro_derive(QueryResponses, attributes(returns))] pub fn query_responses_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream { let input = parse_macro_input!(input as ItemEnum); @@ -54,10 +39,6 @@ pub fn query_responses_derive(input: proc_macro::TokenStream) -> proc_macro::Tok } fn query_responses_derive_impl(input: ItemEnum) -> ItemImpl { - if !verify_serde_snake_case(input.clone()) { - panic!("queries need to be serialized as snake_case using #[serde(rename_all = \"snake_case\")]"); - } - let ident = input.ident; let responses = input.variants.into_iter().map(parse_query); @@ -143,9 +124,7 @@ mod tests { } #[test] - #[should_panic( - expected = "queries need to be serialized as snake_case using #[serde(rename_all = \"snake_case\")]" - )] + #[should_panic] fn panic_if_queries_are_not_snake_case1() { let input: ItemEnum = parse_quote! { #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema, QueryResponses)] @@ -159,9 +138,7 @@ mod tests { } #[test] - #[should_panic( - expected = "queries need to be serialized as snake_case using #[serde(rename_all = \"snake_case\")]" - )] + #[should_panic] fn panic_if_queries_are_not_snake_case2() { let input: ItemEnum = parse_quote! { #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema, QueryResponses)] @@ -176,9 +153,7 @@ mod tests { } #[test] - #[should_panic( - expected = "queries need to be serialized as snake_case using #[serde(rename_all = \"snake_case\")]" - )] + #[should_panic] fn panic_if_queries_are_not_snake_case3() { let input: ItemEnum = parse_quote! { #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema, QueryResponses)] From 7e9ea3d4b321e717c6d6129384d0600575fc6458 Mon Sep 17 00:00:00 2001 From: Tomasz Kurcz Date: Mon, 4 Jul 2022 19:32:33 +0200 Subject: [PATCH 17/41] idl: integrity check for query names in generated JSON --- Cargo.lock | 29 +++++----- contracts/hackatom/Cargo.lock | 27 +++++---- packages/schema-derive/Cargo.toml | 2 +- packages/schema-derive/src/lib.rs | 79 ++++++++------------------- packages/schema/src/query_response.rs | 31 ++++++++++- 5 files changed, 81 insertions(+), 87 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1eb2c8009f..c02f3e62bf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -296,13 +296,23 @@ dependencies = [ "thiserror", ] +[[package]] +name = "cosmwasm-schema" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "772e80bbad231a47a2068812b723a1ff81dd4a0d56c9391ac748177bea3a61da" +dependencies = [ + "schemars", + "serde_json", +] + [[package]] name = "cosmwasm-schema-derive" version = "1.0.0" dependencies = [ + "cosmwasm-schema 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "proc-macro2", "quote", - "serde_derive_internals 0.26.0", "syn", ] @@ -314,7 +324,7 @@ dependencies = [ "chrono", "cosmwasm-crypto", "cosmwasm-derive", - "cosmwasm-schema", + "cosmwasm-schema 1.0.0", "forward_ref", "hex", "hex-literal", @@ -864,7 +874,7 @@ dependencies = [ name = "hackatom" version = "0.0.0" dependencies = [ - "cosmwasm-schema", + "cosmwasm-schema 1.0.0", "cosmwasm-std", "rust-argon2", "schemars", @@ -1540,7 +1550,7 @@ checksum = "41ae4dce13e8614c46ac3c38ef1c0d668b101df6ac39817aebdaa26642ddae9b" dependencies = [ "proc-macro2", "quote", - "serde_derive_internals 0.25.0", + "serde_derive_internals", "syn", ] @@ -1634,17 +1644,6 @@ dependencies = [ "syn", ] -[[package]] -name = "serde_derive_internals" -version = "0.26.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85bf8229e7920a9f636479437026331ce11aa132b4dde37d121944a44d6e5f3c" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "serde_json" version = "1.0.81" diff --git a/contracts/hackatom/Cargo.lock b/contracts/hackatom/Cargo.lock index 94108cf448..135c9eeccc 100644 --- a/contracts/hackatom/Cargo.lock +++ b/contracts/hackatom/Cargo.lock @@ -197,13 +197,23 @@ dependencies = [ "thiserror", ] +[[package]] +name = "cosmwasm-schema" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "772e80bbad231a47a2068812b723a1ff81dd4a0d56c9391ac748177bea3a61da" +dependencies = [ + "schemars", + "serde_json", +] + [[package]] name = "cosmwasm-schema-derive" version = "1.0.0" dependencies = [ + "cosmwasm-schema 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "proc-macro2", "quote", - "serde_derive_internals 0.26.0", "syn", ] @@ -681,7 +691,7 @@ dependencies = [ name = "hackatom" version = "0.0.0" dependencies = [ - "cosmwasm-schema", + "cosmwasm-schema 1.0.0", "cosmwasm-std", "cosmwasm-storage", "cosmwasm-vm", @@ -1237,7 +1247,7 @@ checksum = "4a9ea2a613fe4cd7118b2bb101a25d8ae6192e1975179b67b2f17afd11e70ac8" dependencies = [ "proc-macro2", "quote", - "serde_derive_internals 0.25.0", + "serde_derive_internals", "syn", ] @@ -1315,17 +1325,6 @@ dependencies = [ "syn", ] -[[package]] -name = "serde_derive_internals" -version = "0.26.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85bf8229e7920a9f636479437026331ce11aa132b4dde37d121944a44d6e5f3c" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "serde_json" version = "1.0.64" diff --git a/packages/schema-derive/Cargo.toml b/packages/schema-derive/Cargo.toml index 8c7359e9cf..89a9b4a2aa 100644 --- a/packages/schema-derive/Cargo.toml +++ b/packages/schema-derive/Cargo.toml @@ -8,9 +8,9 @@ repository = "https://github.com/CosmWasm/cosmwasm/tree/main/packages/schema" license = "Apache-2.0" [dependencies] +cosmwasm-schema = "1" proc-macro2 = "1" quote = "1" -serde_derive_internals = "0.26.0" syn = { version = "1", features = ["full", "printing", "extra-traits"] } [lib] diff --git a/packages/schema-derive/src/lib.rs b/packages/schema-derive/src/lib.rs index a6b66af3a8..e279294e04 100644 --- a/packages/schema-derive/src/lib.rs +++ b/packages/schema-derive/src/lib.rs @@ -1,8 +1,8 @@ use quote::ToTokens; -use syn::{parse_macro_input, parse_quote, ExprTuple, ItemEnum, ItemImpl, Type, Variant}; +use syn::{parse_macro_input, parse_quote, Expr, ExprTuple, ItemEnum, ItemImpl, Type, Variant}; /// Extract the query -> response mapping out of an enum variant. -fn parse_query(v: Variant) -> ExprTuple { +fn parse_query(v: Variant) -> (String, Expr) { let query = to_snake_case(&v.ident.to_string()); let response_ty: Type = v .attrs @@ -12,8 +12,15 @@ fn parse_query(v: Variant) -> ExprTuple { .parse_args() .unwrap(); + ( + query, + parse_quote!(cosmwasm_schema::schema_for!(#response_ty)), + ) +} + +fn parse_tuple((q, r): (String, Expr)) -> ExprTuple { parse_quote! { - (#query.to_string(), cosmwasm_schema::schema_for!(#response_ty)) + (#q.to_string(), #r) } } @@ -40,16 +47,20 @@ pub fn query_responses_derive(input: proc_macro::TokenStream) -> proc_macro::Tok fn query_responses_derive_impl(input: ItemEnum) -> ItemImpl { let ident = input.ident; - - let responses = input.variants.into_iter().map(parse_query); + let mappings = input.variants.into_iter().map(parse_query); + let mut queries: Vec<_> = mappings.clone().map(|(q, _)| q).collect(); + queries.sort(); + let mappings = mappings.map(parse_tuple); parse_quote! { #[automatically_derived] #[cfg(not(target_arch = "wasm32"))] impl cosmwasm_schema::QueryResponses for #ident { fn response_schemas() -> std::collections::BTreeMap { + Self::check_api_integrity(&[#(#queries),*]); + std::collections::BTreeMap::from([ - #( #responses, )* + #( #mappings, )* ]) } } @@ -68,10 +79,10 @@ mod tests { #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema, QueryResponses)] #[serde(rename_all = "snake_case")] pub enum QueryMsg { - #[returns(SomeType)] - Balance {}, #[returns(some_crate::AnotherType)] Supply {}, + #[returns(SomeType)] + Balance {}, } }; @@ -82,9 +93,11 @@ mod tests { #[cfg(not(target_arch = "wasm32"))] impl cosmwasm_schema::QueryResponses for QueryMsg { fn response_schemas() -> std::collections::BTreeMap { + Self::check_api_integrity(&["balance", "supply"]); + std::collections::BTreeMap::from([ - ("balance".to_string(), cosmwasm_schema::schema_for!(SomeType)), ("supply".to_string(), cosmwasm_schema::schema_for!(some_crate::AnotherType)), + ("balance".to_string(), cosmwasm_schema::schema_for!(SomeType)), ]) } } @@ -100,7 +113,7 @@ mod tests { }; assert_eq!( - parse_query(variant), + parse_tuple(parse_query(variant)), parse_quote! { ("get_foo".to_string(), cosmwasm_schema::schema_for!(Foo)) } @@ -112,7 +125,7 @@ mod tests { }; assert_eq!( - parse_query(variant), + parse_tuple(parse_query(variant)), parse_quote! { ("get_foo".to_string(), cosmwasm_schema::schema_for!(some_crate::Foo)) } ); } @@ -122,48 +135,4 @@ mod tests { assert_eq!(to_snake_case("SnakeCase"), "snake_case"); assert_eq!(to_snake_case("Wasm123AndCo"), "wasm123_and_co"); } - - #[test] - #[should_panic] - fn panic_if_queries_are_not_snake_case1() { - let input: ItemEnum = parse_quote! { - #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema, QueryResponses)] - pub enum QueryMsg { - #[returns(SomeType)] - Balance {}, - } - }; - - query_responses_derive_impl(input); - } - - #[test] - #[should_panic] - fn panic_if_queries_are_not_snake_case2() { - let input: ItemEnum = parse_quote! { - #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema, QueryResponses)] - #[serde(deny_unknown_fields)] - pub enum QueryMsg { - #[returns(SomeType)] - Balance {}, - } - }; - - query_responses_derive_impl(input); - } - - #[test] - #[should_panic] - fn panic_if_queries_are_not_snake_case3() { - let input: ItemEnum = parse_quote! { - #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema, QueryResponses)] - #[serde(rename_all = "kebab-case")] - pub enum QueryMsg { - #[returns(SomeType)] - Balance {}, - } - }; - - query_responses_derive_impl(input); - } } diff --git a/packages/schema/src/query_response.rs b/packages/schema/src/query_response.rs index cb585af1dc..527ff4cfe6 100644 --- a/packages/schema/src/query_response.rs +++ b/packages/schema/src/query_response.rs @@ -1,9 +1,36 @@ use std::collections::BTreeMap; -use schemars::schema::RootSchema; +use schemars::{schema::RootSchema, JsonSchema}; pub use cosmwasm_schema_derive::QueryResponses; -pub trait QueryResponses { +pub trait QueryResponses: JsonSchema { fn response_schemas() -> BTreeMap; + + /// `generated_queries` is expected to be a sorted slice here! + fn check_api_integrity(generated_queries: &[&str]) { + let schema = crate::schema_for!(Self); + let mut schema_queries: Vec<_> = schema + .schema + .subschemas + .unwrap() + .one_of + .unwrap() + .into_iter() + .map(|s| { + s.into_object() + .object + .unwrap() + .required + .into_iter() + .next() + .unwrap() + }) + .collect(); + schema_queries.sort(); + if schema_queries != generated_queries { + // TODO: errors + panic!("NOES"); + } + } } From ef970e72b8cf2849502fc037b7c5dca161d5a15c Mon Sep 17 00:00:00 2001 From: Tomasz Kurcz Date: Mon, 4 Jul 2022 19:59:46 +0200 Subject: [PATCH 18/41] contracts: update Cargo.lock --- contracts/burner/Cargo.lock | 13 ++++++++++++- contracts/crypto-verify/Cargo.lock | 13 ++++++++++++- contracts/floaty/Cargo.lock | 13 ++++++++++++- contracts/ibc-reflect-send/Cargo.lock | 13 ++++++++++++- contracts/ibc-reflect/Cargo.lock | 13 ++++++++++++- contracts/queue/Cargo.lock | 13 ++++++++++++- contracts/reflect/Cargo.lock | 13 ++++++++++++- contracts/staking/Cargo.lock | 13 ++++++++++++- 8 files changed, 96 insertions(+), 8 deletions(-) diff --git a/contracts/burner/Cargo.lock b/contracts/burner/Cargo.lock index c7b5cf9480..685840b589 100644 --- a/contracts/burner/Cargo.lock +++ b/contracts/burner/Cargo.lock @@ -92,7 +92,7 @@ checksum = "d9df67f7bf9ef8498769f994239c45613ef0c5899415fb58e9add412d2c1a538" name = "burner" version = "0.0.0" dependencies = [ - "cosmwasm-schema", + "cosmwasm-schema 1.0.0", "cosmwasm-std", "cosmwasm-vm", "schemars", @@ -179,10 +179,21 @@ dependencies = [ "thiserror", ] +[[package]] +name = "cosmwasm-schema" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "772e80bbad231a47a2068812b723a1ff81dd4a0d56c9391ac748177bea3a61da" +dependencies = [ + "schemars", + "serde_json", +] + [[package]] name = "cosmwasm-schema-derive" version = "1.0.0" dependencies = [ + "cosmwasm-schema 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "proc-macro2", "quote", "syn", diff --git a/contracts/crypto-verify/Cargo.lock b/contracts/crypto-verify/Cargo.lock index 5c1fede9c2..eab5be3802 100644 --- a/contracts/crypto-verify/Cargo.lock +++ b/contracts/crypto-verify/Cargo.lock @@ -181,10 +181,21 @@ dependencies = [ "thiserror", ] +[[package]] +name = "cosmwasm-schema" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "772e80bbad231a47a2068812b723a1ff81dd4a0d56c9391ac748177bea3a61da" +dependencies = [ + "schemars", + "serde_json", +] + [[package]] name = "cosmwasm-schema-derive" version = "1.0.0" dependencies = [ + "cosmwasm-schema 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "proc-macro2", "quote", "syn", @@ -386,7 +397,7 @@ dependencies = [ name = "crypto-verify" version = "0.0.0" dependencies = [ - "cosmwasm-schema", + "cosmwasm-schema 1.0.0", "cosmwasm-std", "cosmwasm-storage", "cosmwasm-vm", diff --git a/contracts/floaty/Cargo.lock b/contracts/floaty/Cargo.lock index 834452bdf6..6326c72b7d 100644 --- a/contracts/floaty/Cargo.lock +++ b/contracts/floaty/Cargo.lock @@ -168,10 +168,21 @@ dependencies = [ "thiserror", ] +[[package]] +name = "cosmwasm-schema" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "772e80bbad231a47a2068812b723a1ff81dd4a0d56c9391ac748177bea3a61da" +dependencies = [ + "schemars", + "serde_json", +] + [[package]] name = "cosmwasm-schema-derive" version = "1.0.0" dependencies = [ + "cosmwasm-schema 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "proc-macro2", "quote", "syn", @@ -579,7 +590,7 @@ dependencies = [ name = "floaty" version = "0.0.0" dependencies = [ - "cosmwasm-schema", + "cosmwasm-schema 1.0.0", "cosmwasm-std", "cosmwasm-storage", "cosmwasm-vm", diff --git a/contracts/ibc-reflect-send/Cargo.lock b/contracts/ibc-reflect-send/Cargo.lock index 5b9b188ac9..76fb4de1f4 100644 --- a/contracts/ibc-reflect-send/Cargo.lock +++ b/contracts/ibc-reflect-send/Cargo.lock @@ -168,10 +168,21 @@ dependencies = [ "thiserror", ] +[[package]] +name = "cosmwasm-schema" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "772e80bbad231a47a2068812b723a1ff81dd4a0d56c9391ac748177bea3a61da" +dependencies = [ + "schemars", + "serde_json", +] + [[package]] name = "cosmwasm-schema-derive" version = "1.0.0" dependencies = [ + "cosmwasm-schema 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "proc-macro2", "quote", "syn", @@ -694,7 +705,7 @@ dependencies = [ name = "ibc-reflect-send" version = "0.0.0" dependencies = [ - "cosmwasm-schema", + "cosmwasm-schema 1.0.0", "cosmwasm-std", "cosmwasm-storage", "cosmwasm-vm", diff --git a/contracts/ibc-reflect/Cargo.lock b/contracts/ibc-reflect/Cargo.lock index 90a3618300..3595878682 100644 --- a/contracts/ibc-reflect/Cargo.lock +++ b/contracts/ibc-reflect/Cargo.lock @@ -168,10 +168,21 @@ dependencies = [ "thiserror", ] +[[package]] +name = "cosmwasm-schema" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "772e80bbad231a47a2068812b723a1ff81dd4a0d56c9391ac748177bea3a61da" +dependencies = [ + "schemars", + "serde_json", +] + [[package]] name = "cosmwasm-schema-derive" version = "1.0.0" dependencies = [ + "cosmwasm-schema 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "proc-macro2", "quote", "syn", @@ -694,7 +705,7 @@ dependencies = [ name = "ibc-reflect" version = "0.0.0" dependencies = [ - "cosmwasm-schema", + "cosmwasm-schema 1.0.0", "cosmwasm-std", "cosmwasm-storage", "cosmwasm-vm", diff --git a/contracts/queue/Cargo.lock b/contracts/queue/Cargo.lock index bd2edf8b08..8a8148d9ab 100644 --- a/contracts/queue/Cargo.lock +++ b/contracts/queue/Cargo.lock @@ -168,10 +168,21 @@ dependencies = [ "thiserror", ] +[[package]] +name = "cosmwasm-schema" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "772e80bbad231a47a2068812b723a1ff81dd4a0d56c9391ac748177bea3a61da" +dependencies = [ + "schemars", + "serde_json", +] + [[package]] name = "cosmwasm-schema-derive" version = "1.0.0" dependencies = [ + "cosmwasm-schema 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "proc-macro2", "quote", "syn", @@ -963,7 +974,7 @@ dependencies = [ name = "queue" version = "0.0.0" dependencies = [ - "cosmwasm-schema", + "cosmwasm-schema 1.0.0", "cosmwasm-std", "cosmwasm-vm", "schemars", diff --git a/contracts/reflect/Cargo.lock b/contracts/reflect/Cargo.lock index 8739c04c78..45df1628b0 100644 --- a/contracts/reflect/Cargo.lock +++ b/contracts/reflect/Cargo.lock @@ -168,10 +168,21 @@ dependencies = [ "thiserror", ] +[[package]] +name = "cosmwasm-schema" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "772e80bbad231a47a2068812b723a1ff81dd4a0d56c9391ac748177bea3a61da" +dependencies = [ + "schemars", + "serde_json", +] + [[package]] name = "cosmwasm-schema-derive" version = "1.0.0" dependencies = [ + "cosmwasm-schema 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "proc-macro2", "quote", "syn", @@ -1063,7 +1074,7 @@ dependencies = [ name = "reflect" version = "0.0.0" dependencies = [ - "cosmwasm-schema", + "cosmwasm-schema 1.0.0", "cosmwasm-std", "cosmwasm-storage", "cosmwasm-vm", diff --git a/contracts/staking/Cargo.lock b/contracts/staking/Cargo.lock index 200549bb67..05856f2cc2 100644 --- a/contracts/staking/Cargo.lock +++ b/contracts/staking/Cargo.lock @@ -168,10 +168,21 @@ dependencies = [ "thiserror", ] +[[package]] +name = "cosmwasm-schema" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "772e80bbad231a47a2068812b723a1ff81dd4a0d56c9391ac748177bea3a61da" +dependencies = [ + "schemars", + "serde_json", +] + [[package]] name = "cosmwasm-schema-derive" version = "1.0.0" dependencies = [ + "cosmwasm-schema 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "proc-macro2", "quote", "syn", @@ -1345,7 +1356,7 @@ checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" name = "staking" version = "0.0.0" dependencies = [ - "cosmwasm-schema", + "cosmwasm-schema 1.0.0", "cosmwasm-std", "cosmwasm-storage", "cosmwasm-vm", From 6a27aaf6cbfba181df7ef3d66aa03cdf998201e0 Mon Sep 17 00:00:00 2001 From: Tomasz Kurcz Date: Mon, 4 Jul 2022 20:14:51 +0200 Subject: [PATCH 19/41] schema-derive: remove unused dep --- Cargo.lock | 15 ++------------- contracts/burner/Cargo.lock | 13 +------------ contracts/crypto-verify/Cargo.lock | 13 +------------ contracts/floaty/Cargo.lock | 13 +------------ contracts/hackatom/Cargo.lock | 13 +------------ contracts/ibc-reflect-send/Cargo.lock | 13 +------------ contracts/ibc-reflect/Cargo.lock | 13 +------------ contracts/queue/Cargo.lock | 13 +------------ contracts/reflect/Cargo.lock | 13 +------------ contracts/staking/Cargo.lock | 13 +------------ packages/schema-derive/Cargo.toml | 1 - 11 files changed, 11 insertions(+), 122 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c02f3e62bf..ef3d6b1290 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -296,21 +296,10 @@ dependencies = [ "thiserror", ] -[[package]] -name = "cosmwasm-schema" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "772e80bbad231a47a2068812b723a1ff81dd4a0d56c9391ac748177bea3a61da" -dependencies = [ - "schemars", - "serde_json", -] - [[package]] name = "cosmwasm-schema-derive" version = "1.0.0" dependencies = [ - "cosmwasm-schema 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "proc-macro2", "quote", "syn", @@ -324,7 +313,7 @@ dependencies = [ "chrono", "cosmwasm-crypto", "cosmwasm-derive", - "cosmwasm-schema 1.0.0", + "cosmwasm-schema", "forward_ref", "hex", "hex-literal", @@ -874,7 +863,7 @@ dependencies = [ name = "hackatom" version = "0.0.0" dependencies = [ - "cosmwasm-schema 1.0.0", + "cosmwasm-schema", "cosmwasm-std", "rust-argon2", "schemars", diff --git a/contracts/burner/Cargo.lock b/contracts/burner/Cargo.lock index 685840b589..c7b5cf9480 100644 --- a/contracts/burner/Cargo.lock +++ b/contracts/burner/Cargo.lock @@ -92,7 +92,7 @@ checksum = "d9df67f7bf9ef8498769f994239c45613ef0c5899415fb58e9add412d2c1a538" name = "burner" version = "0.0.0" dependencies = [ - "cosmwasm-schema 1.0.0", + "cosmwasm-schema", "cosmwasm-std", "cosmwasm-vm", "schemars", @@ -179,21 +179,10 @@ dependencies = [ "thiserror", ] -[[package]] -name = "cosmwasm-schema" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "772e80bbad231a47a2068812b723a1ff81dd4a0d56c9391ac748177bea3a61da" -dependencies = [ - "schemars", - "serde_json", -] - [[package]] name = "cosmwasm-schema-derive" version = "1.0.0" dependencies = [ - "cosmwasm-schema 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "proc-macro2", "quote", "syn", diff --git a/contracts/crypto-verify/Cargo.lock b/contracts/crypto-verify/Cargo.lock index eab5be3802..5c1fede9c2 100644 --- a/contracts/crypto-verify/Cargo.lock +++ b/contracts/crypto-verify/Cargo.lock @@ -181,21 +181,10 @@ dependencies = [ "thiserror", ] -[[package]] -name = "cosmwasm-schema" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "772e80bbad231a47a2068812b723a1ff81dd4a0d56c9391ac748177bea3a61da" -dependencies = [ - "schemars", - "serde_json", -] - [[package]] name = "cosmwasm-schema-derive" version = "1.0.0" dependencies = [ - "cosmwasm-schema 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "proc-macro2", "quote", "syn", @@ -397,7 +386,7 @@ dependencies = [ name = "crypto-verify" version = "0.0.0" dependencies = [ - "cosmwasm-schema 1.0.0", + "cosmwasm-schema", "cosmwasm-std", "cosmwasm-storage", "cosmwasm-vm", diff --git a/contracts/floaty/Cargo.lock b/contracts/floaty/Cargo.lock index 6326c72b7d..834452bdf6 100644 --- a/contracts/floaty/Cargo.lock +++ b/contracts/floaty/Cargo.lock @@ -168,21 +168,10 @@ dependencies = [ "thiserror", ] -[[package]] -name = "cosmwasm-schema" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "772e80bbad231a47a2068812b723a1ff81dd4a0d56c9391ac748177bea3a61da" -dependencies = [ - "schemars", - "serde_json", -] - [[package]] name = "cosmwasm-schema-derive" version = "1.0.0" dependencies = [ - "cosmwasm-schema 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "proc-macro2", "quote", "syn", @@ -590,7 +579,7 @@ dependencies = [ name = "floaty" version = "0.0.0" dependencies = [ - "cosmwasm-schema 1.0.0", + "cosmwasm-schema", "cosmwasm-std", "cosmwasm-storage", "cosmwasm-vm", diff --git a/contracts/hackatom/Cargo.lock b/contracts/hackatom/Cargo.lock index 135c9eeccc..e9b7339596 100644 --- a/contracts/hackatom/Cargo.lock +++ b/contracts/hackatom/Cargo.lock @@ -197,21 +197,10 @@ dependencies = [ "thiserror", ] -[[package]] -name = "cosmwasm-schema" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "772e80bbad231a47a2068812b723a1ff81dd4a0d56c9391ac748177bea3a61da" -dependencies = [ - "schemars", - "serde_json", -] - [[package]] name = "cosmwasm-schema-derive" version = "1.0.0" dependencies = [ - "cosmwasm-schema 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "proc-macro2", "quote", "syn", @@ -691,7 +680,7 @@ dependencies = [ name = "hackatom" version = "0.0.0" dependencies = [ - "cosmwasm-schema 1.0.0", + "cosmwasm-schema", "cosmwasm-std", "cosmwasm-storage", "cosmwasm-vm", diff --git a/contracts/ibc-reflect-send/Cargo.lock b/contracts/ibc-reflect-send/Cargo.lock index 76fb4de1f4..5b9b188ac9 100644 --- a/contracts/ibc-reflect-send/Cargo.lock +++ b/contracts/ibc-reflect-send/Cargo.lock @@ -168,21 +168,10 @@ dependencies = [ "thiserror", ] -[[package]] -name = "cosmwasm-schema" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "772e80bbad231a47a2068812b723a1ff81dd4a0d56c9391ac748177bea3a61da" -dependencies = [ - "schemars", - "serde_json", -] - [[package]] name = "cosmwasm-schema-derive" version = "1.0.0" dependencies = [ - "cosmwasm-schema 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "proc-macro2", "quote", "syn", @@ -705,7 +694,7 @@ dependencies = [ name = "ibc-reflect-send" version = "0.0.0" dependencies = [ - "cosmwasm-schema 1.0.0", + "cosmwasm-schema", "cosmwasm-std", "cosmwasm-storage", "cosmwasm-vm", diff --git a/contracts/ibc-reflect/Cargo.lock b/contracts/ibc-reflect/Cargo.lock index 3595878682..90a3618300 100644 --- a/contracts/ibc-reflect/Cargo.lock +++ b/contracts/ibc-reflect/Cargo.lock @@ -168,21 +168,10 @@ dependencies = [ "thiserror", ] -[[package]] -name = "cosmwasm-schema" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "772e80bbad231a47a2068812b723a1ff81dd4a0d56c9391ac748177bea3a61da" -dependencies = [ - "schemars", - "serde_json", -] - [[package]] name = "cosmwasm-schema-derive" version = "1.0.0" dependencies = [ - "cosmwasm-schema 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "proc-macro2", "quote", "syn", @@ -705,7 +694,7 @@ dependencies = [ name = "ibc-reflect" version = "0.0.0" dependencies = [ - "cosmwasm-schema 1.0.0", + "cosmwasm-schema", "cosmwasm-std", "cosmwasm-storage", "cosmwasm-vm", diff --git a/contracts/queue/Cargo.lock b/contracts/queue/Cargo.lock index 8a8148d9ab..bd2edf8b08 100644 --- a/contracts/queue/Cargo.lock +++ b/contracts/queue/Cargo.lock @@ -168,21 +168,10 @@ dependencies = [ "thiserror", ] -[[package]] -name = "cosmwasm-schema" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "772e80bbad231a47a2068812b723a1ff81dd4a0d56c9391ac748177bea3a61da" -dependencies = [ - "schemars", - "serde_json", -] - [[package]] name = "cosmwasm-schema-derive" version = "1.0.0" dependencies = [ - "cosmwasm-schema 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "proc-macro2", "quote", "syn", @@ -974,7 +963,7 @@ dependencies = [ name = "queue" version = "0.0.0" dependencies = [ - "cosmwasm-schema 1.0.0", + "cosmwasm-schema", "cosmwasm-std", "cosmwasm-vm", "schemars", diff --git a/contracts/reflect/Cargo.lock b/contracts/reflect/Cargo.lock index 45df1628b0..8739c04c78 100644 --- a/contracts/reflect/Cargo.lock +++ b/contracts/reflect/Cargo.lock @@ -168,21 +168,10 @@ dependencies = [ "thiserror", ] -[[package]] -name = "cosmwasm-schema" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "772e80bbad231a47a2068812b723a1ff81dd4a0d56c9391ac748177bea3a61da" -dependencies = [ - "schemars", - "serde_json", -] - [[package]] name = "cosmwasm-schema-derive" version = "1.0.0" dependencies = [ - "cosmwasm-schema 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "proc-macro2", "quote", "syn", @@ -1074,7 +1063,7 @@ dependencies = [ name = "reflect" version = "0.0.0" dependencies = [ - "cosmwasm-schema 1.0.0", + "cosmwasm-schema", "cosmwasm-std", "cosmwasm-storage", "cosmwasm-vm", diff --git a/contracts/staking/Cargo.lock b/contracts/staking/Cargo.lock index 05856f2cc2..200549bb67 100644 --- a/contracts/staking/Cargo.lock +++ b/contracts/staking/Cargo.lock @@ -168,21 +168,10 @@ dependencies = [ "thiserror", ] -[[package]] -name = "cosmwasm-schema" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "772e80bbad231a47a2068812b723a1ff81dd4a0d56c9391ac748177bea3a61da" -dependencies = [ - "schemars", - "serde_json", -] - [[package]] name = "cosmwasm-schema-derive" version = "1.0.0" dependencies = [ - "cosmwasm-schema 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "proc-macro2", "quote", "syn", @@ -1356,7 +1345,7 @@ checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" name = "staking" version = "0.0.0" dependencies = [ - "cosmwasm-schema 1.0.0", + "cosmwasm-schema", "cosmwasm-std", "cosmwasm-storage", "cosmwasm-vm", diff --git a/packages/schema-derive/Cargo.toml b/packages/schema-derive/Cargo.toml index 89a9b4a2aa..904796c4cc 100644 --- a/packages/schema-derive/Cargo.toml +++ b/packages/schema-derive/Cargo.toml @@ -8,7 +8,6 @@ repository = "https://github.com/CosmWasm/cosmwasm/tree/main/packages/schema" license = "Apache-2.0" [dependencies] -cosmwasm-schema = "1" proc-macro2 = "1" quote = "1" syn = { version = "1", features = ["full", "printing", "extra-traits"] } From 6308461a8cc0d2bbc1054905393be3be55360031 Mon Sep 17 00:00:00 2001 From: Tomasz Kurcz Date: Mon, 4 Jul 2022 20:32:10 +0200 Subject: [PATCH 20/41] schema, schema-derive: refactor --- contracts/hackatom/schema/hackatom.json | 435 ----------------------- contracts/hackatom/schema/query_msg.json | 8 +- packages/schema-derive/src/lib.rs | 8 +- packages/schema/src/query_response.rs | 65 ++-- 4 files changed, 45 insertions(+), 471 deletions(-) delete mode 100644 contracts/hackatom/schema/hackatom.json diff --git a/contracts/hackatom/schema/hackatom.json b/contracts/hackatom/schema/hackatom.json deleted file mode 100644 index f498fe07fc..0000000000 --- a/contracts/hackatom/schema/hackatom.json +++ /dev/null @@ -1,435 +0,0 @@ -{ - "contract_name": "hackatom", - "contract_version": "0.0.0", - "idl_version": "1.0.0", - "instantiate": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "InstantiateMsg", - "type": "object", - "required": [ - "beneficiary", - "verifier" - ], - "properties": { - "beneficiary": { - "type": "string" - }, - "verifier": { - "type": "string" - } - }, - "additionalProperties": false - }, - "execute": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "ExecuteMsg", - "oneOf": [ - { - "description": "Releasing all funds in the contract to the beneficiary. This is the only \"proper\" action of this demo contract.", - "type": "object", - "required": [ - "release" - ], - "properties": { - "release": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Hashes some data. Uses CPU and memory, but no external calls.", - "type": "object", - "required": [ - "argon2" - ], - "properties": { - "argon2": { - "type": "object", - "required": [ - "mem_cost", - "time_cost" - ], - "properties": { - "mem_cost": { - "description": "The amount of memory requested (KB).", - "type": "integer", - "format": "uint32", - "minimum": 0.0 - }, - "time_cost": { - "description": "The number of passes.", - "type": "integer", - "format": "uint32", - "minimum": 0.0 - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Infinite loop to burn cpu cycles (only run when metering is enabled)", - "type": "object", - "required": [ - "cpu_loop" - ], - "properties": { - "cpu_loop": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Infinite loop making storage calls (to test when their limit hits)", - "type": "object", - "required": [ - "storage_loop" - ], - "properties": { - "storage_loop": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Infinite loop reading and writing memory", - "type": "object", - "required": [ - "memory_loop" - ], - "properties": { - "memory_loop": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Infinite loop sending message to itself", - "type": "object", - "required": [ - "message_loop" - ], - "properties": { - "message_loop": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Allocate large amounts of memory without consuming much gas", - "type": "object", - "required": [ - "allocate_large_memory" - ], - "properties": { - "allocate_large_memory": { - "type": "object", - "required": [ - "pages" - ], - "properties": { - "pages": { - "type": "integer", - "format": "uint32", - "minimum": 0.0 - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Trigger a panic to ensure framework handles gracefully", - "type": "object", - "required": [ - "panic" - ], - "properties": { - "panic": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Starting with CosmWasm 0.10, some API calls return user errors back to the contract. This triggers such user errors, ensuring the transaction does not fail in the backend.", - "type": "object", - "required": [ - "user_errors_in_api_calls" - ], - "properties": { - "user_errors_in_api_calls": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - } - ] - }, - "query": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "QueryMsg", - "oneOf": [ - { - "description": "returns a human-readable representation of the verifier use to ensure query path works in integration tests", - "type": "object", - "required": [ - "verifier" - ], - "properties": { - "verifier": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "This returns cosmwasm_std::AllBalanceResponse to demo use of the querier", - "type": "object", - "required": [ - "other_balance" - ], - "properties": { - "other_balance": { - "type": "object", - "required": [ - "address" - ], - "properties": { - "address": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Recurse will execute a query into itself up to depth-times and return Each step of the recursion may perform some extra work to test gas metering (`work` rounds of sha256 on contract). Now that we have Env, we can auto-calculate the address to recurse into", - "type": "object", - "required": [ - "recurse" - ], - "properties": { - "recurse": { - "type": "object", - "required": [ - "depth", - "work" - ], - "properties": { - "depth": { - "type": "integer", - "format": "uint32", - "minimum": 0.0 - }, - "work": { - "type": "integer", - "format": "uint32", - "minimum": 0.0 - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "GetInt returns a hardcoded u32 value", - "type": "object", - "required": [ - "get_int" - ], - "properties": { - "get_int": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - } - ] - }, - "migrate": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "MigrateMsg", - "description": "MigrateMsg allows a privileged contract administrator to run a migration on the contract. In this (demo) case it is just migrating from one hackatom code to the same code, but taking advantage of the migration step to set a new validator.\n\nNote that the contract doesn't enforce permissions here, this is done by blockchain logic (in the future by blockchain governance)", - "type": "object", - "required": [ - "verifier" - ], - "properties": { - "verifier": { - "type": "string" - } - }, - "additionalProperties": false - }, - "sudo": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "SudoMsg", - "description": "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", - "oneOf": [ - { - "type": "object", - "required": [ - "steal_funds" - ], - "properties": { - "steal_funds": { - "type": "object", - "required": [ - "amount", - "recipient" - ], - "properties": { - "amount": { - "type": "array", - "items": { - "$ref": "#/definitions/Coin" - } - }, - "recipient": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - } - ], - "definitions": { - "Coin": { - "type": "object", - "required": [ - "amount", - "denom" - ], - "properties": { - "amount": { - "$ref": "#/definitions/Uint128" - }, - "denom": { - "type": "string" - } - } - }, - "Uint128": { - "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", - "type": "string" - } - } - }, - "responses": { - "get_int": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "IntResponse", - "type": "object", - "required": [ - "int" - ], - "properties": { - "int": { - "type": "integer", - "format": "uint32", - "minimum": 0.0 - } - }, - "additionalProperties": false - }, - "other_balance": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "AllBalanceResponse", - "type": "object", - "required": [ - "amount" - ], - "properties": { - "amount": { - "description": "Returns all non-zero coins held by this account.", - "type": "array", - "items": { - "$ref": "#/definitions/Coin" - } - } - }, - "definitions": { - "Coin": { - "type": "object", - "required": [ - "amount", - "denom" - ], - "properties": { - "amount": { - "$ref": "#/definitions/Uint128" - }, - "denom": { - "type": "string" - } - } - }, - "Uint128": { - "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", - "type": "string" - } - } - }, - "recurse": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "RecurseResponse", - "type": "object", - "required": [ - "hashed" - ], - "properties": { - "hashed": { - "description": "hashed is the result of running sha256 \"work+1\" times on the contract's human address", - "allOf": [ - { - "$ref": "#/definitions/Binary" - } - ] - } - }, - "additionalProperties": false, - "definitions": { - "Binary": { - "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec", - "type": "string" - } - } - }, - "verifier": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "VerifierResponse", - "type": "object", - "required": [ - "verifier" - ], - "properties": { - "verifier": { - "type": "string" - } - }, - "additionalProperties": false - } - } -} diff --git a/contracts/hackatom/schema/query_msg.json b/contracts/hackatom/schema/query_msg.json index dfd58202b2..8d14b2f322 100644 --- a/contracts/hackatom/schema/query_msg.json +++ b/contracts/hackatom/schema/query_msg.json @@ -20,10 +20,10 @@ "description": "This returns cosmwasm_std::AllBalanceResponse to demo use of the querier", "type": "object", "required": [ - "other_balance" + "other-balance" ], "properties": { - "other_balance": { + "other-balance": { "type": "object", "required": [ "address" @@ -72,10 +72,10 @@ "description": "GetInt returns a hardcoded u32 value", "type": "object", "required": [ - "get_int" + "get-int" ], "properties": { - "get_int": { + "get-int": { "type": "object", "additionalProperties": false } diff --git a/packages/schema-derive/src/lib.rs b/packages/schema-derive/src/lib.rs index e279294e04..0201cbc93f 100644 --- a/packages/schema-derive/src/lib.rs +++ b/packages/schema-derive/src/lib.rs @@ -56,9 +56,7 @@ fn query_responses_derive_impl(input: ItemEnum) -> ItemImpl { #[automatically_derived] #[cfg(not(target_arch = "wasm32"))] impl cosmwasm_schema::QueryResponses for #ident { - fn response_schemas() -> std::collections::BTreeMap { - Self::check_api_integrity(&[#(#queries),*]); - + fn response_schemas_impl() -> std::collections::BTreeMap { std::collections::BTreeMap::from([ #( #mappings, )* ]) @@ -92,9 +90,7 @@ mod tests { #[automatically_derived] #[cfg(not(target_arch = "wasm32"))] impl cosmwasm_schema::QueryResponses for QueryMsg { - fn response_schemas() -> std::collections::BTreeMap { - Self::check_api_integrity(&["balance", "supply"]); - + fn response_schemas_impl() -> std::collections::BTreeMap { std::collections::BTreeMap::from([ ("supply".to_string(), cosmwasm_schema::schema_for!(some_crate::AnotherType)), ("balance".to_string(), cosmwasm_schema::schema_for!(SomeType)), diff --git a/packages/schema/src/query_response.rs b/packages/schema/src/query_response.rs index 527ff4cfe6..26b2ed6390 100644 --- a/packages/schema/src/query_response.rs +++ b/packages/schema/src/query_response.rs @@ -5,32 +5,45 @@ use schemars::{schema::RootSchema, JsonSchema}; pub use cosmwasm_schema_derive::QueryResponses; pub trait QueryResponses: JsonSchema { - fn response_schemas() -> BTreeMap; - - /// `generated_queries` is expected to be a sorted slice here! - fn check_api_integrity(generated_queries: &[&str]) { - let schema = crate::schema_for!(Self); - let mut schema_queries: Vec<_> = schema - .schema - .subschemas - .unwrap() - .one_of - .unwrap() - .into_iter() - .map(|s| { - s.into_object() - .object - .unwrap() - .required - .into_iter() - .next() - .unwrap() - }) + fn response_schemas() -> BTreeMap { + let response_schemas = Self::response_schemas_impl(); + + let queries: Vec<_> = response_schemas + .keys() + .map(std::borrow::Borrow::borrow) .collect(); - schema_queries.sort(); - if schema_queries != generated_queries { - // TODO: errors - panic!("NOES"); - } + + check_api_integrity::(&queries); + + response_schemas + } + + fn response_schemas_impl() -> BTreeMap; +} + +/// `generated_queries` is expected to be a sorted slice here! +fn check_api_integrity(generated_queries: &[&str]) { + let schema = crate::schema_for!(T); + let mut schema_queries: Vec<_> = schema + .schema + .subschemas + .unwrap() + .one_of + .unwrap() + .into_iter() + .map(|s| { + s.into_object() + .object + .unwrap() + .required + .into_iter() + .next() + .unwrap() + }) + .collect(); + schema_queries.sort(); + if schema_queries != generated_queries { + // TODO: errors + panic!("NOES"); } } From 23e638bb608a50ce0c1de31e6dda2f9cf695f2ba Mon Sep 17 00:00:00 2001 From: Tomasz Kurcz Date: Mon, 4 Jul 2022 20:55:29 +0200 Subject: [PATCH 21/41] schema: better errors for the integrity check --- contracts/hackatom/examples/schema.rs | 2 +- contracts/hackatom/schema/hackatom.json | 435 +++++++++++++++++++++++ contracts/hackatom/schema/query_msg.json | 8 +- packages/schema/src/query_response.rs | 45 ++- 4 files changed, 474 insertions(+), 16 deletions(-) create mode 100644 contracts/hackatom/schema/hackatom.json diff --git a/contracts/hackatom/examples/schema.rs b/contracts/hackatom/examples/schema.rs index 0cdde9a978..9cc7d0921c 100644 --- a/contracts/hackatom/examples/schema.rs +++ b/contracts/hackatom/examples/schema.rs @@ -38,7 +38,7 @@ fn main() { query: Some(schema_for!(QueryMsg)), migrate: Some(schema_for!(MigrateMsg)), sudo: Some(schema_for!(SudoMsg)), - responses: QueryMsg::response_schemas(), + responses: QueryMsg::response_schemas().unwrap(), } .render(); let json = api.to_string().unwrap(); diff --git a/contracts/hackatom/schema/hackatom.json b/contracts/hackatom/schema/hackatom.json new file mode 100644 index 0000000000..f498fe07fc --- /dev/null +++ b/contracts/hackatom/schema/hackatom.json @@ -0,0 +1,435 @@ +{ + "contract_name": "hackatom", + "contract_version": "0.0.0", + "idl_version": "1.0.0", + "instantiate": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "InstantiateMsg", + "type": "object", + "required": [ + "beneficiary", + "verifier" + ], + "properties": { + "beneficiary": { + "type": "string" + }, + "verifier": { + "type": "string" + } + }, + "additionalProperties": false + }, + "execute": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ExecuteMsg", + "oneOf": [ + { + "description": "Releasing all funds in the contract to the beneficiary. This is the only \"proper\" action of this demo contract.", + "type": "object", + "required": [ + "release" + ], + "properties": { + "release": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Hashes some data. Uses CPU and memory, but no external calls.", + "type": "object", + "required": [ + "argon2" + ], + "properties": { + "argon2": { + "type": "object", + "required": [ + "mem_cost", + "time_cost" + ], + "properties": { + "mem_cost": { + "description": "The amount of memory requested (KB).", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "time_cost": { + "description": "The number of passes.", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Infinite loop to burn cpu cycles (only run when metering is enabled)", + "type": "object", + "required": [ + "cpu_loop" + ], + "properties": { + "cpu_loop": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Infinite loop making storage calls (to test when their limit hits)", + "type": "object", + "required": [ + "storage_loop" + ], + "properties": { + "storage_loop": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Infinite loop reading and writing memory", + "type": "object", + "required": [ + "memory_loop" + ], + "properties": { + "memory_loop": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Infinite loop sending message to itself", + "type": "object", + "required": [ + "message_loop" + ], + "properties": { + "message_loop": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Allocate large amounts of memory without consuming much gas", + "type": "object", + "required": [ + "allocate_large_memory" + ], + "properties": { + "allocate_large_memory": { + "type": "object", + "required": [ + "pages" + ], + "properties": { + "pages": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Trigger a panic to ensure framework handles gracefully", + "type": "object", + "required": [ + "panic" + ], + "properties": { + "panic": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Starting with CosmWasm 0.10, some API calls return user errors back to the contract. This triggers such user errors, ensuring the transaction does not fail in the backend.", + "type": "object", + "required": [ + "user_errors_in_api_calls" + ], + "properties": { + "user_errors_in_api_calls": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "query": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "QueryMsg", + "oneOf": [ + { + "description": "returns a human-readable representation of the verifier use to ensure query path works in integration tests", + "type": "object", + "required": [ + "verifier" + ], + "properties": { + "verifier": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "This returns cosmwasm_std::AllBalanceResponse to demo use of the querier", + "type": "object", + "required": [ + "other_balance" + ], + "properties": { + "other_balance": { + "type": "object", + "required": [ + "address" + ], + "properties": { + "address": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Recurse will execute a query into itself up to depth-times and return Each step of the recursion may perform some extra work to test gas metering (`work` rounds of sha256 on contract). Now that we have Env, we can auto-calculate the address to recurse into", + "type": "object", + "required": [ + "recurse" + ], + "properties": { + "recurse": { + "type": "object", + "required": [ + "depth", + "work" + ], + "properties": { + "depth": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "work": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "GetInt returns a hardcoded u32 value", + "type": "object", + "required": [ + "get_int" + ], + "properties": { + "get_int": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "migrate": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "MigrateMsg", + "description": "MigrateMsg allows a privileged contract administrator to run a migration on the contract. In this (demo) case it is just migrating from one hackatom code to the same code, but taking advantage of the migration step to set a new validator.\n\nNote that the contract doesn't enforce permissions here, this is done by blockchain logic (in the future by blockchain governance)", + "type": "object", + "required": [ + "verifier" + ], + "properties": { + "verifier": { + "type": "string" + } + }, + "additionalProperties": false + }, + "sudo": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "SudoMsg", + "description": "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", + "oneOf": [ + { + "type": "object", + "required": [ + "steal_funds" + ], + "properties": { + "steal_funds": { + "type": "object", + "required": [ + "amount", + "recipient" + ], + "properties": { + "amount": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + }, + "recipient": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ], + "definitions": { + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } + }, + "responses": { + "get_int": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "IntResponse", + "type": "object", + "required": [ + "int" + ], + "properties": { + "int": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + "other_balance": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "AllBalanceResponse", + "type": "object", + "required": [ + "amount" + ], + "properties": { + "amount": { + "description": "Returns all non-zero coins held by this account.", + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + } + }, + "definitions": { + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } + }, + "recurse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "RecurseResponse", + "type": "object", + "required": [ + "hashed" + ], + "properties": { + "hashed": { + "description": "hashed is the result of running sha256 \"work+1\" times on the contract's human address", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + } + }, + "additionalProperties": false, + "definitions": { + "Binary": { + "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec", + "type": "string" + } + } + }, + "verifier": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "VerifierResponse", + "type": "object", + "required": [ + "verifier" + ], + "properties": { + "verifier": { + "type": "string" + } + }, + "additionalProperties": false + } + } +} diff --git a/contracts/hackatom/schema/query_msg.json b/contracts/hackatom/schema/query_msg.json index 8d14b2f322..dfd58202b2 100644 --- a/contracts/hackatom/schema/query_msg.json +++ b/contracts/hackatom/schema/query_msg.json @@ -20,10 +20,10 @@ "description": "This returns cosmwasm_std::AllBalanceResponse to demo use of the querier", "type": "object", "required": [ - "other-balance" + "other_balance" ], "properties": { - "other-balance": { + "other_balance": { "type": "object", "required": [ "address" @@ -72,10 +72,10 @@ "description": "GetInt returns a hardcoded u32 value", "type": "object", "required": [ - "get-int" + "get_int" ], "properties": { - "get-int": { + "get_int": { "type": "object", "additionalProperties": false } diff --git a/packages/schema/src/query_response.rs b/packages/schema/src/query_response.rs index 26b2ed6390..c195e25103 100644 --- a/packages/schema/src/query_response.rs +++ b/packages/schema/src/query_response.rs @@ -1,11 +1,12 @@ use std::collections::BTreeMap; use schemars::{schema::RootSchema, JsonSchema}; +use thiserror::Error; pub use cosmwasm_schema_derive::QueryResponses; pub trait QueryResponses: JsonSchema { - fn response_schemas() -> BTreeMap { + fn response_schemas() -> Result, IntegrityError> { let response_schemas = Self::response_schemas_impl(); let queries: Vec<_> = response_schemas @@ -13,37 +14,59 @@ pub trait QueryResponses: JsonSchema { .map(std::borrow::Borrow::borrow) .collect(); - check_api_integrity::(&queries); + check_api_integrity::(&queries)?; - response_schemas + Ok(response_schemas) } fn response_schemas_impl() -> BTreeMap; } /// `generated_queries` is expected to be a sorted slice here! -fn check_api_integrity(generated_queries: &[&str]) { +fn check_api_integrity( + generated_queries: &[&str], +) -> Result<(), IntegrityError> { let schema = crate::schema_for!(T); + + // something more readable below? + let mut schema_queries: Vec<_> = schema .schema .subschemas - .unwrap() + .ok_or(IntegrityError::InvalidQueryMsgSchema)? .one_of - .unwrap() + .ok_or(IntegrityError::InvalidQueryMsgSchema)? .into_iter() .map(|s| { s.into_object() .object - .unwrap() + .ok_or(IntegrityError::InvalidQueryMsgSchema)? .required .into_iter() .next() - .unwrap() + .ok_or(IntegrityError::InvalidQueryMsgSchema) }) - .collect(); + .collect::>()?; schema_queries.sort(); if schema_queries != generated_queries { - // TODO: errors - panic!("NOES"); + return Err(IntegrityError::InconsistentQueries { + query_msg: schema_queries, + responses: generated_queries.iter().map(ToString::to_string).collect(), + }); } + + Ok(()) +} + +#[derive(Debug, Error)] +pub enum IntegrityError { + #[error("the structure of the QueryMsg schema was unexpected")] + InvalidQueryMsgSchema, + #[error( + "inconsistent queries - QueryMsg schema has {query_msg:?}, but query responses have {responses:?}" + )] + InconsistentQueries { + query_msg: Vec, + responses: Vec, + }, } From 57f20d217ef0f2d5d8fa8fd57841caad2f117f97 Mon Sep 17 00:00:00 2001 From: Tomasz Kurcz Date: Mon, 4 Jul 2022 21:16:36 +0200 Subject: [PATCH 22/41] schema: make `responses` in IDL optional --- contracts/hackatom/examples/schema.rs | 2 +- packages/schema/src/idl.rs | 4 ++-- packages/schema/tests/idl.rs | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/contracts/hackatom/examples/schema.rs b/contracts/hackatom/examples/schema.rs index 9cc7d0921c..ebb67b6feb 100644 --- a/contracts/hackatom/examples/schema.rs +++ b/contracts/hackatom/examples/schema.rs @@ -38,7 +38,7 @@ fn main() { query: Some(schema_for!(QueryMsg)), migrate: Some(schema_for!(MigrateMsg)), sudo: Some(schema_for!(SudoMsg)), - responses: QueryMsg::response_schemas().unwrap(), + responses: Some(QueryMsg::response_schemas().unwrap()), } .render(); let json = api.to_string().unwrap(); diff --git a/packages/schema/src/idl.rs b/packages/schema/src/idl.rs index 470217954b..ebfa163c19 100644 --- a/packages/schema/src/idl.rs +++ b/packages/schema/src/idl.rs @@ -22,7 +22,7 @@ pub struct Api { pub migrate: Option, pub sudo: Option, /// A mapping of query variants to response types - pub responses: BTreeMap, + pub responses: Option>, } impl Api { @@ -78,7 +78,7 @@ pub struct JsonApi { query: Option, migrate: Option, sudo: Option, - responses: BTreeMap, + responses: Option>, } impl JsonApi { diff --git a/packages/schema/tests/idl.rs b/packages/schema/tests/idl.rs index d2897fbe99..fdab94412c 100644 --- a/packages/schema/tests/idl.rs +++ b/packages/schema/tests/idl.rs @@ -47,7 +47,7 @@ fn test_basic_structure() { query: Some(schema_for!(QueryMsg)), migrate: Some(schema_for!(MigrateMsg)), sudo: Some(schema_for!(SudoMsg)), - responses: BTreeMap::from([("balance".to_string(), schema_for!(u128))]), + responses: Some(BTreeMap::from([("balance".to_string(), schema_for!(u128))])), } .render() .to_string() @@ -89,7 +89,7 @@ fn test_query_responses() { query: Some(schema_for!(QueryMsg)), migrate: None, sudo: None, - responses: BTreeMap::from([("balance".to_string(), schema_for!(u128))]), + responses: Some(BTreeMap::from([("balance".to_string(), schema_for!(u128))])), } .render() .to_string() From 8ed00f5549a881e9d7761b0337861ee1686f7763 Mon Sep 17 00:00:00 2001 From: Tomasz Kurcz Date: Mon, 4 Jul 2022 23:06:15 +0200 Subject: [PATCH 23/41] schema-derive: introduce generate_api!() --- packages/schema-derive/src/lib.rs | 224 +++++++++++++++++++++++++++++- packages/schema/tests/idl.rs | 1 + 2 files changed, 223 insertions(+), 2 deletions(-) diff --git a/packages/schema-derive/src/lib.rs b/packages/schema-derive/src/lib.rs index 0201cbc93f..004bee2cbf 100644 --- a/packages/schema-derive/src/lib.rs +++ b/packages/schema-derive/src/lib.rs @@ -1,5 +1,11 @@ -use quote::ToTokens; -use syn::{parse_macro_input, parse_quote, Expr, ExprTuple, ItemEnum, ItemImpl, Type, Variant}; +use std::collections::BTreeMap; + +use quote::{quote, ToTokens}; +use syn::{ + parse::{Parse, ParseStream}, + parse_macro_input, parse_quote, Expr, ExprTuple, Ident, ItemEnum, ItemImpl, Token, Type, + Variant, +}; /// Extract the query -> response mapping out of an enum variant. fn parse_query(v: Variant) -> (String, Expr) { @@ -65,6 +71,151 @@ fn query_responses_derive_impl(input: ItemEnum) -> ItemImpl { } } +#[proc_macro] +pub fn generate_api(input: proc_macro::TokenStream) -> proc_macro::TokenStream { + let input = parse_macro_input!(input as KV); + + let expanded = generate_api_impl(input).into_token_stream(); + + proc_macro::TokenStream::from(expanded) +} + +fn generate_api_impl(input: KV) -> Expr { + let mut input = input.0; + + let name = if let Some(name_override) = input.remove(&parse_quote!(name)) { + let name_override = name_override.unwrap_str(); + quote! { + #name_override.to_string() + } + } else { + quote! { + env!("CARGO_PKG_NAME").to_string() + } + }; + + let version = if let Some(version_override) = input.remove(&parse_quote!(version)) { + let version_override = version_override.unwrap_str(); + quote! { + #version_override.to_string() + } + } else { + quote! { + env!("CARGO_PKG_VERSION").to_string() + } + }; + + let instantiate = input + .remove(&parse_quote!(instantiate)) + .unwrap() + .unwrap_type(); + + let execute = match input.remove(&parse_quote!(execute)) { + Some(ty) => { + let ty = ty.unwrap_type(); + quote! {Some(schema_for!(#ty))} + } + None => quote! { None }, + }; + + let (query, responses) = match input.remove(&parse_quote!(query)) { + Some(ty) => { + let ty = ty.unwrap_type(); + ( + quote! {Some(schema_for!(#ty))}, + quote! { Some(#ty::response_schemas().unwrap()) }, + ) + } + None => (quote! { None }, quote! { None }), + }; + + let migrate = match input.remove(&parse_quote!(migrate)) { + Some(ty) => { + let ty = ty.unwrap_type(); + quote! {Some(schema_for!(#ty))} + } + None => quote! { None }, + }; + + let sudo = match input.remove(&parse_quote!(sudo)) { + Some(ty) => { + let ty = ty.unwrap_type(); + quote! {Some(schema_for!(#ty))} + } + None => quote! { None }, + }; + + parse_quote! { + Api { + contract_name: #name, + contract_version: #version, + instantiate: schema_for!(#instantiate), + execute: #execute, + query: #query, + migrate: #migrate, + sudo: #sudo, + responses: #responses, + } + } +} + +#[derive(Debug)] +enum Value { + Type(syn::Path), + Str(syn::LitStr), +} + +impl Value { + fn unwrap_type(self) -> syn::Path { + if let Self::Type(p) = self { + p + } else { + panic!("expected a type"); + } + } + + fn unwrap_str(self) -> syn::LitStr { + if let Self::Str(s) = self { + s + } else { + panic!("expected a string literal"); + } + } +} + +impl Parse for Value { + fn parse(input: ParseStream) -> syn::parse::Result { + if let Ok(p) = input.parse::() { + Ok(Self::Type(p)) + } else { + Ok(Self::Str(input.parse::()?)) + } + } +} + +#[derive(Debug)] +struct Pair((Ident, Value)); + +impl Parse for Pair { + fn parse(input: ParseStream) -> syn::parse::Result { + let k = input.parse::()?; + input.parse::()?; + let v = input.parse::()?; + + Ok(Self((k, v))) + } +} + +#[derive(Debug)] +struct KV(BTreeMap); + +impl Parse for KV { + fn parse(input: ParseStream) -> syn::parse::Result { + let pairs = input.parse_terminated::(Pair::parse)?; + Ok(Self(pairs.into_iter().map(|p| p.0).collect())) + } +} + #[cfg(test)] mod tests { use syn::parse_quote; @@ -131,4 +282,73 @@ mod tests { assert_eq!(to_snake_case("SnakeCase"), "snake_case"); assert_eq!(to_snake_case("Wasm123AndCo"), "wasm123_and_co"); } + + #[test] + fn generate_api_minimal() { + assert_eq!( + generate_api_impl(parse_quote! { + instantiate: InstantiateMsg, + }), + parse_quote! { + Api { + contract_name: env!("CARGO_PKG_NAME").to_string(), + contract_version: env!("CARGO_PKG_VERSION").to_string(), + instantiate: schema_for!(InstantiateMsg), + execute: None, + query: None, + migrate: None, + sudo: None, + responses: None, + } + } + ); + } + + #[test] + fn generate_api_name_vesion_override() { + assert_eq!( + generate_api_impl(parse_quote! { + name: "foo", + version: "bar", + instantiate: InstantiateMsg, + }), + parse_quote! { + Api { + contract_name: "foo".to_string(), + contract_version: "bar".to_string(), + instantiate: schema_for!(InstantiateMsg), + execute: None, + query: None, + migrate: None, + sudo: None, + responses: None, + } + } + ); + } + + #[test] + fn generate_api_all_msgs() { + assert_eq!( + generate_api_impl(parse_quote! { + instantiate: InstantiateMsg, + execute: ExecuteMsg, + query: QueryMsg, + migrate: MigrateMsg, + sudo: SudoMsg, + }), + parse_quote! { + Api { + contract_name: env!("CARGO_PKG_NAME").to_string(), + contract_version: env!("CARGO_PKG_VERSION").to_string(), + instantiate: schema_for!(InstantiateMsg), + execute: Some(schema_for!(ExecuteMsg)), + query: Some(schema_for!(QueryMsg)), + migrate: Some(schema_for!(MigrateMsg)), + sudo: Some(schema_for!(SudoMsg)), + responses: Some(QueryMsg::response_schemas().unwrap()), + } + } + ); + } } diff --git a/packages/schema/tests/idl.rs b/packages/schema/tests/idl.rs index fdab94412c..4e8ba42bd3 100644 --- a/packages/schema/tests/idl.rs +++ b/packages/schema/tests/idl.rs @@ -1,6 +1,7 @@ use std::collections::{BTreeMap, HashMap}; use cosmwasm_schema::{schema_for, Api, IDL_VERSION}; +use cosmwasm_schema_derive::generate_api; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use serde_json::Value; From de786c4a10abe1e00821f56202a3495b7d400f85 Mon Sep 17 00:00:00 2001 From: Tomasz Kurcz Date: Mon, 4 Jul 2022 23:32:48 +0200 Subject: [PATCH 24/41] schema-derive: Cargo.toml fix --- packages/schema-derive/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/schema-derive/Cargo.toml b/packages/schema-derive/Cargo.toml index 904796c4cc..bf9b5d01c1 100644 --- a/packages/schema-derive/Cargo.toml +++ b/packages/schema-derive/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "cosmwasm-schema-derive" version = "1.0.0" -author = "Tomasz Kurcz " +authors = ["Tomasz Kurcz "] edition = "2021" description = "Derive macros for cosmwasm-schema" repository = "https://github.com/CosmWasm/cosmwasm/tree/main/packages/schema" From c238af7f47aff1da2625b1c902c6f1fbb3f0da56 Mon Sep 17 00:00:00 2001 From: Tomasz Kurcz Date: Mon, 4 Jul 2022 23:39:08 +0200 Subject: [PATCH 25/41] hackatom: migrate to the generate_api!() macro --- contracts/hackatom/examples/schema.rs | 20 +++++++++----------- packages/schema/src/lib.rs | 1 + 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/contracts/hackatom/examples/schema.rs b/contracts/hackatom/examples/schema.rs index ebb67b6feb..e21155115e 100644 --- a/contracts/hackatom/examples/schema.rs +++ b/contracts/hackatom/examples/schema.rs @@ -1,7 +1,9 @@ use std::env::current_dir; use std::fs::{create_dir_all, write}; -use cosmwasm_schema::{export_schema, remove_schemas, schema_for, Api, QueryResponses}; +use cosmwasm_schema::{ + export_schema, generate_api, remove_schemas, schema_for, Api, QueryResponses, +}; use cosmwasm_std::BalanceResponse; use hackatom::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg, SudoMsg, VerifierResponse}; @@ -26,19 +28,15 @@ fn main() { export_schema(&schema_for!(State), &out_dir); let contract_name = env!("CARGO_PKG_NAME"); - let contract_version = env!("CARGO_PKG_VERSION"); // The new IDL let path = out_dir.join(format!("{}.json", contract_name)); - let api = Api { - contract_name: contract_name.to_string(), - contract_version: contract_version.to_string(), - instantiate: schema_for!(InstantiateMsg), - execute: Some(schema_for!(ExecuteMsg)), - query: Some(schema_for!(QueryMsg)), - migrate: Some(schema_for!(MigrateMsg)), - sudo: Some(schema_for!(SudoMsg)), - responses: Some(QueryMsg::response_schemas().unwrap()), + let api = generate_api! { + instantiate: InstantiateMsg, + query: QueryMsg, + execute: ExecuteMsg, + sudo: SudoMsg, + migrate: MigrateMsg, } .render(); let json = api.to_string().unwrap(); diff --git a/packages/schema/src/lib.rs b/packages/schema/src/lib.rs index 7d4ef180e7..9640583463 100644 --- a/packages/schema/src/lib.rs +++ b/packages/schema/src/lib.rs @@ -10,4 +10,5 @@ pub use query_response::QueryResponses; pub use remove::remove_schemas; // Re-exports +pub use cosmwasm_schema_derive::generate_api; pub use schemars::schema_for; From 11022aad1aa6928c97378e985baf01e54a8d3367 Mon Sep 17 00:00:00 2001 From: Tomasz Kurcz Date: Mon, 4 Jul 2022 23:42:28 +0200 Subject: [PATCH 26/41] clippy --- packages/schema/tests/idl.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/schema/tests/idl.rs b/packages/schema/tests/idl.rs index 4e8ba42bd3..fdab94412c 100644 --- a/packages/schema/tests/idl.rs +++ b/packages/schema/tests/idl.rs @@ -1,7 +1,6 @@ use std::collections::{BTreeMap, HashMap}; use cosmwasm_schema::{schema_for, Api, IDL_VERSION}; -use cosmwasm_schema_derive::generate_api; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use serde_json::Value; From 6c8a7e672a7f6956bee58e91a66fd978b7fcf298 Mon Sep 17 00:00:00 2001 From: Tomasz Kurcz Date: Mon, 4 Jul 2022 23:56:13 +0200 Subject: [PATCH 27/41] schema: simplify tests --- packages/schema/tests/idl.rs | 36 +++++++++++++++--------------------- 1 file changed, 15 insertions(+), 21 deletions(-) diff --git a/packages/schema/tests/idl.rs b/packages/schema/tests/idl.rs index fdab94412c..801d568b1a 100644 --- a/packages/schema/tests/idl.rs +++ b/packages/schema/tests/idl.rs @@ -1,6 +1,6 @@ -use std::collections::{BTreeMap, HashMap}; +use std::collections::HashMap; -use cosmwasm_schema::{schema_for, Api, IDL_VERSION}; +use cosmwasm_schema::{generate_api, schema_for, Api, QueryResponses, IDL_VERSION}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use serde_json::Value; @@ -19,9 +19,10 @@ pub enum ExecuteMsg { Mint { amount: u128 }, } -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema, QueryResponses)] #[serde(rename_all = "snake_case")] pub enum QueryMsg { + #[returns(u128)] Balance { account: String }, } @@ -39,15 +40,14 @@ pub struct MigrateMsg { #[test] fn test_basic_structure() { - let api_str = Api { - contract_name: "test".to_string(), - contract_version: "0.1.0".to_string(), - instantiate: schema_for!(InstantiateMsg), - execute: Some(schema_for!(ExecuteMsg)), - query: Some(schema_for!(QueryMsg)), - migrate: Some(schema_for!(MigrateMsg)), - sudo: Some(schema_for!(SudoMsg)), - responses: Some(BTreeMap::from([("balance".to_string(), schema_for!(u128))])), + let api_str = generate_api! { + name: "test", + version: "0.1.0", + instantiate: InstantiateMsg, + query: QueryMsg, + execute: ExecuteMsg, + sudo: SudoMsg, + migrate: MigrateMsg, } .render() .to_string() @@ -81,15 +81,9 @@ fn test_basic_structure() { #[test] fn test_query_responses() { - let api_str = Api { - contract_name: "test".to_string(), - contract_version: "0.1.0".to_string(), - instantiate: schema_for!(InstantiateMsg), - execute: Some(schema_for!(ExecuteMsg)), - query: Some(schema_for!(QueryMsg)), - migrate: None, - sudo: None, - responses: Some(BTreeMap::from([("balance".to_string(), schema_for!(u128))])), + let api_str = generate_api! { + instantiate: InstantiateMsg, + query: QueryMsg, } .render() .to_string() From c0e2c07d77f61f2402836466efc1b4511997e884 Mon Sep 17 00:00:00 2001 From: Tomasz Kurcz Date: Tue, 5 Jul 2022 14:42:06 +0200 Subject: [PATCH 28/41] schema: QueryResponse tests --- packages/schema/src/query_response.rs | 76 ++++++++++++++++++++++----- 1 file changed, 63 insertions(+), 13 deletions(-) diff --git a/packages/schema/src/query_response.rs b/packages/schema/src/query_response.rs index c195e25103..871cb62d4b 100644 --- a/packages/schema/src/query_response.rs +++ b/packages/schema/src/query_response.rs @@ -1,4 +1,4 @@ -use std::collections::BTreeMap; +use std::collections::{BTreeMap, BTreeSet}; use schemars::{schema::RootSchema, JsonSchema}; use thiserror::Error; @@ -9,12 +9,9 @@ pub trait QueryResponses: JsonSchema { fn response_schemas() -> Result, IntegrityError> { let response_schemas = Self::response_schemas_impl(); - let queries: Vec<_> = response_schemas - .keys() - .map(std::borrow::Borrow::borrow) - .collect(); + let queries: BTreeSet<_> = response_schemas.keys().cloned().collect(); - check_api_integrity::(&queries)?; + check_api_integrity::(queries)?; Ok(response_schemas) } @@ -24,13 +21,13 @@ pub trait QueryResponses: JsonSchema { /// `generated_queries` is expected to be a sorted slice here! fn check_api_integrity( - generated_queries: &[&str], + generated_queries: BTreeSet, ) -> Result<(), IntegrityError> { let schema = crate::schema_for!(T); // something more readable below? - let mut schema_queries: Vec<_> = schema + let schema_queries: BTreeSet<_> = schema .schema .subschemas .ok_or(IntegrityError::InvalidQueryMsgSchema)? @@ -47,18 +44,17 @@ fn check_api_integrity( .ok_or(IntegrityError::InvalidQueryMsgSchema) }) .collect::>()?; - schema_queries.sort(); if schema_queries != generated_queries { return Err(IntegrityError::InconsistentQueries { query_msg: schema_queries, - responses: generated_queries.iter().map(ToString::to_string).collect(), + responses: generated_queries, }); } Ok(()) } -#[derive(Debug, Error)] +#[derive(Debug, Error, PartialEq)] pub enum IntegrityError { #[error("the structure of the QueryMsg schema was unexpected")] InvalidQueryMsgSchema, @@ -66,7 +62,61 @@ pub enum IntegrityError { "inconsistent queries - QueryMsg schema has {query_msg:?}, but query responses have {responses:?}" )] InconsistentQueries { - query_msg: Vec, - responses: Vec, + query_msg: BTreeSet, + responses: BTreeSet, }, } + +#[cfg(test)] +mod tests { + use schemars::schema_for; + + use super::*; + + #[derive(Debug, JsonSchema)] + #[serde(rename_all = "snake_case")] + #[allow(dead_code)] + pub enum GoodMsg { + BalanceFor { account: String }, + } + + impl QueryResponses for GoodMsg { + fn response_schemas_impl() -> BTreeMap { + BTreeMap::from([("balance_for".to_string(), schema_for!(u128))]) + } + } + + #[test] + fn good_msg_works() { + let response_schemas = GoodMsg::response_schemas().unwrap(); + assert_eq!( + response_schemas, + BTreeMap::from([("balance_for".to_string(), schema_for!(u128))]) + ); + } + + #[derive(Debug, JsonSchema)] + #[serde(rename_all = "kebab-case")] + #[allow(dead_code)] + pub enum BadMsg { + BalanceFor { account: String }, + } + + impl QueryResponses for BadMsg { + fn response_schemas_impl() -> BTreeMap { + BTreeMap::from([("balance_for".to_string(), schema_for!(u128))]) + } + } + + #[test] + fn bad_msg_fails() { + let err = BadMsg::response_schemas().unwrap_err(); + assert_eq!( + err, + IntegrityError::InconsistentQueries { + query_msg: BTreeSet::from(["balance-for".to_string()]), + responses: BTreeSet::from(["balance_for".to_string()]) + } + ); + } +} From 09c55ad676b3562bf361408621bc7051fd38d02b Mon Sep 17 00:00:00 2001 From: Tomasz Kurcz Date: Tue, 5 Jul 2022 14:54:12 +0200 Subject: [PATCH 29/41] schema-derive: refactor --- packages/schema-derive/src/generate_api.rs | 217 +++++++++++ packages/schema-derive/src/lib.rs | 345 +----------------- packages/schema-derive/src/query_responses.rs | 124 +++++++ 3 files changed, 348 insertions(+), 338 deletions(-) create mode 100644 packages/schema-derive/src/generate_api.rs create mode 100644 packages/schema-derive/src/query_responses.rs diff --git a/packages/schema-derive/src/generate_api.rs b/packages/schema-derive/src/generate_api.rs new file mode 100644 index 0000000000..ec2db4ba5b --- /dev/null +++ b/packages/schema-derive/src/generate_api.rs @@ -0,0 +1,217 @@ +use std::collections::BTreeMap; + +use quote::quote; +use syn::{ + parse::{Parse, ParseStream}, + parse_quote, Expr, Ident, Token, +}; + +pub fn generate_api_impl(input: KV) -> Expr { + let mut input = input.0; + + let name = if let Some(name_override) = input.remove(&parse_quote!(name)) { + let name_override = name_override.unwrap_str(); + quote! { + #name_override.to_string() + } + } else { + quote! { + env!("CARGO_PKG_NAME").to_string() + } + }; + + let version = if let Some(version_override) = input.remove(&parse_quote!(version)) { + let version_override = version_override.unwrap_str(); + quote! { + #version_override.to_string() + } + } else { + quote! { + env!("CARGO_PKG_VERSION").to_string() + } + }; + + let instantiate = input + .remove(&parse_quote!(instantiate)) + .unwrap() + .unwrap_type(); + + let execute = match input.remove(&parse_quote!(execute)) { + Some(ty) => { + let ty = ty.unwrap_type(); + quote! {Some(schema_for!(#ty))} + } + None => quote! { None }, + }; + + let (query, responses) = match input.remove(&parse_quote!(query)) { + Some(ty) => { + let ty = ty.unwrap_type(); + ( + quote! {Some(schema_for!(#ty))}, + quote! { Some(#ty::response_schemas().unwrap()) }, + ) + } + None => (quote! { None }, quote! { None }), + }; + + let migrate = match input.remove(&parse_quote!(migrate)) { + Some(ty) => { + let ty = ty.unwrap_type(); + quote! {Some(schema_for!(#ty))} + } + None => quote! { None }, + }; + + let sudo = match input.remove(&parse_quote!(sudo)) { + Some(ty) => { + let ty = ty.unwrap_type(); + quote! {Some(schema_for!(#ty))} + } + None => quote! { None }, + }; + + parse_quote! { + Api { + contract_name: #name, + contract_version: #version, + instantiate: schema_for!(#instantiate), + execute: #execute, + query: #query, + migrate: #migrate, + sudo: #sudo, + responses: #responses, + } + } +} + +#[derive(Debug)] +enum Value { + Type(syn::Path), + Str(syn::LitStr), +} + +impl Value { + fn unwrap_type(self) -> syn::Path { + if let Self::Type(p) = self { + p + } else { + panic!("expected a type"); + } + } + + fn unwrap_str(self) -> syn::LitStr { + if let Self::Str(s) = self { + s + } else { + panic!("expected a string literal"); + } + } +} + +impl Parse for Value { + fn parse(input: ParseStream) -> syn::parse::Result { + if let Ok(p) = input.parse::() { + Ok(Self::Type(p)) + } else { + Ok(Self::Str(input.parse::()?)) + } + } +} + +#[derive(Debug)] +struct Pair((Ident, Value)); + +impl Parse for Pair { + fn parse(input: ParseStream) -> syn::parse::Result { + let k = input.parse::()?; + input.parse::()?; + let v = input.parse::()?; + + Ok(Self((k, v))) + } +} + +#[derive(Debug)] +pub struct KV(BTreeMap); + +impl Parse for KV { + fn parse(input: ParseStream) -> syn::parse::Result { + let pairs = input.parse_terminated::(Pair::parse)?; + Ok(Self(pairs.into_iter().map(|p| p.0).collect())) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn generate_api_minimal() { + assert_eq!( + generate_api_impl(parse_quote! { + instantiate: InstantiateMsg, + }), + parse_quote! { + Api { + contract_name: env!("CARGO_PKG_NAME").to_string(), + contract_version: env!("CARGO_PKG_VERSION").to_string(), + instantiate: schema_for!(InstantiateMsg), + execute: None, + query: None, + migrate: None, + sudo: None, + responses: None, + } + } + ); + } + + #[test] + fn generate_api_name_vesion_override() { + assert_eq!( + generate_api_impl(parse_quote! { + name: "foo", + version: "bar", + instantiate: InstantiateMsg, + }), + parse_quote! { + Api { + contract_name: "foo".to_string(), + contract_version: "bar".to_string(), + instantiate: schema_for!(InstantiateMsg), + execute: None, + query: None, + migrate: None, + sudo: None, + responses: None, + } + } + ); + } + + #[test] + fn generate_api_all_msgs() { + assert_eq!( + generate_api_impl(parse_quote! { + instantiate: InstantiateMsg, + execute: ExecuteMsg, + query: QueryMsg, + migrate: MigrateMsg, + sudo: SudoMsg, + }), + parse_quote! { + Api { + contract_name: env!("CARGO_PKG_NAME").to_string(), + contract_version: env!("CARGO_PKG_VERSION").to_string(), + instantiate: schema_for!(InstantiateMsg), + execute: Some(schema_for!(ExecuteMsg)), + query: Some(schema_for!(QueryMsg)), + migrate: Some(schema_for!(MigrateMsg)), + sudo: Some(schema_for!(SudoMsg)), + responses: Some(QueryMsg::response_schemas().unwrap()), + } + } + ); + } +} diff --git a/packages/schema-derive/src/lib.rs b/packages/schema-derive/src/lib.rs index 004bee2cbf..d844d9bb1f 100644 --- a/packages/schema-derive/src/lib.rs +++ b/packages/schema-derive/src/lib.rs @@ -1,354 +1,23 @@ -use std::collections::BTreeMap; +mod generate_api; +mod query_responses; -use quote::{quote, ToTokens}; -use syn::{ - parse::{Parse, ParseStream}, - parse_macro_input, parse_quote, Expr, ExprTuple, Ident, ItemEnum, ItemImpl, Token, Type, - Variant, -}; - -/// Extract the query -> response mapping out of an enum variant. -fn parse_query(v: Variant) -> (String, Expr) { - let query = to_snake_case(&v.ident.to_string()); - let response_ty: Type = v - .attrs - .iter() - .find(|a| a.path.get_ident().unwrap() == "returns") - .unwrap() - .parse_args() - .unwrap(); - - ( - query, - parse_quote!(cosmwasm_schema::schema_for!(#response_ty)), - ) -} - -fn parse_tuple((q, r): (String, Expr)) -> ExprTuple { - parse_quote! { - (#q.to_string(), #r) - } -} - -fn to_snake_case(input: &str) -> String { - // this was stolen from serde for consistent behavior - let mut snake = String::new(); - for (i, ch) in input.char_indices() { - if i > 0 && ch.is_uppercase() { - snake.push('_'); - } - snake.push(ch.to_ascii_lowercase()); - } - snake -} +use quote::ToTokens; +use syn::{parse_macro_input, ItemEnum}; #[proc_macro_derive(QueryResponses, attributes(returns))] pub fn query_responses_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream { let input = parse_macro_input!(input as ItemEnum); - let expanded = query_responses_derive_impl(input).into_token_stream(); + let expanded = query_responses::query_responses_derive_impl(input).into_token_stream(); proc_macro::TokenStream::from(expanded) } -fn query_responses_derive_impl(input: ItemEnum) -> ItemImpl { - let ident = input.ident; - let mappings = input.variants.into_iter().map(parse_query); - let mut queries: Vec<_> = mappings.clone().map(|(q, _)| q).collect(); - queries.sort(); - let mappings = mappings.map(parse_tuple); - - parse_quote! { - #[automatically_derived] - #[cfg(not(target_arch = "wasm32"))] - impl cosmwasm_schema::QueryResponses for #ident { - fn response_schemas_impl() -> std::collections::BTreeMap { - std::collections::BTreeMap::from([ - #( #mappings, )* - ]) - } - } - } -} - #[proc_macro] pub fn generate_api(input: proc_macro::TokenStream) -> proc_macro::TokenStream { - let input = parse_macro_input!(input as KV); + let input = parse_macro_input!(input as generate_api::KV); - let expanded = generate_api_impl(input).into_token_stream(); + let expanded = generate_api::generate_api_impl(input).into_token_stream(); proc_macro::TokenStream::from(expanded) } - -fn generate_api_impl(input: KV) -> Expr { - let mut input = input.0; - - let name = if let Some(name_override) = input.remove(&parse_quote!(name)) { - let name_override = name_override.unwrap_str(); - quote! { - #name_override.to_string() - } - } else { - quote! { - env!("CARGO_PKG_NAME").to_string() - } - }; - - let version = if let Some(version_override) = input.remove(&parse_quote!(version)) { - let version_override = version_override.unwrap_str(); - quote! { - #version_override.to_string() - } - } else { - quote! { - env!("CARGO_PKG_VERSION").to_string() - } - }; - - let instantiate = input - .remove(&parse_quote!(instantiate)) - .unwrap() - .unwrap_type(); - - let execute = match input.remove(&parse_quote!(execute)) { - Some(ty) => { - let ty = ty.unwrap_type(); - quote! {Some(schema_for!(#ty))} - } - None => quote! { None }, - }; - - let (query, responses) = match input.remove(&parse_quote!(query)) { - Some(ty) => { - let ty = ty.unwrap_type(); - ( - quote! {Some(schema_for!(#ty))}, - quote! { Some(#ty::response_schemas().unwrap()) }, - ) - } - None => (quote! { None }, quote! { None }), - }; - - let migrate = match input.remove(&parse_quote!(migrate)) { - Some(ty) => { - let ty = ty.unwrap_type(); - quote! {Some(schema_for!(#ty))} - } - None => quote! { None }, - }; - - let sudo = match input.remove(&parse_quote!(sudo)) { - Some(ty) => { - let ty = ty.unwrap_type(); - quote! {Some(schema_for!(#ty))} - } - None => quote! { None }, - }; - - parse_quote! { - Api { - contract_name: #name, - contract_version: #version, - instantiate: schema_for!(#instantiate), - execute: #execute, - query: #query, - migrate: #migrate, - sudo: #sudo, - responses: #responses, - } - } -} - -#[derive(Debug)] -enum Value { - Type(syn::Path), - Str(syn::LitStr), -} - -impl Value { - fn unwrap_type(self) -> syn::Path { - if let Self::Type(p) = self { - p - } else { - panic!("expected a type"); - } - } - - fn unwrap_str(self) -> syn::LitStr { - if let Self::Str(s) = self { - s - } else { - panic!("expected a string literal"); - } - } -} - -impl Parse for Value { - fn parse(input: ParseStream) -> syn::parse::Result { - if let Ok(p) = input.parse::() { - Ok(Self::Type(p)) - } else { - Ok(Self::Str(input.parse::()?)) - } - } -} - -#[derive(Debug)] -struct Pair((Ident, Value)); - -impl Parse for Pair { - fn parse(input: ParseStream) -> syn::parse::Result { - let k = input.parse::()?; - input.parse::()?; - let v = input.parse::()?; - - Ok(Self((k, v))) - } -} - -#[derive(Debug)] -struct KV(BTreeMap); - -impl Parse for KV { - fn parse(input: ParseStream) -> syn::parse::Result { - let pairs = input.parse_terminated::(Pair::parse)?; - Ok(Self(pairs.into_iter().map(|p| p.0).collect())) - } -} - -#[cfg(test)] -mod tests { - use syn::parse_quote; - - use super::*; - - #[test] - fn happy_path() { - let input: ItemEnum = parse_quote! { - #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema, QueryResponses)] - #[serde(rename_all = "snake_case")] - pub enum QueryMsg { - #[returns(some_crate::AnotherType)] - Supply {}, - #[returns(SomeType)] - Balance {}, - } - }; - - assert_eq!( - query_responses_derive_impl(input), - parse_quote! { - #[automatically_derived] - #[cfg(not(target_arch = "wasm32"))] - impl cosmwasm_schema::QueryResponses for QueryMsg { - fn response_schemas_impl() -> std::collections::BTreeMap { - std::collections::BTreeMap::from([ - ("supply".to_string(), cosmwasm_schema::schema_for!(some_crate::AnotherType)), - ("balance".to_string(), cosmwasm_schema::schema_for!(SomeType)), - ]) - } - } - } - ); - } - - #[test] - fn parse_query_works() { - let variant = parse_quote! { - #[returns(Foo)] - GetFoo {} - }; - - assert_eq!( - parse_tuple(parse_query(variant)), - parse_quote! { - ("get_foo".to_string(), cosmwasm_schema::schema_for!(Foo)) - } - ); - - let variant = parse_quote! { - #[returns(some_crate::Foo)] - GetFoo {} - }; - - assert_eq!( - parse_tuple(parse_query(variant)), - parse_quote! { ("get_foo".to_string(), cosmwasm_schema::schema_for!(some_crate::Foo)) } - ); - } - - #[test] - fn to_snake_case_works() { - assert_eq!(to_snake_case("SnakeCase"), "snake_case"); - assert_eq!(to_snake_case("Wasm123AndCo"), "wasm123_and_co"); - } - - #[test] - fn generate_api_minimal() { - assert_eq!( - generate_api_impl(parse_quote! { - instantiate: InstantiateMsg, - }), - parse_quote! { - Api { - contract_name: env!("CARGO_PKG_NAME").to_string(), - contract_version: env!("CARGO_PKG_VERSION").to_string(), - instantiate: schema_for!(InstantiateMsg), - execute: None, - query: None, - migrate: None, - sudo: None, - responses: None, - } - } - ); - } - - #[test] - fn generate_api_name_vesion_override() { - assert_eq!( - generate_api_impl(parse_quote! { - name: "foo", - version: "bar", - instantiate: InstantiateMsg, - }), - parse_quote! { - Api { - contract_name: "foo".to_string(), - contract_version: "bar".to_string(), - instantiate: schema_for!(InstantiateMsg), - execute: None, - query: None, - migrate: None, - sudo: None, - responses: None, - } - } - ); - } - - #[test] - fn generate_api_all_msgs() { - assert_eq!( - generate_api_impl(parse_quote! { - instantiate: InstantiateMsg, - execute: ExecuteMsg, - query: QueryMsg, - migrate: MigrateMsg, - sudo: SudoMsg, - }), - parse_quote! { - Api { - contract_name: env!("CARGO_PKG_NAME").to_string(), - contract_version: env!("CARGO_PKG_VERSION").to_string(), - instantiate: schema_for!(InstantiateMsg), - execute: Some(schema_for!(ExecuteMsg)), - query: Some(schema_for!(QueryMsg)), - migrate: Some(schema_for!(MigrateMsg)), - sudo: Some(schema_for!(SudoMsg)), - responses: Some(QueryMsg::response_schemas().unwrap()), - } - } - ); - } -} diff --git a/packages/schema-derive/src/query_responses.rs b/packages/schema-derive/src/query_responses.rs new file mode 100644 index 0000000000..973ac7434a --- /dev/null +++ b/packages/schema-derive/src/query_responses.rs @@ -0,0 +1,124 @@ +use syn::{parse_quote, Expr, ExprTuple, ItemEnum, ItemImpl, Type, Variant}; + +pub fn query_responses_derive_impl(input: ItemEnum) -> ItemImpl { + let ident = input.ident; + let mappings = input.variants.into_iter().map(parse_query); + let mut queries: Vec<_> = mappings.clone().map(|(q, _)| q).collect(); + queries.sort(); + let mappings = mappings.map(parse_tuple); + + parse_quote! { + #[automatically_derived] + #[cfg(not(target_arch = "wasm32"))] + impl cosmwasm_schema::QueryResponses for #ident { + fn response_schemas_impl() -> std::collections::BTreeMap { + std::collections::BTreeMap::from([ + #( #mappings, )* + ]) + } + } + } +} + +/// Extract the query -> response mapping out of an enum variant. +fn parse_query(v: Variant) -> (String, Expr) { + let query = to_snake_case(&v.ident.to_string()); + let response_ty: Type = v + .attrs + .iter() + .find(|a| a.path.get_ident().unwrap() == "returns") + .unwrap() + .parse_args() + .unwrap(); + + ( + query, + parse_quote!(cosmwasm_schema::schema_for!(#response_ty)), + ) +} + +fn parse_tuple((q, r): (String, Expr)) -> ExprTuple { + parse_quote! { + (#q.to_string(), #r) + } +} + +fn to_snake_case(input: &str) -> String { + // this was stolen from serde for consistent behavior + let mut snake = String::new(); + for (i, ch) in input.char_indices() { + if i > 0 && ch.is_uppercase() { + snake.push('_'); + } + snake.push(ch.to_ascii_lowercase()); + } + snake +} + +#[cfg(test)] +mod tests { + use syn::parse_quote; + + use super::*; + + #[test] + fn happy_path() { + let input: ItemEnum = parse_quote! { + #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema, QueryResponses)] + #[serde(rename_all = "snake_case")] + pub enum QueryMsg { + #[returns(some_crate::AnotherType)] + Supply {}, + #[returns(SomeType)] + Balance {}, + } + }; + + assert_eq!( + query_responses_derive_impl(input), + parse_quote! { + #[automatically_derived] + #[cfg(not(target_arch = "wasm32"))] + impl cosmwasm_schema::QueryResponses for QueryMsg { + fn response_schemas_impl() -> std::collections::BTreeMap { + std::collections::BTreeMap::from([ + ("supply".to_string(), cosmwasm_schema::schema_for!(some_crate::AnotherType)), + ("balance".to_string(), cosmwasm_schema::schema_for!(SomeType)), + ]) + } + } + } + ); + } + + #[test] + fn parse_query_works() { + let variant = parse_quote! { + #[returns(Foo)] + GetFoo {} + }; + + assert_eq!( + parse_tuple(parse_query(variant)), + parse_quote! { + ("get_foo".to_string(), cosmwasm_schema::schema_for!(Foo)) + } + ); + + let variant = parse_quote! { + #[returns(some_crate::Foo)] + GetFoo {} + }; + + assert_eq!( + parse_tuple(parse_query(variant)), + parse_quote! { ("get_foo".to_string(), cosmwasm_schema::schema_for!(some_crate::Foo)) } + ); + } + + #[test] + fn to_snake_case_works() { + assert_eq!(to_snake_case("SnakeCase"), "snake_case"); + assert_eq!(to_snake_case("Wasm123AndCo"), "wasm123_and_co"); + } +} From ba50c97590de393f484ee850f823d8e22fa90755 Mon Sep 17 00:00:00 2001 From: Tomasz Kurcz Date: Tue, 5 Jul 2022 15:24:00 +0200 Subject: [PATCH 30/41] schema: doc comments for proc macros --- packages/schema/src/lib.rs | 31 +++++++++++++++++++++++++++ packages/schema/src/query_response.rs | 23 ++++++++++++++++++++ 2 files changed, 54 insertions(+) diff --git a/packages/schema/src/lib.rs b/packages/schema/src/lib.rs index 9640583463..323cf7cf0a 100644 --- a/packages/schema/src/lib.rs +++ b/packages/schema/src/lib.rs @@ -10,5 +10,36 @@ pub use query_response::QueryResponses; pub use remove::remove_schemas; // Re-exports +/// Generates an [`Api`](schema::Api) for the contract. The body describes the message +/// types exported in the schema and allows setting contract name and version overrides. +/// +/// The only obligatory field is `instantiate` - to set the InstantiateMsg type. +/// +/// # Available fields +/// - `name` - contract name +/// - `version` - contract version +/// - `instantiate` - instantiate msg type +/// - `query` - query msg type +/// - `execute` - execute msg type +/// - `migrate` - migrate msg type +/// - `sudo` - sudo msg type +/// +/// # Example +/// ``` +/// use cosmwasm_schema::{generate_api, Api}; +/// use schemars::{schema_for, JsonSchema}; +/// +/// #[derive(JsonSchema)] +/// struct InstantiateMsg; +/// +/// #[derive(JsonSchema)] +/// struct MigrateMsg; +/// +/// let api = generate_api! { +/// name: "cw20", +/// instantiate: InstantiateMsg, +/// migrate: MigrateMsg, +/// }; +/// ``` pub use cosmwasm_schema_derive::generate_api; pub use schemars::schema_for; diff --git a/packages/schema/src/query_response.rs b/packages/schema/src/query_response.rs index 871cb62d4b..f35570ba59 100644 --- a/packages/schema/src/query_response.rs +++ b/packages/schema/src/query_response.rs @@ -5,6 +5,29 @@ use thiserror::Error; pub use cosmwasm_schema_derive::QueryResponses; +/// A trait for tying QueryMsg variants (different contract queries) to their response types. +/// This is mostly useful for the generated contracted API description when using `cargo schema`. +/// +/// Using the derive macro is the preferred way of implementing this trait. +/// +/// # Example +/// ``` +/// use cosmwasm_schema::QueryResponses; +/// use schemars::JsonSchema; +/// +/// #[derive(JsonSchema)] +/// struct AccountInfo { +/// IcqHandle: String, +/// } +/// +/// #[derive(JsonSchema, QueryResponses)] +/// enum QueryMsg { +/// #[returns(Vec)] +/// Denoms {}, +/// #[returns(AccountInfo)] +/// AccountInfo { account: String }, +/// } +/// ``` pub trait QueryResponses: JsonSchema { fn response_schemas() -> Result, IntegrityError> { let response_schemas = Self::response_schemas_impl(); From 5c89ee91b624b1e3cdc89ab72e8698f801a655c9 Mon Sep 17 00:00:00 2001 From: Tomasz Kurcz Date: Tue, 5 Jul 2022 16:37:17 +0200 Subject: [PATCH 31/41] schema-derive: go nuts with boilerplate removal --- contracts/hackatom/examples/schema.rs | 38 +--- .../hackatom/schema/balance_response.json | 39 ---- contracts/hackatom/schema/execute_msg.json | 160 -------------- .../hackatom/schema/instantiate_msg.json | 18 -- contracts/hackatom/schema/migrate_msg.json | 15 -- contracts/hackatom/schema/query_msg.json | 86 -------- contracts/hackatom/schema/state.json | 27 --- contracts/hackatom/schema/sudo_msg.json | 56 ----- .../hackatom/schema/verifier_response.json | 14 -- packages/schema-derive/src/generate_api.rs | 208 ++++++++++++------ packages/schema-derive/src/lib.rs | 2 +- 11 files changed, 143 insertions(+), 520 deletions(-) delete mode 100644 contracts/hackatom/schema/balance_response.json delete mode 100644 contracts/hackatom/schema/execute_msg.json delete mode 100644 contracts/hackatom/schema/instantiate_msg.json delete mode 100644 contracts/hackatom/schema/migrate_msg.json delete mode 100644 contracts/hackatom/schema/query_msg.json delete mode 100644 contracts/hackatom/schema/state.json delete mode 100644 contracts/hackatom/schema/sudo_msg.json delete mode 100644 contracts/hackatom/schema/verifier_response.json diff --git a/contracts/hackatom/examples/schema.rs b/contracts/hackatom/examples/schema.rs index e21155115e..c94b584511 100644 --- a/contracts/hackatom/examples/schema.rs +++ b/contracts/hackatom/examples/schema.rs @@ -1,45 +1,13 @@ -use std::env::current_dir; -use std::fs::{create_dir_all, write}; +use cosmwasm_schema::generate_api; -use cosmwasm_schema::{ - export_schema, generate_api, remove_schemas, schema_for, Api, QueryResponses, -}; -use cosmwasm_std::BalanceResponse; - -use hackatom::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg, SudoMsg, VerifierResponse}; -use hackatom::state::State; +use hackatom::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg, SudoMsg}; fn main() { - let mut out_dir = current_dir().unwrap(); - out_dir.push("schema"); - create_dir_all(&out_dir).unwrap(); - remove_schemas(&out_dir).unwrap(); - - // messages - export_schema(&schema_for!(InstantiateMsg), &out_dir); - export_schema(&schema_for!(ExecuteMsg), &out_dir); - export_schema(&schema_for!(MigrateMsg), &out_dir); - export_schema(&schema_for!(SudoMsg), &out_dir); - export_schema(&schema_for!(QueryMsg), &out_dir); - export_schema(&schema_for!(VerifierResponse), &out_dir); - export_schema(&schema_for!(BalanceResponse), &out_dir); - - // state - export_schema(&schema_for!(State), &out_dir); - - let contract_name = env!("CARGO_PKG_NAME"); - - // The new IDL - let path = out_dir.join(format!("{}.json", contract_name)); - let api = generate_api! { + generate_api! { instantiate: InstantiateMsg, query: QueryMsg, execute: ExecuteMsg, sudo: SudoMsg, migrate: MigrateMsg, } - .render(); - let json = api.to_string().unwrap(); - write(&path, json + "\n").unwrap(); - println!("Exported the full API as {}", path.to_str().unwrap()); } diff --git a/contracts/hackatom/schema/balance_response.json b/contracts/hackatom/schema/balance_response.json deleted file mode 100644 index 4e3b16aa4a..0000000000 --- a/contracts/hackatom/schema/balance_response.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "BalanceResponse", - "type": "object", - "required": [ - "amount" - ], - "properties": { - "amount": { - "description": "Always returns a Coin with the requested denom. This may be of 0 amount if no such funds.", - "allOf": [ - { - "$ref": "#/definitions/Coin" - } - ] - } - }, - "definitions": { - "Coin": { - "type": "object", - "required": [ - "amount", - "denom" - ], - "properties": { - "amount": { - "$ref": "#/definitions/Uint128" - }, - "denom": { - "type": "string" - } - } - }, - "Uint128": { - "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", - "type": "string" - } - } -} diff --git a/contracts/hackatom/schema/execute_msg.json b/contracts/hackatom/schema/execute_msg.json deleted file mode 100644 index 0bddc48c7e..0000000000 --- a/contracts/hackatom/schema/execute_msg.json +++ /dev/null @@ -1,160 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "ExecuteMsg", - "oneOf": [ - { - "description": "Releasing all funds in the contract to the beneficiary. This is the only \"proper\" action of this demo contract.", - "type": "object", - "required": [ - "release" - ], - "properties": { - "release": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Hashes some data. Uses CPU and memory, but no external calls.", - "type": "object", - "required": [ - "argon2" - ], - "properties": { - "argon2": { - "type": "object", - "required": [ - "mem_cost", - "time_cost" - ], - "properties": { - "mem_cost": { - "description": "The amount of memory requested (KB).", - "type": "integer", - "format": "uint32", - "minimum": 0.0 - }, - "time_cost": { - "description": "The number of passes.", - "type": "integer", - "format": "uint32", - "minimum": 0.0 - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Infinite loop to burn cpu cycles (only run when metering is enabled)", - "type": "object", - "required": [ - "cpu_loop" - ], - "properties": { - "cpu_loop": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Infinite loop making storage calls (to test when their limit hits)", - "type": "object", - "required": [ - "storage_loop" - ], - "properties": { - "storage_loop": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Infinite loop reading and writing memory", - "type": "object", - "required": [ - "memory_loop" - ], - "properties": { - "memory_loop": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Infinite loop sending message to itself", - "type": "object", - "required": [ - "message_loop" - ], - "properties": { - "message_loop": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Allocate large amounts of memory without consuming much gas", - "type": "object", - "required": [ - "allocate_large_memory" - ], - "properties": { - "allocate_large_memory": { - "type": "object", - "required": [ - "pages" - ], - "properties": { - "pages": { - "type": "integer", - "format": "uint32", - "minimum": 0.0 - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Trigger a panic to ensure framework handles gracefully", - "type": "object", - "required": [ - "panic" - ], - "properties": { - "panic": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Starting with CosmWasm 0.10, some API calls return user errors back to the contract. This triggers such user errors, ensuring the transaction does not fail in the backend.", - "type": "object", - "required": [ - "user_errors_in_api_calls" - ], - "properties": { - "user_errors_in_api_calls": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - } - ] -} diff --git a/contracts/hackatom/schema/instantiate_msg.json b/contracts/hackatom/schema/instantiate_msg.json deleted file mode 100644 index 8639103d34..0000000000 --- a/contracts/hackatom/schema/instantiate_msg.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "InstantiateMsg", - "type": "object", - "required": [ - "beneficiary", - "verifier" - ], - "properties": { - "beneficiary": { - "type": "string" - }, - "verifier": { - "type": "string" - } - }, - "additionalProperties": false -} diff --git a/contracts/hackatom/schema/migrate_msg.json b/contracts/hackatom/schema/migrate_msg.json deleted file mode 100644 index 2696e3ac26..0000000000 --- a/contracts/hackatom/schema/migrate_msg.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "MigrateMsg", - "description": "MigrateMsg allows a privileged contract administrator to run a migration on the contract. In this (demo) case it is just migrating from one hackatom code to the same code, but taking advantage of the migration step to set a new validator.\n\nNote that the contract doesn't enforce permissions here, this is done by blockchain logic (in the future by blockchain governance)", - "type": "object", - "required": [ - "verifier" - ], - "properties": { - "verifier": { - "type": "string" - } - }, - "additionalProperties": false -} diff --git a/contracts/hackatom/schema/query_msg.json b/contracts/hackatom/schema/query_msg.json deleted file mode 100644 index dfd58202b2..0000000000 --- a/contracts/hackatom/schema/query_msg.json +++ /dev/null @@ -1,86 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "QueryMsg", - "oneOf": [ - { - "description": "returns a human-readable representation of the verifier use to ensure query path works in integration tests", - "type": "object", - "required": [ - "verifier" - ], - "properties": { - "verifier": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "This returns cosmwasm_std::AllBalanceResponse to demo use of the querier", - "type": "object", - "required": [ - "other_balance" - ], - "properties": { - "other_balance": { - "type": "object", - "required": [ - "address" - ], - "properties": { - "address": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Recurse will execute a query into itself up to depth-times and return Each step of the recursion may perform some extra work to test gas metering (`work` rounds of sha256 on contract). Now that we have Env, we can auto-calculate the address to recurse into", - "type": "object", - "required": [ - "recurse" - ], - "properties": { - "recurse": { - "type": "object", - "required": [ - "depth", - "work" - ], - "properties": { - "depth": { - "type": "integer", - "format": "uint32", - "minimum": 0.0 - }, - "work": { - "type": "integer", - "format": "uint32", - "minimum": 0.0 - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "GetInt returns a hardcoded u32 value", - "type": "object", - "required": [ - "get_int" - ], - "properties": { - "get_int": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - } - ] -} diff --git a/contracts/hackatom/schema/state.json b/contracts/hackatom/schema/state.json deleted file mode 100644 index 8462e9fb34..0000000000 --- a/contracts/hackatom/schema/state.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "State", - "type": "object", - "required": [ - "beneficiary", - "funder", - "verifier" - ], - "properties": { - "beneficiary": { - "$ref": "#/definitions/Addr" - }, - "funder": { - "$ref": "#/definitions/Addr" - }, - "verifier": { - "$ref": "#/definitions/Addr" - } - }, - "definitions": { - "Addr": { - "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", - "type": "string" - } - } -} diff --git a/contracts/hackatom/schema/sudo_msg.json b/contracts/hackatom/schema/sudo_msg.json deleted file mode 100644 index d41253aeb0..0000000000 --- a/contracts/hackatom/schema/sudo_msg.json +++ /dev/null @@ -1,56 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "SudoMsg", - "description": "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", - "oneOf": [ - { - "type": "object", - "required": [ - "steal_funds" - ], - "properties": { - "steal_funds": { - "type": "object", - "required": [ - "amount", - "recipient" - ], - "properties": { - "amount": { - "type": "array", - "items": { - "$ref": "#/definitions/Coin" - } - }, - "recipient": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - } - ], - "definitions": { - "Coin": { - "type": "object", - "required": [ - "amount", - "denom" - ], - "properties": { - "amount": { - "$ref": "#/definitions/Uint128" - }, - "denom": { - "type": "string" - } - } - }, - "Uint128": { - "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", - "type": "string" - } - } -} diff --git a/contracts/hackatom/schema/verifier_response.json b/contracts/hackatom/schema/verifier_response.json deleted file mode 100644 index fb04c86d67..0000000000 --- a/contracts/hackatom/schema/verifier_response.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "VerifierResponse", - "type": "object", - "required": [ - "verifier" - ], - "properties": { - "verifier": { - "type": "string" - } - }, - "additionalProperties": false -} diff --git a/packages/schema-derive/src/generate_api.rs b/packages/schema-derive/src/generate_api.rs index ec2db4ba5b..03b5a87ecf 100644 --- a/packages/schema-derive/src/generate_api.rs +++ b/packages/schema-derive/src/generate_api.rs @@ -1,80 +1,55 @@ use std::collections::BTreeMap; +use proc_macro2::TokenStream; use quote::quote; use syn::{ parse::{Parse, ParseStream}, - parse_quote, Expr, Ident, Token, + parse_quote, Block, ExprStruct, Ident, Path, Token, }; -pub fn generate_api_impl(input: KV) -> Expr { - let mut input = input.0; +pub fn generate_api_impl(input: Options) -> Block { + let api_object = api_object(&input); + let name = input.name; - let name = if let Some(name_override) = input.remove(&parse_quote!(name)) { - let name_override = name_override.unwrap_str(); - quote! { - #name_override.to_string() - } - } else { - quote! { - env!("CARGO_PKG_NAME").to_string() - } - }; + parse_quote! { + { + use std::env::current_dir; + use std::fs::{create_dir_all, write}; - let version = if let Some(version_override) = input.remove(&parse_quote!(version)) { - let version_override = version_override.unwrap_str(); - quote! { - #version_override.to_string() - } - } else { - quote! { - env!("CARGO_PKG_VERSION").to_string() - } - }; + use cosmwasm_schema::{remove_schemas, schema_for, Api, QueryResponses}; - let instantiate = input - .remove(&parse_quote!(instantiate)) - .unwrap() - .unwrap_type(); + let mut out_dir = current_dir().unwrap(); + out_dir.push("schema"); + create_dir_all(&out_dir).unwrap(); + remove_schemas(&out_dir).unwrap(); - let execute = match input.remove(&parse_quote!(execute)) { - Some(ty) => { - let ty = ty.unwrap_type(); - quote! {Some(schema_for!(#ty))} - } - None => quote! { None }, - }; - - let (query, responses) = match input.remove(&parse_quote!(query)) { - Some(ty) => { - let ty = ty.unwrap_type(); - ( - quote! {Some(schema_for!(#ty))}, - quote! { Some(#ty::response_schemas().unwrap()) }, - ) - } - None => (quote! { None }, quote! { None }), - }; + let path = out_dir.join(concat!(#name, ".json")); - let migrate = match input.remove(&parse_quote!(migrate)) { - Some(ty) => { - let ty = ty.unwrap_type(); - quote! {Some(schema_for!(#ty))} - } - None => quote! { None }, - }; + let api = #api_object.render(); - let sudo = match input.remove(&parse_quote!(sudo)) { - Some(ty) => { - let ty = ty.unwrap_type(); - quote! {Some(schema_for!(#ty))} + let json = api.to_string().unwrap(); + write(&path, json + "\n").unwrap(); + println!("Exported the full API as {}", path.to_str().unwrap()); } - None => quote! { None }, - }; + } +} + +fn api_object(input: &Options) -> ExprStruct { + let Options { + name, + version, + instantiate, + execute, + query, + migrate, + sudo, + responses, + } = input; parse_quote! { Api { - contract_name: #name, - contract_version: #version, + contract_name: #name.to_string(), + contract_version: #version.to_string(), instantiate: schema_for!(#instantiate), execute: #execute, query: #query, @@ -133,12 +108,98 @@ impl Parse for Pair { } #[derive(Debug)] -pub struct KV(BTreeMap); +pub struct Options { + name: TokenStream, + version: TokenStream, + instantiate: Path, + execute: TokenStream, + query: TokenStream, + migrate: TokenStream, + sudo: TokenStream, + responses: TokenStream, +} -impl Parse for KV { +impl Parse for Options { fn parse(input: ParseStream) -> syn::parse::Result { let pairs = input.parse_terminated::(Pair::parse)?; - Ok(Self(pairs.into_iter().map(|p| p.0).collect())) + let mut map: BTreeMap<_, _> = pairs.into_iter().map(|p| p.0).collect(); + + let name = if let Some(name_override) = map.remove(&parse_quote!(name)) { + let name_override = name_override.unwrap_str(); + quote! { + #name_override + } + } else { + quote! { + env!("CARGO_PKG_NAME") + } + }; + + let version = if let Some(version_override) = map.remove(&parse_quote!(version)) { + let version_override = version_override.unwrap_str(); + quote! { + #version_override + } + } else { + quote! { + env!("CARGO_PKG_VERSION") + } + }; + + let instantiate = map + .remove(&parse_quote!(instantiate)) + .unwrap() + .unwrap_type(); + + let execute = match map.remove(&parse_quote!(execute)) { + Some(ty) => { + let ty = ty.unwrap_type(); + quote! {Some(schema_for!(#ty))} + } + None => quote! { None }, + }; + + let (query, responses) = match map.remove(&parse_quote!(query)) { + Some(ty) => { + let ty = ty.unwrap_type(); + ( + quote! {Some(schema_for!(#ty))}, + quote! { Some(#ty::response_schemas().unwrap()) }, + ) + } + None => (quote! { None }, quote! { None }), + }; + + let migrate = match map.remove(&parse_quote!(migrate)) { + Some(ty) => { + let ty = ty.unwrap_type(); + quote! {Some(schema_for!(#ty))} + } + None => quote! { None }, + }; + + let sudo = match map.remove(&parse_quote!(sudo)) { + Some(ty) => { + let ty = ty.unwrap_type(); + quote! {Some(schema_for!(#ty))} + } + None => quote! { None }, + }; + + if let Some((invalid_option, _)) = map.into_iter().next() { + panic!("unknown generate_api option: {}", invalid_option); + } + + Ok(Self { + name, + version, + instantiate, + execute, + query, + migrate, + sudo, + responses, + }) } } @@ -147,9 +208,9 @@ mod tests { use super::*; #[test] - fn generate_api_minimal() { + fn api_object_minimal() { assert_eq!( - generate_api_impl(parse_quote! { + api_object(&parse_quote! { instantiate: InstantiateMsg, }), parse_quote! { @@ -168,9 +229,9 @@ mod tests { } #[test] - fn generate_api_name_vesion_override() { + fn api_object_name_vesion_override() { assert_eq!( - generate_api_impl(parse_quote! { + api_object(&parse_quote! { name: "foo", version: "bar", instantiate: InstantiateMsg, @@ -191,9 +252,9 @@ mod tests { } #[test] - fn generate_api_all_msgs() { + fn api_object_all_msgs() { assert_eq!( - generate_api_impl(parse_quote! { + api_object(&parse_quote! { instantiate: InstantiateMsg, execute: ExecuteMsg, query: QueryMsg, @@ -214,4 +275,13 @@ mod tests { } ); } + + #[test] + #[should_panic(expected = "unknown generate_api option: asd")] + fn invalid_option() { + let _options: Options = parse_quote! { + instantiate: InstantiateMsg, + asd: Asd, + }; + } } diff --git a/packages/schema-derive/src/lib.rs b/packages/schema-derive/src/lib.rs index d844d9bb1f..b5bc339ca6 100644 --- a/packages/schema-derive/src/lib.rs +++ b/packages/schema-derive/src/lib.rs @@ -15,7 +15,7 @@ pub fn query_responses_derive(input: proc_macro::TokenStream) -> proc_macro::Tok #[proc_macro] pub fn generate_api(input: proc_macro::TokenStream) -> proc_macro::TokenStream { - let input = parse_macro_input!(input as generate_api::KV); + let input = parse_macro_input!(input as generate_api::Options); let expanded = generate_api::generate_api_impl(input).into_token_stream(); From 3a355073e84da83536c0d9feb9a869878dbab2c1 Mon Sep 17 00:00:00 2001 From: Tomasz Kurcz Date: Tue, 5 Jul 2022 17:05:56 +0200 Subject: [PATCH 32/41] schema-derive: assorted fixes --- packages/schema-derive/src/generate_api.rs | 36 ++++++++++---------- packages/schema-derive/src/lib.rs | 9 +++++ packages/schema/schema/cw20.json | 19 +++++++++++ packages/schema/src/lib.rs | 38 ++++++++++++++++++++-- packages/schema/tests/idl.rs | 6 ++-- 5 files changed, 84 insertions(+), 24 deletions(-) create mode 100644 packages/schema/schema/cw20.json diff --git a/packages/schema-derive/src/generate_api.rs b/packages/schema-derive/src/generate_api.rs index 03b5a87ecf..09d6bf81a8 100644 --- a/packages/schema-derive/src/generate_api.rs +++ b/packages/schema-derive/src/generate_api.rs @@ -16,7 +16,7 @@ pub fn generate_api_impl(input: Options) -> Block { use std::env::current_dir; use std::fs::{create_dir_all, write}; - use cosmwasm_schema::{remove_schemas, schema_for, Api, QueryResponses}; + use cosmwasm_schema::{remove_schemas, Api, QueryResponses}; let mut out_dir = current_dir().unwrap(); out_dir.push("schema"); @@ -34,7 +34,7 @@ pub fn generate_api_impl(input: Options) -> Block { } } -fn api_object(input: &Options) -> ExprStruct { +pub fn api_object(input: &Options) -> ExprStruct { let Options { name, version, @@ -47,10 +47,10 @@ fn api_object(input: &Options) -> ExprStruct { } = input; parse_quote! { - Api { + cosmwasm_schema::Api { contract_name: #name.to_string(), contract_version: #version.to_string(), - instantiate: schema_for!(#instantiate), + instantiate: cosmwasm_schema::schema_for!(#instantiate), execute: #execute, query: #query, migrate: #migrate, @@ -154,7 +154,7 @@ impl Parse for Options { let execute = match map.remove(&parse_quote!(execute)) { Some(ty) => { let ty = ty.unwrap_type(); - quote! {Some(schema_for!(#ty))} + quote! {Some(cosmwasm_schema::schema_for!(#ty))} } None => quote! { None }, }; @@ -163,7 +163,7 @@ impl Parse for Options { Some(ty) => { let ty = ty.unwrap_type(); ( - quote! {Some(schema_for!(#ty))}, + quote! {Some(cosmwasm_schema::schema_for!(#ty))}, quote! { Some(#ty::response_schemas().unwrap()) }, ) } @@ -173,7 +173,7 @@ impl Parse for Options { let migrate = match map.remove(&parse_quote!(migrate)) { Some(ty) => { let ty = ty.unwrap_type(); - quote! {Some(schema_for!(#ty))} + quote! {Some(cosmwasm_schema::schema_for!(#ty))} } None => quote! { None }, }; @@ -181,7 +181,7 @@ impl Parse for Options { let sudo = match map.remove(&parse_quote!(sudo)) { Some(ty) => { let ty = ty.unwrap_type(); - quote! {Some(schema_for!(#ty))} + quote! {Some(cosmwasm_schema::schema_for!(#ty))} } None => quote! { None }, }; @@ -214,10 +214,10 @@ mod tests { instantiate: InstantiateMsg, }), parse_quote! { - Api { + cosmwasm_schema::Api { contract_name: env!("CARGO_PKG_NAME").to_string(), contract_version: env!("CARGO_PKG_VERSION").to_string(), - instantiate: schema_for!(InstantiateMsg), + instantiate: cosmwasm_schema::schema_for!(InstantiateMsg), execute: None, query: None, migrate: None, @@ -237,10 +237,10 @@ mod tests { instantiate: InstantiateMsg, }), parse_quote! { - Api { + cosmwasm_schema::Api { contract_name: "foo".to_string(), contract_version: "bar".to_string(), - instantiate: schema_for!(InstantiateMsg), + instantiate: cosmwasm_schema::schema_for!(InstantiateMsg), execute: None, query: None, migrate: None, @@ -262,14 +262,14 @@ mod tests { sudo: SudoMsg, }), parse_quote! { - Api { + cosmwasm_schema::Api { contract_name: env!("CARGO_PKG_NAME").to_string(), contract_version: env!("CARGO_PKG_VERSION").to_string(), - instantiate: schema_for!(InstantiateMsg), - execute: Some(schema_for!(ExecuteMsg)), - query: Some(schema_for!(QueryMsg)), - migrate: Some(schema_for!(MigrateMsg)), - sudo: Some(schema_for!(SudoMsg)), + instantiate: cosmwasm_schema::schema_for!(InstantiateMsg), + execute: Some(cosmwasm_schema::schema_for!(ExecuteMsg)), + query: Some(cosmwasm_schema::schema_for!(QueryMsg)), + migrate: Some(cosmwasm_schema::schema_for!(MigrateMsg)), + sudo: Some(cosmwasm_schema::schema_for!(SudoMsg)), responses: Some(QueryMsg::response_schemas().unwrap()), } } diff --git a/packages/schema-derive/src/lib.rs b/packages/schema-derive/src/lib.rs index b5bc339ca6..ff77a3cfd3 100644 --- a/packages/schema-derive/src/lib.rs +++ b/packages/schema-derive/src/lib.rs @@ -21,3 +21,12 @@ pub fn generate_api(input: proc_macro::TokenStream) -> proc_macro::TokenStream { proc_macro::TokenStream::from(expanded) } + +#[proc_macro] +pub fn generate_api_obj(input: proc_macro::TokenStream) -> proc_macro::TokenStream { + let input = parse_macro_input!(input as generate_api::Options); + + let expanded = generate_api::api_object(&input).into_token_stream(); + + proc_macro::TokenStream::from(expanded) +} diff --git a/packages/schema/schema/cw20.json b/packages/schema/schema/cw20.json new file mode 100644 index 0000000000..8481724aa1 --- /dev/null +++ b/packages/schema/schema/cw20.json @@ -0,0 +1,19 @@ +{ + "contract_name": "cw20", + "contract_version": "1.0.0", + "idl_version": "1.0.0", + "instantiate": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "InstantiateMsg", + "type": "null" + }, + "execute": null, + "query": null, + "migrate": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "MigrateMsg", + "type": "null" + }, + "sudo": null, + "responses": null +} diff --git a/packages/schema/src/lib.rs b/packages/schema/src/lib.rs index 323cf7cf0a..2455167cd5 100644 --- a/packages/schema/src/lib.rs +++ b/packages/schema/src/lib.rs @@ -26,8 +26,8 @@ pub use remove::remove_schemas; /// /// # Example /// ``` -/// use cosmwasm_schema::{generate_api, Api}; -/// use schemars::{schema_for, JsonSchema}; +/// use cosmwasm_schema::{generate_api}; +/// use schemars::{JsonSchema}; /// /// #[derive(JsonSchema)] /// struct InstantiateMsg; @@ -35,11 +35,43 @@ pub use remove::remove_schemas; /// #[derive(JsonSchema)] /// struct MigrateMsg; /// -/// let api = generate_api! { +/// generate_api! { /// name: "cw20", /// instantiate: InstantiateMsg, /// migrate: MigrateMsg, /// }; /// ``` pub use cosmwasm_schema_derive::generate_api; +/// Generates an [`Api`](schema::Api) for the contract. The body describes the message +/// types exported in the schema and allows setting contract name and version overrides. +/// +/// The only obligatory field is `instantiate` - to set the InstantiateMsg type. +/// +/// # Available fields +/// - `name` - contract name +/// - `version` - contract version +/// - `instantiate` - instantiate msg type +/// - `query` - query msg type +/// - `execute` - execute msg type +/// - `migrate` - migrate msg type +/// - `sudo` - sudo msg type +/// +/// # Example +/// ``` +/// use cosmwasm_schema::{generate_api_obj}; +/// use schemars::{JsonSchema}; +/// +/// #[derive(JsonSchema)] +/// struct InstantiateMsg; +/// +/// #[derive(JsonSchema)] +/// struct MigrateMsg; +/// +/// let api = generate_api_obj! { +/// name: "cw20", +/// instantiate: InstantiateMsg, +/// migrate: MigrateMsg, +/// }.render(); +/// ``` +pub use cosmwasm_schema_derive::generate_api_obj; pub use schemars::schema_for; diff --git a/packages/schema/tests/idl.rs b/packages/schema/tests/idl.rs index 801d568b1a..3f312cd88e 100644 --- a/packages/schema/tests/idl.rs +++ b/packages/schema/tests/idl.rs @@ -1,6 +1,6 @@ use std::collections::HashMap; -use cosmwasm_schema::{generate_api, schema_for, Api, QueryResponses, IDL_VERSION}; +use cosmwasm_schema::{generate_api_obj, QueryResponses, IDL_VERSION}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use serde_json::Value; @@ -40,7 +40,7 @@ pub struct MigrateMsg { #[test] fn test_basic_structure() { - let api_str = generate_api! { + let api_str = generate_api_obj! { name: "test", version: "0.1.0", instantiate: InstantiateMsg, @@ -81,7 +81,7 @@ fn test_basic_structure() { #[test] fn test_query_responses() { - let api_str = generate_api! { + let api_str = generate_api_obj! { instantiate: InstantiateMsg, query: QueryMsg, } From 998dfbe3dd4336c6951580ffa6e642eb5a0bf887 Mon Sep 17 00:00:00 2001 From: Tomasz Kurcz Date: Tue, 5 Jul 2022 17:13:33 +0200 Subject: [PATCH 33/41] schema: improve proc macro doc comments --- packages/schema/src/lib.rs | 26 ++++++++++---------------- 1 file changed, 10 insertions(+), 16 deletions(-) diff --git a/packages/schema/src/lib.rs b/packages/schema/src/lib.rs index 2455167cd5..04baf276ab 100644 --- a/packages/schema/src/lib.rs +++ b/packages/schema/src/lib.rs @@ -10,19 +10,19 @@ pub use query_response::QueryResponses; pub use remove::remove_schemas; // Re-exports -/// Generates an [`Api`](schema::Api) for the contract. The body describes the message -/// types exported in the schema and allows setting contract name and version overrides. +/// 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. /// /// The only obligatory field is `instantiate` - to set the InstantiateMsg type. /// /// # Available fields -/// - `name` - contract name -/// - `version` - contract version +/// - `name` - contract name, crate name by default +/// - `version` - contract version, crate version by default /// - `instantiate` - instantiate msg type -/// - `query` - query msg type -/// - `execute` - execute msg type -/// - `migrate` - migrate msg type -/// - `sudo` - sudo msg type +/// - `query` - query msg type, empty by default +/// - `execute` - execute msg type, empty by default +/// - `migrate` - migrate msg type, empty by default +/// - `sudo` - sudo msg type, empty by default /// /// # Example /// ``` @@ -42,19 +42,13 @@ pub use remove::remove_schemas; /// }; /// ``` pub use cosmwasm_schema_derive::generate_api; -/// Generates an [`Api`](schema::Api) for the contract. The body describes the message +/// 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. /// /// The only obligatory field is `instantiate` - to set the InstantiateMsg type. /// /// # Available fields -/// - `name` - contract name -/// - `version` - contract version -/// - `instantiate` - instantiate msg type -/// - `query` - query msg type -/// - `execute` - execute msg type -/// - `migrate` - migrate msg type -/// - `sudo` - sudo msg type +/// See [`generate_api`](crate::generate_api). /// /// # Example /// ``` From 90d8355c11138219fd1ffccb9c7f6f0d63955c0d Mon Sep 17 00:00:00 2001 From: Tomasz Kurcz Date: Tue, 5 Jul 2022 17:15:27 +0200 Subject: [PATCH 34/41] schema: doc comment style --- packages/schema/src/idl.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/schema/src/idl.rs b/packages/schema/src/idl.rs index ebfa163c19..8297d6f191 100644 --- a/packages/schema/src/idl.rs +++ b/packages/schema/src/idl.rs @@ -7,9 +7,8 @@ use thiserror::Error; /// The version of the CosmWasm IDL. /// -/// Follows Semantic Versioning 2.0.0: https://semver.org/ -/// -/// To determine if a change is breaking, assume consumers allow unknown fields. +/// Follows Semantic Versioning 2.0.0: +// To determine if a change is breaking, assume consumers allow unknown fields and bump accordingly. pub const IDL_VERSION: &str = "1.0.0"; /// Rust representation of a contract's API. From 163a9d7ddc852540d67912f6a9e33516b424f11e Mon Sep 17 00:00:00 2001 From: Tomasz Kurcz Date: Wed, 6 Jul 2022 18:18:56 +0200 Subject: [PATCH 35/41] schema-derive: panic msg tests --- packages/schema-derive/src/query_responses.rs | 37 ++++++++++++++++++- packages/schema/src/query_response.rs | 11 +++++- 2 files changed, 44 insertions(+), 4 deletions(-) diff --git a/packages/schema-derive/src/query_responses.rs b/packages/schema-derive/src/query_responses.rs index 973ac7434a..bdffae96dd 100644 --- a/packages/schema-derive/src/query_responses.rs +++ b/packages/schema-derive/src/query_responses.rs @@ -27,9 +27,9 @@ fn parse_query(v: Variant) -> (String, Expr) { .attrs .iter() .find(|a| a.path.get_ident().unwrap() == "returns") - .unwrap() + .unwrap_or_else(|| panic!("missing return type for query: {}", v.ident)) .parse_args() - .unwrap(); + .unwrap_or_else(|_| panic!("return for {} must be a type", v.ident)); ( query, @@ -91,6 +91,39 @@ mod tests { ); } + #[test] + #[should_panic(expected = "missing return type for query: Supply")] + fn missing_return() { + let input: ItemEnum = parse_quote! { + #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema, QueryResponses)] + #[serde(rename_all = "snake_case")] + pub enum QueryMsg { + Supply {}, + #[returns(SomeType)] + Balance {}, + } + }; + + query_responses_derive_impl(input); + } + + #[test] + #[should_panic(expected = "return for Supply must be a type")] + fn invalid_return() { + let input: ItemEnum = parse_quote! { + #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema, QueryResponses)] + #[serde(rename_all = "snake_case")] + pub enum QueryMsg { + #[returns(1)] + Supply {}, + #[returns(SomeType)] + Balance {}, + } + }; + + query_responses_derive_impl(input); + } + #[test] fn parse_query_works() { let variant = parse_quote! { diff --git a/packages/schema/src/query_response.rs b/packages/schema/src/query_response.rs index f35570ba59..29162c1695 100644 --- a/packages/schema/src/query_response.rs +++ b/packages/schema/src/query_response.rs @@ -101,11 +101,15 @@ mod tests { #[allow(dead_code)] pub enum GoodMsg { BalanceFor { account: String }, + Supply {}, } impl QueryResponses for GoodMsg { fn response_schemas_impl() -> BTreeMap { - BTreeMap::from([("balance_for".to_string(), schema_for!(u128))]) + BTreeMap::from([ + ("balance_for".to_string(), schema_for!(u128)), + ("supply".to_string(), schema_for!(u128)), + ]) } } @@ -114,7 +118,10 @@ mod tests { let response_schemas = GoodMsg::response_schemas().unwrap(); assert_eq!( response_schemas, - BTreeMap::from([("balance_for".to_string(), schema_for!(u128))]) + BTreeMap::from([ + ("balance_for".to_string(), schema_for!(u128)), + ("supply".to_string(), schema_for!(u128)) + ]) ); } From 88a8f5936ac4ed157221065756c9f7e0f7026856 Mon Sep 17 00:00:00 2001 From: Tomasz Kurcz Date: Mon, 11 Jul 2022 13:02:01 +0200 Subject: [PATCH 36/41] put `schema-derive` in separate codecov flag --- codecov.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/codecov.yml b/codecov.yml index d873594098..ca6f4a47d7 100644 --- a/codecov.yml +++ b/codecov.yml @@ -25,6 +25,7 @@ flags: cosmwasm-schema: paths: - packages/schema/ + cosmwasm-schema-derive: - packages/schema-derive/ cosmwasm-std: paths: From ce17cf4ae7050df63f92f96343ee3541866b521a Mon Sep 17 00:00:00 2001 From: Tomasz Kurcz Date: Mon, 11 Jul 2022 13:07:56 +0200 Subject: [PATCH 37/41] schema: rename API generation macros for clarity --- contracts/hackatom/examples/schema.rs | 4 +-- packages/schema-derive/src/generate_api.rs | 12 ++++---- packages/schema-derive/src/lib.rs | 8 ++--- packages/schema/src/lib.rs | 36 +++++++++++----------- packages/schema/tests/idl.rs | 6 ++-- 5 files changed, 33 insertions(+), 33 deletions(-) diff --git a/contracts/hackatom/examples/schema.rs b/contracts/hackatom/examples/schema.rs index c94b584511..e2fa27d802 100644 --- a/contracts/hackatom/examples/schema.rs +++ b/contracts/hackatom/examples/schema.rs @@ -1,9 +1,9 @@ -use cosmwasm_schema::generate_api; +use cosmwasm_schema::write_api; use hackatom::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg, SudoMsg}; fn main() { - generate_api! { + write_api! { instantiate: InstantiateMsg, query: QueryMsg, execute: ExecuteMsg, diff --git a/packages/schema-derive/src/generate_api.rs b/packages/schema-derive/src/generate_api.rs index 09d6bf81a8..70a61c4f22 100644 --- a/packages/schema-derive/src/generate_api.rs +++ b/packages/schema-derive/src/generate_api.rs @@ -7,8 +7,8 @@ use syn::{ parse_quote, Block, ExprStruct, Ident, Path, Token, }; -pub fn generate_api_impl(input: Options) -> Block { - let api_object = api_object(&input); +pub fn write_api_impl(input: Options) -> Block { + let api_object = generate_api_impl(&input); let name = input.name; parse_quote! { @@ -34,7 +34,7 @@ pub fn generate_api_impl(input: Options) -> Block { } } -pub fn api_object(input: &Options) -> ExprStruct { +pub fn generate_api_impl(input: &Options) -> ExprStruct { let Options { name, version, @@ -210,7 +210,7 @@ mod tests { #[test] fn api_object_minimal() { assert_eq!( - api_object(&parse_quote! { + generate_api_impl(&parse_quote! { instantiate: InstantiateMsg, }), parse_quote! { @@ -231,7 +231,7 @@ mod tests { #[test] fn api_object_name_vesion_override() { assert_eq!( - api_object(&parse_quote! { + generate_api_impl(&parse_quote! { name: "foo", version: "bar", instantiate: InstantiateMsg, @@ -254,7 +254,7 @@ mod tests { #[test] fn api_object_all_msgs() { assert_eq!( - api_object(&parse_quote! { + generate_api_impl(&parse_quote! { instantiate: InstantiateMsg, execute: ExecuteMsg, query: QueryMsg, diff --git a/packages/schema-derive/src/lib.rs b/packages/schema-derive/src/lib.rs index ff77a3cfd3..61b8321567 100644 --- a/packages/schema-derive/src/lib.rs +++ b/packages/schema-derive/src/lib.rs @@ -14,19 +14,19 @@ pub fn query_responses_derive(input: proc_macro::TokenStream) -> proc_macro::Tok } #[proc_macro] -pub fn generate_api(input: proc_macro::TokenStream) -> proc_macro::TokenStream { +pub fn write_api(input: proc_macro::TokenStream) -> proc_macro::TokenStream { let input = parse_macro_input!(input as generate_api::Options); - let expanded = generate_api::generate_api_impl(input).into_token_stream(); + let expanded = generate_api::write_api_impl(input).into_token_stream(); proc_macro::TokenStream::from(expanded) } #[proc_macro] -pub fn generate_api_obj(input: proc_macro::TokenStream) -> proc_macro::TokenStream { +pub fn generate_api(input: proc_macro::TokenStream) -> proc_macro::TokenStream { let input = parse_macro_input!(input as generate_api::Options); - let expanded = generate_api::api_object(&input).into_token_stream(); + let expanded = generate_api::generate_api_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 04baf276ab..415acf59fd 100644 --- a/packages/schema/src/lib.rs +++ b/packages/schema/src/lib.rs @@ -10,19 +10,13 @@ pub use query_response::QueryResponses; pub use remove::remove_schemas; // Re-exports -/// 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. +/// 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. /// /// The only obligatory field is `instantiate` - to set the InstantiateMsg type. /// /// # Available fields -/// - `name` - contract name, crate name by default -/// - `version` - contract version, crate version by default -/// - `instantiate` - instantiate msg type -/// - `query` - query msg type, empty by default -/// - `execute` - execute msg type, empty by default -/// - `migrate` - migrate msg type, empty by default -/// - `sudo` - sudo msg type, empty by default +/// See [`write_api`](crate::write_api). /// /// # Example /// ``` @@ -35,24 +29,30 @@ pub use remove::remove_schemas; /// #[derive(JsonSchema)] /// struct MigrateMsg; /// -/// generate_api! { +/// let api = generate_api! { /// name: "cw20", /// instantiate: InstantiateMsg, /// migrate: MigrateMsg, -/// }; +/// }.render(); /// ``` pub use cosmwasm_schema_derive::generate_api; -/// 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. +/// 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. /// /// The only obligatory field is `instantiate` - to set the InstantiateMsg type. /// /// # Available fields -/// See [`generate_api`](crate::generate_api). +/// - `name` - contract name, crate name by default +/// - `version` - contract version, crate version by default +/// - `instantiate` - instantiate msg type +/// - `query` - query msg type, empty by default +/// - `execute` - execute msg type, empty by default +/// - `migrate` - migrate msg type, empty by default +/// - `sudo` - sudo msg type, empty by default /// /// # Example /// ``` -/// use cosmwasm_schema::{generate_api_obj}; +/// use cosmwasm_schema::{write_api}; /// use schemars::{JsonSchema}; /// /// #[derive(JsonSchema)] @@ -61,11 +61,11 @@ pub use cosmwasm_schema_derive::generate_api; /// #[derive(JsonSchema)] /// struct MigrateMsg; /// -/// let api = generate_api_obj! { +/// generate_api! { /// name: "cw20", /// instantiate: InstantiateMsg, /// migrate: MigrateMsg, -/// }.render(); +/// }; /// ``` -pub use cosmwasm_schema_derive::generate_api_obj; +pub use cosmwasm_schema_derive::write_api; pub use schemars::schema_for; diff --git a/packages/schema/tests/idl.rs b/packages/schema/tests/idl.rs index 3f312cd88e..841237f911 100644 --- a/packages/schema/tests/idl.rs +++ b/packages/schema/tests/idl.rs @@ -1,6 +1,6 @@ use std::collections::HashMap; -use cosmwasm_schema::{generate_api_obj, QueryResponses, IDL_VERSION}; +use cosmwasm_schema::{generate_api, QueryResponses, IDL_VERSION}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use serde_json::Value; @@ -40,7 +40,7 @@ pub struct MigrateMsg { #[test] fn test_basic_structure() { - let api_str = generate_api_obj! { + let api_str = generate_api! { name: "test", version: "0.1.0", instantiate: InstantiateMsg, @@ -81,7 +81,7 @@ fn test_basic_structure() { #[test] fn test_query_responses() { - let api_str = generate_api_obj! { + let api_str = generate_api! { instantiate: InstantiateMsg, query: QueryMsg, } From ff89d447abd9e5c4ae6b38fc4bdfd702a9ce9dfb Mon Sep 17 00:00:00 2001 From: Tomasz Kurcz Date: Mon, 11 Jul 2022 13:09:04 +0200 Subject: [PATCH 38/41] format codecov.yml file --- codecov.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codecov.yml b/codecov.yml index ca6f4a47d7..b1bea1d2da 100644 --- a/codecov.yml +++ b/codecov.yml @@ -26,7 +26,7 @@ flags: paths: - packages/schema/ cosmwasm-schema-derive: - - packages/schema-derive/ + - packages/schema-derive/ cosmwasm-std: paths: - packages/std/ From da722a42f113a2848d217bcd3178d9d859090dcb Mon Sep 17 00:00:00 2001 From: Tomasz Kurcz Date: Mon, 11 Jul 2022 13:11:58 +0200 Subject: [PATCH 39/41] schema: fix failing doc test --- packages/schema/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/schema/src/lib.rs b/packages/schema/src/lib.rs index 415acf59fd..254c219523 100644 --- a/packages/schema/src/lib.rs +++ b/packages/schema/src/lib.rs @@ -61,7 +61,7 @@ pub use cosmwasm_schema_derive::generate_api; /// #[derive(JsonSchema)] /// struct MigrateMsg; /// -/// generate_api! { +/// write_api! { /// name: "cw20", /// instantiate: InstantiateMsg, /// migrate: MigrateMsg, From c2035609233792e9a00197311183a09f1a8365ea Mon Sep 17 00:00:00 2001 From: Tomasz Kurcz Date: Mon, 11 Jul 2022 15:34:12 +0200 Subject: [PATCH 40/41] CI: run clippy for schema-derive --- .circleci/config.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.circleci/config.yml b/.circleci/config.yml index 0b4e3aaa25..1973b823db 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -966,6 +966,10 @@ jobs: name: Clippy linting on schema working_directory: ~/project/packages/schema command: cargo clippy --all-targets -- -D warnings + - run: + name: Clippy linting on schema-derive + working_directory: ~/project/packages/schema-derive + command: cargo clippy --all-targets -- -D warnings - run: name: Clippy linting on std (no feature flags) working_directory: ~/project/packages/std From 4e0985a38a6672373a3ae3ad73ecc902799a3f50 Mon Sep 17 00:00:00 2001 From: Tomasz Kurcz Date: Mon, 11 Jul 2022 15:34:54 +0200 Subject: [PATCH 41/41] Update codecov.yml Co-authored-by: Simon Warta <2603011+webmaster128@users.noreply.github.com> --- codecov.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/codecov.yml b/codecov.yml index b1bea1d2da..f04225aed5 100644 --- a/codecov.yml +++ b/codecov.yml @@ -26,7 +26,8 @@ flags: paths: - packages/schema/ cosmwasm-schema-derive: - - packages/schema-derive/ + paths: + - packages/schema-derive/ cosmwasm-std: paths: - packages/std/