Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

docs: Describe generics support in README.md #254

Merged
merged 2 commits into from
Nov 20, 2023
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
204 changes: 198 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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:
Expand Down Expand Up @@ -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<ExecParam, QueryParam, RetType>
where
for<'msg_de> ExecParam: CustomMsg + Deserialize<'msg_de>,
QueryParam: sylvia::types::CustomMsg,
RetType: CustomMsg + DeserializeOwned,
{
type Error: From<StdError>;

#[msg(exec)]
fn generic_exec(
&self,
ctx: ExecCtx,
msgs: Vec<CosmosMsg<ExecParam>>,
) -> Result<Response, Self::Error>;

#[msg(query)]
fn generic_query(&self, ctx: QueryCtx, param: QueryParam) -> Result<RetType, Self::Error>;
}
```

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<ExecParam, QueryParam, RetType>
where
for<'msg_de> ExecParam: CustomMsg + Deserialize<'msg_de>,
QueryParam: sylvia::types::CustomMsg,
RetType: CustomMsg + DeserializeOwned,
{
type Error: From<StdError>;

#[msg(exec)]
fn custom_generic_execute(
&self,
ctx: ExecCtx,
msgs: Vec<CosmosMsg<ExecParam>>,
) -> Result<Response<RetType>, Self::Error>;

#[msg(query)]
fn custom_generic_query(
&self,
ctx: QueryCtx,
param: QueryParam,
) -> Result<RetType, Self::Error>;
}
```

### 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<InstantiateParam, ExecParam, FieldType>
GenericContract<InstantiateParam, ExecParam, FieldType>
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<Response> {
Ok(Response::new())
}

#[msg(exec)]
pub fn contract_execute(
&self,
_ctx: ExecCtx,
_msg: ExecParam,
) -> StdResult<Response> {
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<InstantiateParam, ExecParam, FieldType>
Generic<SvCustomMsg, SvCustomMsg, sylvia::types::SvCustomMsg>
for crate::contract::GenericContract<
InstantiateParam,
ExecParam,
FieldType,
>
{
type Error = StdError;

#[msg(exec)]
fn generic_exec(
&self,
_ctx: ExecCtx,
_msgs: Vec<CosmosMsg<sylvia::types::SvCustomMsg>>,
) -> StdResult<Response> {
Ok(Response::new())
}

#[msg(query)]
fn generic_query(
&self,
_ctx: QueryCtx,
_msg: sylvia::types::SvCustomMsg,
) -> StdResult<SvCustomMsg> {
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<SvCustomMsg, SvCustomMsg, sylvia::types::SvCustomMsg> as Generic)]
impl<InstantiateParam, ExecParam, FieldType>
GenericContract<InstantiateParam, ExecParam, FieldType>
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<SvCustomMsg, SvCustomMsg, SvCustomMsg>))]
#[contract]
impl<InstantiateParam, ExecParam, FieldType>
GenericContract<InstantiateParam, ExecParam, FieldType>
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
Expand All @@ -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
Expand Down
Loading