diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 31b3ba7e..8eb303fa 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -66,6 +66,7 @@ jobs: run: cargo clippy --all-targets -- -D warnings - name: Fmt check project run: cargo fmt --check + - name: Test examples working-directory: examples run: cargo test --locked @@ -75,6 +76,7 @@ jobs: - name: Fmt check examples working-directory: examples run: cargo fmt --check + - name: Build cw20-base example working-directory: examples/contracts/cw20-base run: cargo build --release --target wasm32-unknown-unknown --locked --lib @@ -90,10 +92,18 @@ jobs: - name: Build custom working-directory: examples/contracts/custom run: cargo build --release --target wasm32-unknown-unknown --locked --lib + - name: Build generic_contract + working-directory: examples/contracts/generic_contract + run: cargo build --release --target wasm32-unknown-unknown --locked --lib + - name: Build generic_iface_on_contract + working-directory: examples/contracts/generic_iface_on_contract + run: cargo build --release --target wasm32-unknown-unknown --locked --lib + - name: Install cosmwasm-check run: cargo install cosmwasm-check --force - name: Check contracts run: find examples/target/wasm32-unknown-unknown/release/ -type f -name "*.wasm" -exec cosmwasm-check {} \; + - name: Cw1-whitelist schema working-directory: examples/contracts/cw1-whitelist/ run: cargo schema @@ -109,6 +119,13 @@ jobs: - name: Custom schema working-directory: examples/contracts/custom run: cargo schema + - name: Generic_contract schema + working-directory: examples/contracts/generic_contract + run: cargo schema + - name: generic_iface_on_contract schema + working-directory: examples/contracts/generic_iface_on_contract + run: cargo schema + - name: Cw1-whitelist ts-codegen working-directory: examples/contracts/cw1-whitelist/ run: cosmwasm-ts-codegen generate --plugin client --schema ./schema --out ./ts --name cw1-whitelist --no-bundle @@ -124,6 +141,13 @@ jobs: - name: Custom ts-codegen working-directory: examples/contracts/custom/ run: cosmwasm-ts-codegen generate --plugin client --schema ./schema --out ./ts --name custom --no-bundle + - name: Generic_contract ts-codegen + working-directory: examples/contracts/generic_contract/ + run: cosmwasm-ts-codegen generate --plugin client --schema ./schema --out ./ts --name custom --no-bundle + - name: Generic_iface_on_contract ts-codegen + working-directory: examples/contracts/generic_iface_on_contract + run: cosmwasm-ts-codegen generate --plugin client --schema ./schema --out ./ts --name custom --no-bundle + - name: Archive schema artifats uses: actions/upload-artifact@v3 with: @@ -134,6 +158,8 @@ jobs: examples/contracts/cw20-base/schema/cw20-base.json examples/contracts/entry-points-overriding/schema/entry-points-overriding.json examples/contracts/custom/schema/custom.json + examples/contracts/generic_contract/schema/generic_contract.json + examples/contracts/generic_iface_on_contract/schema/generic_iface_on_contract.json coverage: name: Code coverage diff --git a/examples/Cargo.lock b/examples/Cargo.lock index fd957db0..74bcaebe 100644 --- a/examples/Cargo.lock +++ b/examples/Cargo.lock @@ -241,6 +241,18 @@ dependencies = [ "sylvia", ] +[[package]] +name = "custom-and-generic" +version = "0.5.0" +dependencies = [ + "anyhow", + "cosmwasm-schema", + "cosmwasm-std", + "cw-multi-test", + "serde", + "sylvia", +] + [[package]] name = "cw-multi-test" version = "0.16.5" @@ -549,6 +561,18 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8cbd1169bd7b4a0a20d92b9af7a7e0422888bd38a6f5ec29c1fd8c1558a272e" +[[package]] +name = "generic" +version = "0.5.0" +dependencies = [ + "anyhow", + "cosmwasm-schema", + "cosmwasm-std", + "cw-multi-test", + "serde", + "sylvia", +] + [[package]] name = "generic-array" version = "0.14.7" @@ -559,6 +583,37 @@ dependencies = [ "version_check", ] +[[package]] +name = "generic_contract" +version = "0.5.0" +dependencies = [ + "anyhow", + "cosmwasm-schema", + "cosmwasm-std", + "cw-multi-test", + "cw-storage-plus", + "cw-utils", + "serde", + "sylvia", +] + +[[package]] +name = "generic_iface_on_contract" +version = "0.5.0" +dependencies = [ + "anyhow", + "cosmwasm-schema", + "cosmwasm-std", + "custom-and-generic", + "cw-multi-test", + "cw-storage-plus", + "cw-utils", + "cw1", + "generic", + "serde", + "sylvia", +] + [[package]] name = "getrandom" version = "0.2.10" diff --git a/examples/Cargo.toml b/examples/Cargo.toml index a8a0e3ad..f5b26b33 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -1,18 +1,22 @@ [workspace] members = [ - # Contract intefaces - "interfaces/cw1", - "interfaces/cw4", - "interfaces/cw20-allowances", - "interfaces/cw20-minting", - "interfaces/cw20-marketing", + # Contract intefaces + "interfaces/cw1", + "interfaces/cw4", + "interfaces/cw20-allowances", + "interfaces/cw20-minting", + "interfaces/cw20-marketing", + "interfaces/custom-and-generic", + "interfaces/generic", - # Contracts - "contracts/cw1-whitelist", - "contracts/cw1-subkeys", - "contracts/cw20-base", - "contracts/entry-points-overriding", - "contracts/custom", + # Contracts + "contracts/cw1-whitelist", + "contracts/cw1-subkeys", + "contracts/cw20-base", + "contracts/entry-points-overriding", + "contracts/custom", + "contracts/generic_contract", + "contracts/generic_iface_on_contract", ] resolver = "2" diff --git a/examples/contracts/generic_contract/.cargo/config b/examples/contracts/generic_contract/.cargo/config new file mode 100644 index 00000000..d8ab80fe --- /dev/null +++ b/examples/contracts/generic_contract/.cargo/config @@ -0,0 +1,6 @@ +[alias] +wasm = "build --release --target wasm32-unknown-unknown --lib" +wasm-debug = "build --target wasm32-unknown-unknown --lib" +unit-test = "test --lib" +integration-test = "test --test integration" +schema = "run --bin schema" diff --git a/examples/contracts/generic_contract/Cargo.toml b/examples/contracts/generic_contract/Cargo.toml new file mode 100644 index 00000000..a34ded2b --- /dev/null +++ b/examples/contracts/generic_contract/Cargo.toml @@ -0,0 +1,31 @@ +[package] +name = "generic_contract" +version = { workspace = true } +authors = ["Jan Woźniak "] +edition = { workspace = true } +description = "Example of generic contract" +license = "Apache-2.0" +repository = "https://github.com/CosmWasm/sylvia" +homepage = "https://cosmwasm.com" + +[lib] +crate-type = ["cdylib", "rlib"] + +[features] +library = [] +tests = ["library", "cw-multi-test", "anyhow"] + +[dependencies] +anyhow = { version = "1.0", optional = true } +cosmwasm-schema = "1.2" +cosmwasm-std = { version = "1.3", features = ["staking"] } +cw-multi-test = { version = "0.16", optional = true } +cw-storage-plus = "1.0" +cw-utils = "1.0" +serde = { version = "1.0", default-features = false, features = ["derive"] } +sylvia = { path = "../../../sylvia" } + +[dev-dependencies] +anyhow = "1.0" +cw-multi-test = "0.16" +sylvia = { path = "../../../sylvia", features = ["mt"] } diff --git a/examples/contracts/generic_contract/src/bin/schema.rs b/examples/contracts/generic_contract/src/bin/schema.rs new file mode 100644 index 00000000..a8828949 --- /dev/null +++ b/examples/contracts/generic_contract/src/bin/schema.rs @@ -0,0 +1,14 @@ +use cosmwasm_schema::write_api; + +#[cfg(not(tarpaulin_include))] +fn main() { + use generic_contract::contract::{ + ContractExecMsg, ContractQueryMsg, ExternalMsg, InstantiateMsg, + }; + + write_api! { + instantiate: InstantiateMsg, + execute: ContractExecMsg, + query: ContractQueryMsg, + } +} diff --git a/examples/contracts/generic_contract/src/contract.rs b/examples/contracts/generic_contract/src/contract.rs new file mode 100644 index 00000000..9ef1ee4b --- /dev/null +++ b/examples/contracts/generic_contract/src/contract.rs @@ -0,0 +1,98 @@ +use cosmwasm_schema::cw_serde; +use cosmwasm_std::{Reply, Response, StdResult}; +use serde::de::DeserializeOwned; +use serde::Deserialize; +use sylvia::types::{CustomMsg, ExecCtx, InstantiateCtx, MigrateCtx, QueryCtx, ReplyCtx}; +use sylvia::{contract, schemars}; + +#[cw_serde] +pub struct ExternalMsg; +impl cosmwasm_std::CustomMsg for ExternalMsg {} + +pub struct GenericContract( + std::marker::PhantomData<( + InstantiateParam, + ExecParam, + QueryParam, + MigrateParam, + RetType, + )>, +); + +#[contract] +impl + GenericContract +where + for<'msg_de> InstantiateParam: CustomMsg + Deserialize<'msg_de> + 'msg_de, + ExecParam: CustomMsg + DeserializeOwned + 'static, + QueryParam: CustomMsg + DeserializeOwned + 'static, + MigrateParam: CustomMsg + DeserializeOwned + 'static, + RetType: CustomMsg + DeserializeOwned + 'static, +{ + pub const fn new() -> Self { + Self(std::marker::PhantomData) + } + + #[msg(instantiate)] + pub fn instantiate(&self, _ctx: InstantiateCtx, _msg: InstantiateParam) -> StdResult { + Ok(Response::new()) + } + + #[msg(exec)] + pub fn execute(&self, _ctx: ExecCtx, _msg: ExecParam) -> StdResult { + Ok(Response::new()) + } + + #[msg(query)] + pub fn query(&self, _ctx: QueryCtx, _msg: QueryParam) -> StdResult { + Ok(Response::new()) + } + + #[msg(migrate)] + pub fn migrate(&self, _ctx: MigrateCtx, _msg: MigrateParam) -> StdResult { + Ok(Response::new()) + } + + #[allow(dead_code)] + #[msg(reply)] + fn reply(&self, _ctx: ReplyCtx, _reply: Reply) -> StdResult { + Ok(Response::new()) + } +} + +#[cfg(test)] +mod tests { + use sylvia::multitest::App; + + use crate::contract::ExternalMsg; + + #[test] + fn generic_contract() { + use super::multitest_utils::CodeId; + let app = App::default(); + let code_id: CodeId< + ExternalMsg, + ExternalMsg, + ExternalMsg, + super::ExternalMsg, + super::ExternalMsg, + _, + > = CodeId::store_code(&app); + + let owner = "owner"; + + let contract = code_id + .instantiate(ExternalMsg {}) + .with_label("GenericContract") + .with_admin(owner) + .call(owner) + .unwrap(); + + contract.execute(ExternalMsg).call(owner).unwrap(); + contract.query(ExternalMsg).unwrap(); + contract + .migrate(ExternalMsg) + .call(owner, code_id.code_id()) + .unwrap(); + } +} diff --git a/examples/contracts/generic_contract/src/lib.rs b/examples/contracts/generic_contract/src/lib.rs new file mode 100644 index 00000000..2943dbb5 --- /dev/null +++ b/examples/contracts/generic_contract/src/lib.rs @@ -0,0 +1 @@ +pub mod contract; diff --git a/examples/contracts/generic_iface_on_contract/.cargo/config b/examples/contracts/generic_iface_on_contract/.cargo/config new file mode 100644 index 00000000..d8ab80fe --- /dev/null +++ b/examples/contracts/generic_iface_on_contract/.cargo/config @@ -0,0 +1,6 @@ +[alias] +wasm = "build --release --target wasm32-unknown-unknown --lib" +wasm-debug = "build --target wasm32-unknown-unknown --lib" +unit-test = "test --lib" +integration-test = "test --test integration" +schema = "run --bin schema" diff --git a/examples/contracts/generic_iface_on_contract/Cargo.toml b/examples/contracts/generic_iface_on_contract/Cargo.toml new file mode 100644 index 00000000..99ad5aa1 --- /dev/null +++ b/examples/contracts/generic_iface_on_contract/Cargo.toml @@ -0,0 +1,34 @@ +[package] +name = "generic_iface_on_contract" +version = { workspace = true } +authors = ["Jan Woźniak "] +edition = { workspace = true } +description = "Generic interfaces implemented on non generic contract" +license = "Apache-2.0" +repository = "https://github.com/CosmWasm/sylvia" +homepage = "https://cosmwasm.com" + +[lib] +crate-type = ["cdylib", "rlib"] + +[features] +library = [] +tests = ["library", "cw-multi-test", "anyhow"] + +[dependencies] +anyhow = { version = "1.0", optional = true } +cosmwasm-schema = "1.2" +cosmwasm-std = { version = "1.3", features = ["staking"] } +cw-multi-test = { version = "0.16", optional = true } +cw-storage-plus = "1.0" +cw-utils = "1.0" +serde = { version = "1.0", default-features = false, features = ["derive"] } +sylvia = { path = "../../../sylvia" } +cw1 = { path = "../../interfaces/cw1" } +generic = { path = "../../interfaces/generic" } +custom-and-generic = { path = "../../interfaces/custom-and-generic" } + +[dev-dependencies] +anyhow = "1.0" +cw-multi-test = "0.16" +sylvia = { path = "../../../sylvia", features = ["mt"] } diff --git a/examples/contracts/generic_iface_on_contract/src/bin/schema.rs b/examples/contracts/generic_iface_on_contract/src/bin/schema.rs new file mode 100644 index 00000000..8a7c48e5 --- /dev/null +++ b/examples/contracts/generic_iface_on_contract/src/bin/schema.rs @@ -0,0 +1,12 @@ +use cosmwasm_schema::write_api; + +#[cfg(not(tarpaulin_include))] +fn main() { + use generic_iface_on_contract::contract::{ContractExecMsg, ContractQueryMsg, InstantiateMsg}; + + write_api! { + instantiate: InstantiateMsg, + execute: ContractExecMsg, + query: ContractQueryMsg, + } +} diff --git a/examples/contracts/generic_iface_on_contract/src/contract.rs b/examples/contracts/generic_iface_on_contract/src/contract.rs new file mode 100644 index 00000000..f35d0d69 --- /dev/null +++ b/examples/contracts/generic_iface_on_contract/src/contract.rs @@ -0,0 +1,81 @@ +use cosmwasm_std::{Response, StdResult}; +use sylvia::types::{InstantiateCtx, SvCustomMsg}; +use sylvia::{contract, schemars}; + +pub struct NonGenericContract; + +#[contract] +#[messages(generic as Generic: custom(msg))] +#[messages(custom_and_generic as CustomAndGeneric)] +#[messages(cw1 as Cw1: custom(msg))] +/// Required if interface returns generic `Response` +#[sv::custom(msg=SvCustomMsg)] +impl NonGenericContract { + pub const fn new() -> Self { + Self + } + + #[msg(instantiate)] + pub fn instantiate(&self, _ctx: InstantiateCtx) -> StdResult> { + Ok(Response::new()) + } +} + +#[cfg(test)] +mod tests { + use cosmwasm_std::{CosmosMsg, Empty}; + use sylvia::{multitest::App, types::SvCustomMsg}; + + use super::NonGenericContract; + use crate::custom_and_generic::test_utils::CustomAndGeneric; + use crate::cw1::test_utils::Cw1; + use crate::generic::test_utils::Generic; + + #[test] + fn mt_helpers() { + let _ = NonGenericContract::new(); + let app = App::>::custom(|_, _, _| {}); + let code_id = super::multitest_utils::CodeId::store_code(&app); + + let owner = "owner"; + + let contract = code_id + .instantiate() + .with_label("Cw1Contract") + .call(owner) + .unwrap(); + + // Non custom non generic interface + contract + .cw1_proxy() + .can_execute("sender".to_owned(), CosmosMsg::Custom(Empty {})) + .unwrap(); + contract + .cw1_proxy() + .execute(vec![CosmosMsg::Custom(Empty {})]) + .call(owner) + .unwrap(); + + // Non-Custom generic Interface + contract + .generic_proxy() + .generic_query(SvCustomMsg {}) + .unwrap(); + contract + .generic_proxy() + .generic_exec(vec![CosmosMsg::Custom(SvCustomMsg {})]) + .call(owner) + .unwrap(); + + // Custom generic Interface + contract + .custom_and_generic_proxy() + .custom_generic_query(SvCustomMsg {}) + .unwrap(); + contract + .custom_and_generic_proxy() + .custom_generic_execute(vec![CosmosMsg::Custom(SvCustomMsg {})]) + .call(owner) + .unwrap(); + } +} diff --git a/examples/contracts/generic_iface_on_contract/src/custom_and_generic.rs b/examples/contracts/generic_iface_on_contract/src/custom_and_generic.rs new file mode 100644 index 00000000..90052b86 --- /dev/null +++ b/examples/contracts/generic_iface_on_contract/src/custom_and_generic.rs @@ -0,0 +1,31 @@ +use cosmwasm_std::{CosmosMsg, Response, StdError, StdResult}; +use custom_and_generic::CustomAndGeneric; +use sylvia::contract; +use sylvia::types::{ExecCtx, QueryCtx, SvCustomMsg}; + +#[contract(module = crate::contract)] +#[messages(custom_and_generic as CustomAndGeneric)] +#[sv::custom(msg=sylvia::types::SvCustomMsg)] +impl CustomAndGeneric + for crate::contract::NonGenericContract +{ + type Error = StdError; + + #[msg(exec)] + fn custom_generic_execute( + &self, + _ctx: ExecCtx, + _msgs: Vec>, + ) -> StdResult> { + Ok(Response::new()) + } + + #[msg(query)] + fn custom_generic_query( + &self, + _ctx: QueryCtx, + _msg: sylvia::types::SvCustomMsg, + ) -> StdResult { + Ok(SvCustomMsg {}) + } +} diff --git a/examples/contracts/generic_iface_on_contract/src/cw1.rs b/examples/contracts/generic_iface_on_contract/src/cw1.rs new file mode 100644 index 00000000..8d45bc0d --- /dev/null +++ b/examples/contracts/generic_iface_on_contract/src/cw1.rs @@ -0,0 +1,26 @@ +use cosmwasm_std::{CosmosMsg, Response, StdError, StdResult}; +use cw1::{CanExecuteResp, Cw1}; +use sylvia::contract; +use sylvia::types::{ExecCtx, QueryCtx}; + +#[contract(module = crate::contract)] +#[messages(cw1 as Cw1)] +#[sv::custom(msg=sylvia::types::SvCustomMsg)] +impl Cw1 for crate::contract::NonGenericContract { + type Error = StdError; + + #[msg(exec)] + fn execute(&self, _ctx: ExecCtx, _msgs: Vec) -> StdResult { + Ok(Response::new()) + } + + #[msg(query)] + fn can_execute( + &self, + _ctx: QueryCtx, + _sender: String, + _msg: CosmosMsg, + ) -> StdResult { + Ok(CanExecuteResp::default()) + } +} diff --git a/examples/contracts/generic_iface_on_contract/src/generic.rs b/examples/contracts/generic_iface_on_contract/src/generic.rs new file mode 100644 index 00000000..d13827a0 --- /dev/null +++ b/examples/contracts/generic_iface_on_contract/src/generic.rs @@ -0,0 +1,35 @@ +use cosmwasm_std::{CosmosMsg, Response, StdError, StdResult}; +use generic::Generic; +use sylvia::contract; +use sylvia::types::{ExecCtx, QueryCtx, SvCustomMsg}; + +#[contract(module = crate::contract)] +#[messages(generic as Generic)] +#[sv::custom(msg = SvCustomMsg)] +impl Generic + for crate::contract::NonGenericContract +{ + type Error = StdError; + + #[msg(exec)] + fn generic_exec( + &self, + _ctx: ExecCtx, + _msgs: Vec>, + ) -> StdResult { + Ok(Response::new()) + } + + // Sylvia will fail if single type is used to match against two different generics + // It's because we have to map unique generics used as they can be used multiple times. + // If for some reason like here one type would be used in place of two generics either full + // path or some alias has to be used. + #[msg(query)] + fn generic_query( + &self, + _ctx: QueryCtx, + _msg: sylvia::types::SvCustomMsg, + ) -> StdResult { + Ok(SvCustomMsg {}) + } +} diff --git a/examples/contracts/generic_iface_on_contract/src/lib.rs b/examples/contracts/generic_iface_on_contract/src/lib.rs new file mode 100644 index 00000000..ea8b3738 --- /dev/null +++ b/examples/contracts/generic_iface_on_contract/src/lib.rs @@ -0,0 +1,4 @@ +pub mod contract; +pub mod custom_and_generic; +pub mod cw1; +pub mod generic; diff --git a/examples/interfaces/custom-and-generic/Cargo.toml b/examples/interfaces/custom-and-generic/Cargo.toml new file mode 100644 index 00000000..c1f38f99 --- /dev/null +++ b/examples/interfaces/custom-and-generic/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "custom-and-generic" +version = { workspace = true } +authors = ["Jan Woźniak "] +edition = { workspace = true } +description = "Interface with custom msg and generic support." +license = "Apache-2.0" +repository = "https://github.com/CosmWasm/sylvia" +homepage = "https://cosmwasm.com" + +[features] +mt = ["sylvia/mt"] + +[dependencies] +cosmwasm-std = { version = "1.3", features = ["staking"] } +cosmwasm-schema = "1.2" +serde = { version = "1.0", default-features = false, features = ["derive"] } +sylvia = { path = "../../../sylvia" } + +[dev-dependencies] +anyhow = "1.0" +cw-multi-test = "0.16" diff --git a/examples/interfaces/custom-and-generic/src/lib.rs b/examples/interfaces/custom-and-generic/src/lib.rs new file mode 100644 index 00000000..134eb77e --- /dev/null +++ b/examples/interfaces/custom-and-generic/src/lib.rs @@ -0,0 +1,69 @@ +use cosmwasm_std::{CosmosMsg, CustomMsg, Response, StdError}; + +use serde::de::DeserializeOwned; +use serde::Deserialize; +use sylvia::types::{ExecCtx, QueryCtx}; +use sylvia::{interface, schemars}; + +#[interface] +#[sv::custom(msg=RetType)] +pub trait CustomAndGeneric +where + for<'msg_de> ExecParam: CustomMsg + Deserialize<'msg_de>, + QueryParam: sylvia::types::CustomMsg, + RetType: CustomMsg + DeserializeOwned, +{ + type Error: From; + + #[msg(exec)] + fn custom_generic_execute( + &self, + ctx: ExecCtx, + msgs: Vec>, + ) -> Result, Self::Error>; + + #[msg(query)] + fn custom_generic_query( + &self, + ctx: QueryCtx, + param: QueryParam, + ) -> Result; +} + +#[cfg(test)] +mod tests { + use cosmwasm_std::testing::mock_dependencies; + use cosmwasm_std::{Addr, CosmosMsg, Empty, QuerierWrapper}; + use sylvia::types::{InterfaceMessages, SvCustomMsg}; + + use crate::Querier; + + #[test] + fn construct_messages() { + let contract = Addr::unchecked("contract"); + + // Direct message construction + let _ = super::QueryMsg::<_, Empty>::custom_generic_query(SvCustomMsg {}); + let _ = super::ExecMsg::custom_generic_execute(vec![CosmosMsg::Custom(SvCustomMsg {})]); + let _ = super::ExecMsg::custom_generic_execute(vec![CosmosMsg::Custom(SvCustomMsg {})]); + + // Querier + let deps = mock_dependencies(); + let querier_wrapper: QuerierWrapper = QuerierWrapper::new(&deps.querier); + + let querier = super::BoundQuerier::borrowed(&contract, &querier_wrapper); + let _: Result = + super::Querier::custom_generic_query(&querier, SvCustomMsg {}); + let _: Result = querier.custom_generic_query(SvCustomMsg {}); + + // Construct messages with Interface extension + let _ = + as InterfaceMessages>::Query::custom_generic_query( + SvCustomMsg {}, + ); + let _= + as InterfaceMessages>::Exec::custom_generic_execute( + vec![ CosmosMsg::Custom(SvCustomMsg{}), + ]); + } +} diff --git a/examples/interfaces/generic/Cargo.toml b/examples/interfaces/generic/Cargo.toml new file mode 100644 index 00000000..c5832b18 --- /dev/null +++ b/examples/interfaces/generic/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "generic" +version = { workspace = true } +authors = ["Jan Woźniak "] +edition = { workspace = true } +description = "Interface with generic support." +license = "Apache-2.0" +repository = "https://github.com/CosmWasm/sylvia" +homepage = "https://cosmwasm.com" + +[features] +mt = ["sylvia/mt"] + +[dependencies] +cosmwasm-std = { version = "1.3", features = ["staking"] } +cosmwasm-schema = "1.2" +serde = { version = "1.0", default-features = false, features = ["derive"] } +sylvia = { path = "../../../sylvia" } + +[dev-dependencies] +anyhow = "1.0" +cw-multi-test = "0.16" diff --git a/examples/interfaces/generic/src/lib.rs b/examples/interfaces/generic/src/lib.rs new file mode 100644 index 00000000..042db389 --- /dev/null +++ b/examples/interfaces/generic/src/lib.rs @@ -0,0 +1,61 @@ +use cosmwasm_std::{CosmosMsg, CustomMsg, Response, StdError}; + +use serde::{de::DeserializeOwned, Deserialize}; +use sylvia::types::{ExecCtx, QueryCtx}; +use sylvia::{interface, schemars}; + +#[interface] +pub trait Generic +where + for<'msg_de> ExecParam: CustomMsg + Deserialize<'msg_de>, + QueryParam: sylvia::types::CustomMsg, + RetType: CustomMsg + DeserializeOwned, +{ + type Error: From; + + #[msg(exec)] + fn generic_exec( + &self, + ctx: ExecCtx, + msgs: Vec>, + ) -> Result; + + #[msg(query)] + fn generic_query(&self, ctx: QueryCtx, param: QueryParam) -> Result; +} + +#[cfg(test)] +mod tests { + use cosmwasm_std::{testing::mock_dependencies, Addr, CosmosMsg, Empty, QuerierWrapper}; + use sylvia::types::{InterfaceMessages, SvCustomMsg}; + + use crate::Querier; + + #[test] + fn construct_messages() { + let contract = Addr::unchecked("contract"); + + // Direct message construction + let _ = super::QueryMsg::<_, Empty>::generic_query(SvCustomMsg {}); + let _ = super::ExecMsg::generic_exec(vec![CosmosMsg::Custom(SvCustomMsg {})]); + let _ = super::ExecMsg::generic_exec(vec![CosmosMsg::Custom(SvCustomMsg {})]); + + // Querier + let deps = mock_dependencies(); + let querier_wrapper: QuerierWrapper = QuerierWrapper::new(&deps.querier); + + let querier = super::BoundQuerier::borrowed(&contract, &querier_wrapper); + let _: Result = super::Querier::generic_query(&querier, SvCustomMsg {}); + let _: Result = querier.generic_query(SvCustomMsg {}); + + // Construct messages with Interface extension + let _ = + as InterfaceMessages>::Query::generic_query( + SvCustomMsg {}, + ); + let _= + as InterfaceMessages>::Exec::generic_exec(vec![ + CosmosMsg::Custom(SvCustomMsg{}), + ]); + } +} diff --git a/sylvia-derive/src/multitest.rs b/sylvia-derive/src/multitest.rs index f958cd5c..b92cac65 100644 --- a/sylvia-derive/src/multitest.rs +++ b/sylvia-derive/src/multitest.rs @@ -455,14 +455,14 @@ where quote! { #impl_contract - pub struct CodeId<'app, MtApp, #(#generics,)* > { + pub struct CodeId<'app, #(#generics,)* MtApp> { code_id: u64, app: &'app #sylvia ::multitest::App, _phantom: std::marker::PhantomData<( #(#generics,)* )>, } - impl<'app, BankT, ApiT, StorageT, CustomT, StakingT, DistrT, IbcT, GovT, #(#generics,)* > CodeId<'app, #mt_app, #(#generics,)* > + impl<'app, BankT, ApiT, StorageT, CustomT, StakingT, DistrT, IbcT, GovT, #(#generics,)* > CodeId<'app, #(#generics,)* #mt_app > where BankT: #sylvia ::cw_multi_test::Bank, ApiT: #sylvia ::cw_std::Api, @@ -487,9 +487,9 @@ where pub fn instantiate( &self,#(#fields,)* - ) -> InstantiateProxy<'_, 'app, #mt_app, #(#generics,)* > { + ) -> InstantiateProxy<'_, 'app, #(#generics,)* #mt_app > { let msg = #instantiate_msg {#(#fields_names,)*}; - InstantiateProxy::<_, #(#generics,)* > { + InstantiateProxy::< #(#generics,)* _> { code_id: self, funds: &[], label: "Contract", @@ -499,15 +499,15 @@ where } } - pub struct InstantiateProxy<'proxy, 'app, MtApp, #(#generics,)* > { - code_id: &'proxy CodeId <'app, MtApp, #(#generics,)* >, + pub struct InstantiateProxy<'proxy, 'app, #(#generics,)* MtApp> { + code_id: &'proxy CodeId <'app, #(#generics,)* MtApp>, funds: &'proxy [#sylvia ::cw_std::Coin], label: &'proxy str, admin: Option, msg: InstantiateMsg #bracketed_used_generics, } - impl<'proxy, 'app, MtApp, #(#generics,)* > InstantiateProxy<'proxy, 'app, MtApp, #(#generics,)* > + impl<'proxy, 'app, #(#generics,)* MtApp> InstantiateProxy<'proxy, 'app, #(#generics,)* MtApp> where MtApp: Executor< #custom_msg >, #where_predicates diff --git a/sylvia/src/types.rs b/sylvia/src/types.rs index c70ed7b3..059ede46 100644 --- a/sylvia/src/types.rs +++ b/sylvia/src/types.rs @@ -1,3 +1,4 @@ +use cosmwasm_schema::cw_serde; use cosmwasm_std::{CustomQuery, Deps, DepsMut, Empty, Env, MessageInfo}; use serde::de::DeserializeOwned; @@ -99,6 +100,11 @@ pub trait CustomMsg: cosmwasm_std::CustomMsg + DeserializeOwned {} impl CustomMsg for T where T: cosmwasm_std::CustomMsg + DeserializeOwned {} +#[cw_serde] +pub struct SvCustomMsg; + +impl cosmwasm_std::CustomMsg for SvCustomMsg {} + pub trait InterfaceMessages { type Exec; type Query; diff --git a/sylvia/tests/generics.rs b/sylvia/tests/generics.rs deleted file mode 100644 index 5b153111..00000000 --- a/sylvia/tests/generics.rs +++ /dev/null @@ -1,394 +0,0 @@ -use cosmwasm_schema::cw_serde; - -pub mod cw1 { - use cosmwasm_std::{CosmosMsg, CustomMsg, CustomQuery, Response, StdError}; - - use serde::{de::DeserializeOwned, Deserialize}; - use sylvia::types::{ExecCtx, QueryCtx}; - use sylvia_derive::interface; - - #[interface(module=msg)] - #[sv::custom(msg=Msg)] - pub trait Cw1 - where - for<'msg_de> Msg: CustomMsg + Deserialize<'msg_de>, - Param: sylvia::types::CustomMsg, - QueryRet: CustomQuery + DeserializeOwned, - { - type Error: From; - - #[msg(exec)] - fn execute( - &self, - ctx: ExecCtx, - msgs: Vec>, - ) -> Result, Self::Error>; - - #[msg(query)] - fn some_query(&self, ctx: QueryCtx, param: Param) -> Result; - } -} - -pub mod whitelist { - use cosmwasm_std::{CosmosMsg, CustomMsg, CustomQuery, Response, StdError}; - - use serde::de::DeserializeOwned; - use serde::Deserialize; - use sylvia::types::{ExecCtx, QueryCtx}; - use sylvia_derive::interface; - - #[interface(module=msg)] - pub trait Whitelist - where - for<'msg_de> Msg: CustomMsg + Deserialize<'msg_de>, - QueryRet: CustomQuery + DeserializeOwned, - { - type Error: From; - - #[msg(exec)] - fn update_admins( - &self, - ctx: ExecCtx, - msgs: Vec>, - ) -> Result; - - #[msg(query)] - fn admins_list(&self, ctx: QueryCtx) -> Result; - } -} - -pub mod non_generic { - use cosmwasm_std::{CosmosMsg, Empty, Response, StdError}; - - use sylvia::types::{ExecCtx, QueryCtx}; - use sylvia_derive::interface; - - #[interface(module=msg)] - pub trait NonGeneric { - type Error: From; - - #[msg(exec)] - fn non_generic_exec( - &self, - ctx: ExecCtx, - msgs: Vec>, - ) -> Result; - - #[msg(query)] - fn non_generic_query(&self, ctx: QueryCtx) -> Result; - } -} - -pub mod generic_contract { - use cosmwasm_std::{Reply, Response, StdResult}; - use serde::de::DeserializeOwned; - use serde::Deserialize; - use sylvia::types::{CustomMsg, ExecCtx, InstantiateCtx, MigrateCtx, QueryCtx, ReplyCtx}; - use sylvia_derive::contract; - - pub struct GenericContract( - std::marker::PhantomData<( - InstantiateParam, - ExecParam, - QueryParam, - MigrateParam, - RetType, - )>, - ); - - #[contract] - impl - GenericContract - where - for<'msg_de> InstantiateParam: CustomMsg + Deserialize<'msg_de> + 'msg_de, - for<'exec> ExecParam: CustomMsg + DeserializeOwned + 'exec, - for<'exec> QueryParam: CustomMsg + DeserializeOwned + 'exec, - for<'exec> MigrateParam: CustomMsg + DeserializeOwned + 'exec, - for<'ret> RetType: CustomMsg + DeserializeOwned + 'ret, - { - pub const fn new() -> Self { - Self(std::marker::PhantomData) - } - - #[msg(instantiate)] - pub fn instantiate( - &self, - _ctx: InstantiateCtx, - _msg: InstantiateParam, - ) -> StdResult { - Ok(Response::new()) - } - - #[msg(exec)] - pub fn execute(&self, _ctx: ExecCtx, _msg: ExecParam) -> StdResult { - Ok(Response::new()) - } - - #[msg(query)] - pub fn query(&self, _ctx: QueryCtx, _msg: QueryParam) -> StdResult { - Ok(Response::new()) - } - - #[msg(migrate)] - pub fn migrate(&self, _ctx: MigrateCtx, _msg: MigrateParam) -> StdResult { - Ok(Response::new()) - } - - #[allow(dead_code)] - #[msg(reply)] - fn reply(&self, _ctx: ReplyCtx, _reply: Reply) -> StdResult { - Ok(Response::new()) - } - } -} - -pub mod cw1_contract { - use cosmwasm_std::{Response, StdResult}; - use sylvia::types::InstantiateCtx; - use sylvia_derive::contract; - - use crate::{ExternalMsg, ExternalQuery}; - - pub struct Cw1Contract; - - #[contract] - #[messages(crate::cw1 as Cw1)] - #[messages(crate::whitelist as Whitelist: custom(msg))] - #[messages(crate::non_generic as NonGeneric: custom(msg))] - /// Required if interface returns generic `Response` - #[sv::custom(msg=ExternalMsg)] - impl Cw1Contract { - pub const fn new() -> Self { - Self - } - - #[msg(instantiate)] - pub fn instantiate(&self, _ctx: InstantiateCtx) -> StdResult> { - Ok(Response::new()) - } - } -} - -pub mod impl_non_generic { - use crate::cw1_contract::Cw1Contract; - use crate::non_generic::NonGeneric; - use cosmwasm_std::{CosmosMsg, Empty, Response, StdError}; - use sylvia::types::{ExecCtx, QueryCtx}; - use sylvia_derive::contract; - - #[contract(module = crate::cw1_contract)] - #[messages(crate::non_generic as NonGeneric)] - #[sv::custom(msg=crate::ExternalMsg)] - impl NonGeneric for Cw1Contract { - type Error = StdError; - - #[msg(exec)] - fn non_generic_exec( - &self, - _ctx: ExecCtx, - _msgs: Vec>, - ) -> Result { - Ok(Response::new()) - } - - #[msg(query)] - fn non_generic_query(&self, _ctx: QueryCtx) -> Result { - Ok(Response::default()) - } - } -} - -pub mod impl_whitelist { - use cosmwasm_std::{CosmosMsg, Response, StdError}; - use sylvia::types::{ExecCtx, QueryCtx}; - use sylvia_derive::contract; - - use crate::cw1_contract::Cw1Contract; - use crate::whitelist::Whitelist; - use crate::{ExternalMsg, ExternalQuery}; - - #[contract(module = crate::cw1_contract)] - #[messages(crate::whitelist as Whitelist)] - #[sv::custom(msg=ExternalMsg)] - impl Whitelist for Cw1Contract { - type Error = StdError; - - #[msg(exec)] - fn update_admins( - &self, - _ctx: ExecCtx, - _msgs: Vec>, - ) -> Result { - Ok(Response::new()) - } - - #[msg(query)] - fn admins_list(&self, _ctx: QueryCtx) -> Result { - Ok(ExternalQuery {}) - } - } -} - -pub mod impl_cw1 { - use cosmwasm_std::{CosmosMsg, Response, StdError}; - use sylvia::types::{ExecCtx, QueryCtx}; - use sylvia_derive::contract; - - use crate::{cw1::Cw1, cw1_contract::Cw1Contract, ExternalMsg}; - - #[contract(module = crate::cw1_contract)] - #[messages(crate::cw1 as Cw1)] - #[sv::custom(msg=ExternalMsg)] - impl Cw1 for Cw1Contract { - type Error = StdError; - - #[msg(exec)] - fn execute( - &self, - _ctx: ExecCtx, - _msgs: Vec>, - ) -> Result, Self::Error> { - Ok(Response::new()) - } - - #[msg(query)] - fn some_query( - &self, - _ctx: QueryCtx, - _param: crate::ExternalMsg, - ) -> Result { - Ok(crate::ExternalQuery {}) - } - } -} - -#[cw_serde] -pub struct ExternalMsg; -impl cosmwasm_std::CustomMsg for ExternalMsg {} - -#[cw_serde] -pub struct ExternalQuery; -impl cosmwasm_std::CustomQuery for ExternalQuery {} - -#[cfg(all(test, feature = "mt"))] -mod tests { - use crate::cw1::{InterfaceTypes, Querier as Cw1Querier}; - use crate::cw1_contract::Cw1Contract; - use crate::impl_cw1::test_utils::Cw1; - use crate::impl_non_generic::test_utils::NonGeneric; - use crate::impl_whitelist::test_utils::Whitelist; - use crate::non_generic::Querier as NonGenericQuerier; - use crate::whitelist::Querier as WhitelistQuerier; - use crate::{ExternalMsg, ExternalQuery}; - use cosmwasm_std::{testing::mock_dependencies, Addr, CosmosMsg, Empty, QuerierWrapper}; - use sylvia::multitest::App; - use sylvia::types::InterfaceMessages; - - #[test] - fn construct_messages() { - let contract = Addr::unchecked("contract"); - - // Direct message construction - // cw1 - let _ = crate::cw1::QueryMsg::<_, Empty>::some_query(ExternalMsg {}); - let _ = crate::cw1::ExecMsg::execute(vec![CosmosMsg::Custom(ExternalMsg {})]); - let _ = crate::cw1::ExecMsg::execute(vec![CosmosMsg::Custom(Empty {})]); - - // whitelist - let _ = crate::whitelist::QueryMsg::::admins_list(); - let _ = crate::whitelist::ExecMsg::update_admins(vec![CosmosMsg::Custom(ExternalMsg {})]); - - // non_generic - let _ = crate::non_generic::QueryMsg::non_generic_query(); - let _ = crate::non_generic::ExecMsg::non_generic_exec(vec![]); - - // Generic Querier - let deps = mock_dependencies(); - let querier: QuerierWrapper = QuerierWrapper::new(&deps.querier); - - let cw1_querier = crate::cw1::BoundQuerier::borrowed(&contract, &querier); - let _: Result = - crate::cw1::Querier::some_query(&cw1_querier, ExternalMsg {}); - let _: Result = cw1_querier.some_query(ExternalMsg {}); - - let contract_querier = crate::cw1_contract::BoundQuerier::borrowed(&contract, &querier); - let _: Result = contract_querier.some_query(ExternalMsg {}); - let _: Result = contract_querier.admins_list(); - let _ = contract_querier.non_generic_query(); - - // Construct messages with Interface extension - let _ = - as InterfaceMessages>::Query::some_query( - ExternalMsg {}, - ); - let _= - as InterfaceMessages>::Exec::execute(vec![ - CosmosMsg::Custom(ExternalMsg {}), - ]); - } - - #[test] - fn mt_helpers() { - let _ = Cw1Contract::new(); - let app = App::>::custom(|_, _, _| {}); - let code_id = crate::cw1_contract::multitest_utils::CodeId::store_code(&app); - - let owner = "owner"; - - let contract = code_id - .instantiate() - .with_label("Cw1Contract") - .call(owner) - .unwrap(); - - // CustomMsg generic Interface - contract.cw1_proxy().some_query(ExternalMsg {}).unwrap(); - contract.cw1_proxy().execute(vec![]).call(owner).unwrap(); - - // Non-CustomMsg generic Interface - contract.whitelist_proxy().admins_list().unwrap(); - contract - .whitelist_proxy() - .update_admins(vec![]) - .call(owner) - .unwrap(); - - // Non-CustomMsg non-generic Interface - contract.non_generic_proxy().non_generic_query().unwrap(); - contract - .non_generic_proxy() - .non_generic_exec(vec![]) - .call(owner) - .unwrap(); - } - - #[test] - fn generic_contract() { - use crate::generic_contract::multitest_utils::CodeId; - let app = App::default(); - let code_id: CodeId< - cw_multi_test::BasicApp, - ExternalMsg, - ExternalMsg, - ExternalMsg, - crate::ExternalMsg, - crate::ExternalMsg, - > = CodeId::store_code(&app); - - let owner = "owner"; - - let contract = code_id - .instantiate(ExternalMsg {}) - .with_label("GenericContract") - .with_admin(owner) - .call(owner) - .unwrap(); - - contract.execute(ExternalMsg).call(owner).unwrap(); - contract.query(ExternalMsg).unwrap(); - contract - .migrate(ExternalMsg) - .call(owner, code_id.code_id()) - .unwrap(); - } -}