diff --git a/README.md b/README.md index 7e0abad5..a8335dce 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ into the forest of Cosmos ecosystem. We provide you with the toolset, so instead of focusing on the raw structure of your contract, you can create them in proper and idiomatic Rust and then just let cargo make sure that they are sound. -Learn more about sylvia in [the book](https://cosmwasm.github.io/sylvia-book/index.html) +Learn more about `sylvia` in [the book](https://cosmwasm.github.io/sylvia-book/index.html) ## The approach @@ -30,6 +30,12 @@ in compile time. Also, as a side effect, as Sylvia has all the knowledge about the contract API structure, it can generate many helpers - utilities for multitests or even queriers. +## Code generation + +Since the proof of concept `Sylvia` grew a lot and generates a lot of utilities. +Because of that we decided that since version `0.9.0` all the code +generated by `Sylvia` is going to be placed in the `sv` module. + ## Using in contracts First you need your contract crate, which should be a library crate: @@ -920,6 +926,197 @@ impl AssociatedInterface for crate::MyContract { In case both associated type and `sv::custom()` attribute are defined `sv::custom()` will be used to determine `CustomMsg` and/or `CustomQuery`. +## Generics + +Since `0.9.0` we can use generics next to the `sylvia` macros. +It is possible to define both generic contract and generic interface. + +### Generic interface + +Defining generic interface is as simple as defining a generic trait. + +```rust +#[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; +} +``` + +We can also use generics with `custom`. In such case we have to provide the generic +type name to the `sv::custom(..)` attribute. + +```rust +#[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; +} +``` + +### Generic contract + +Generics in contract might be both used as generic field types or as generic parameters or return +types in the messages. +The mechanism for generation of messages here is the same as in case of the interfaces. +Only generics used in respective methods will be part of generated messages. + +```rust +pub struct GenericContract< + InstantiateParam, + ExecParam, + FieldType, +> { + _field: Item<'static, FieldType>, + _phantom: std::marker::PhantomData<( + InstantiateParam, + ExecParam, + )>, +} + +#[contract] +impl + GenericContract +where + for<'msg_de> InstantiateParam: CustomMsg + Deserialize<'msg_de> + 'msg_de, + ExecParam: CustomMsg + DeserializeOwned + 'static, + FieldType: 'static, +{ + pub const fn new() -> Self { + Self { + _field: Item::new("field"), + _phantom: std::marker::PhantomData, + } + } + + #[msg(instantiate)] + pub fn instantiate( + &self, + _ctx: InstantiateCtx, + _msg: InstantiateParam, + ) -> StdResult { + Ok(Response::new()) + } + + #[msg(exec)] + pub fn contract_execute( + &self, + _ctx: ExecCtx, + _msg: ExecParam, + ) -> StdResult { + Ok(Response::new()) + } +} +``` + +### Implement interface + +To implement generic interface we have to provide solid types for the interface +generics. No additional attributes are required. + +```rust +#[contract(module = crate::contract)] +#[messages(generic as Generic)] +#[sv::custom(msg=SvCustomMsg)] +impl + Generic + for crate::contract::GenericContract< + InstantiateParam, + ExecParam, + FieldType, + > +{ + type Error = StdError; + + #[msg(exec)] + fn generic_exec( + &self, + _ctx: ExecCtx, + _msgs: Vec>, + ) -> StdResult { + Ok(Response::new()) + } + + #[msg(query)] + fn generic_query( + &self, + _ctx: QueryCtx, + _msg: sylvia::types::SvCustomMsg, + ) -> StdResult { + Ok(SvCustomMsg {}) + } +} +``` + +Then we have to inform `sylvia` about the generics used while implementing +interface in the main `contract` macro call: + +```rust +#[contract] +#[messages(generic as Generic)] +impl + GenericContract +where + for<'msg_de> InstantiateParam: CustomMsg + Deserialize<'msg_de> + 'msg_de, + ExecParam: CustomMsg + DeserializeOwned + 'static, + FieldType: 'static, +{ +} +``` + +### Generics in entry_points + +Entry points has to be generated with solid types. Using the `entry_points` macro +on the generic contract we have to specify the types that has to be used. +We do that by via `entry_points(generics<..>)`: + +```rust +#[cfg_attr(not(feature = "library"), entry_points(generics))] +#[contract] +impl + GenericContract +where + for<'msg_de> InstantiateParam: CustomMsg + Deserialize<'msg_de> + 'msg_de, + ExecParam: CustomMsg + DeserializeOwned + 'static, + FieldType: 'static, +{ + ... +} +``` + ## Generating schema Sylvia is designed to generate all the code which cosmwasm-schema relies on - this @@ -940,11 +1137,6 @@ fn main() { } ``` -Unfortunately, because of [a bug](https://github.com/CosmWasm/ts-codegen/issues/103) -in the `ts-codegen`, schemas for Sylvia contracts are not properly interpreted there. -However, we are working on how to solve this issue regardless of the `ts-codegen` -implementation. - ## Road map Sylvia is in the adoption stage right now, but we are still working on more and more