Skip to content

Commit

Permalink
docs: Describe generics support in README.md
Browse files Browse the repository at this point in the history
  • Loading branch information
jawoznia committed Nov 13, 2023
1 parent 23f072c commit cd090f5
Showing 1 changed file with 191 additions and 5 deletions.
196 changes: 191 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -920,6 +920,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 +1131,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

0 comments on commit cd090f5

Please sign in to comment.