Skip to content

Commit

Permalink
Added automatic into for msgs (#389)
Browse files Browse the repository at this point in the history
* Added automatic into for msgs

* Fix impl into and move to into instead of from

* Clippiedd

* Ok, good

* Added impl into deprecation

* Added docs + chanelog
  • Loading branch information
Kayanski authored Jun 3, 2024
1 parent 6ea5560 commit 31932f5
Show file tree
Hide file tree
Showing 11 changed files with 382 additions and 85 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
- Added a test to make sure the derive macros stay compatible with new cw-orch versions
- Changed the derive macros import from cw_orch to cw_orch_core. This allows changing the cw-orch API without breaking the derive macros.
- Cw-orch mock env info doesn't error when using chain ids that don't match the `osmosis-1` pattern
- Remove `impl_into`, the old `impl_into` behavior is now the default behavior

### Breaking

Expand Down
9 changes: 4 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -146,9 +146,9 @@ impl<Chain> Cw1<Chain> {

The `QueryFns` derive macro works in the same way as the `ExecuteFns` macro but it also uses the `#[returns(QueryResponse)]` attribute from `cosmwasm-schema` to generate the queries with the correct response types.

### `impl_into` Attribute
### Nested types

For nested messages (execute and query) you can add an `impl_into` attribute. This expects the enum to implement the `Into` trait for the provided type. This is extremely useful when working with generic messages:
For nested messages (execute and query), you just need to derive `ExecuteFns` or `QueryFns` on the underlying structures. In general, every structure that implements the `Into` trait for the contract message will make the function available on the contract. To make that clearer, her's an example:

```rust,ignore
use cw_orch::interface;
Expand All @@ -163,7 +163,6 @@ pub enum GenericExecuteMsg<T> {
// A type that will fill the generic.
#[cosmwasm_schema::cw_serde]
#[derive(cw_orch::ExecuteFns)]
#[impl_into(ExecuteMsg)]
pub enum Foo {
Bar { a: String },
}
Expand All @@ -183,15 +182,15 @@ struct Example<Chain>;
impl<Chain: CwEnv> Example<Chain> {
pub fn test_macro(&self) {
// Function `bar` is available because of the `impl_into` attribute!
// Function `bar` is available because `Foo` implements `Into<GenericExecuteMsg<Foo>>`
self.bar("hello".to_string()).unwrap();
}
}
```

### Testing with OsmosisTestTube

[OsmosisTestTube](https://github.com/osmosis-labs/test-tube) is available for testing in cw-orchestrator. In order to use it, you may need to install [clang](https://clang.llvm.org/) and [go](https://go.dev/) to compile the osmosis blockchain that serves as the backend for this env. This compilation is taken care of by cargo directly but if you don't have the right dependencies installed, weird errors may arise.
[OsmosisTestTube](https://github.com/osmosis-labs/test-tube) is available for testing in cw-orchestrator. In order to use it, you may need to install [clang](https://clang.llvm.org/) and [go](https://go.dev/) to compile the osmosis blockchain that serves as the backend for this env. This compilation is taken care of by cargo directly but if you don't have the right dependencies installed, weird errors may arise.

- Visit <https://docs.osmosis.zone/osmosis-core/osmosisd> for a comprehensive list of dependencies.
- Visit [the INSTALL.md file](./INSTALL.md) for a list of dependencies we have written specifically for use with cw-orch.
Expand Down
222 changes: 222 additions & 0 deletions cw-orch/examples/automatic-into.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
use cosmwasm_schema::cw_serde;
use cosmwasm_std::{to_json_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult};
use cw_orch::prelude::*;
use msg::{
execute::{BaseExecMsg, BaseExecMsgFns as _, MintingExecMsg, MintingExecMsgFns as _},
query::{
BalanceResponse, BaseQueryMsg, BaseQueryMsgFns as _, MinterResponse, MintingQueryMsg,
MintingQueryMsgFns as _,
},
};

#[cw_orch::interface(Empty, ExecuteMsg, QueryMsg, Empty)]
pub struct Cw20;

#[cw_orch::interface(Empty, MintingExecMsg, Empty, Empty)]
pub struct Cw20Minter;

#[cw_orch::interface(Empty, BaseExecMsg, Empty, Empty)]
pub struct Cw20Base;

pub fn instantiate(
_deps: DepsMut,
_env: Env,
_info: MessageInfo,
_msg: Empty,
) -> StdResult<Response> {
Ok(Response::new().add_attribute("action", "instantiate"))
}

pub fn execute(
_deps: DepsMut,
_env: Env,
_info: MessageInfo,
_msg: ExecuteMsg,
) -> StdResult<Response> {
Ok(Response::new())
}

pub fn minter_execute(
_deps: DepsMut,
_env: Env,
_info: MessageInfo,
_msg: MintingExecMsg,
) -> StdResult<Response> {
Ok(Response::new())
}

pub fn base_execute(
_deps: DepsMut,
_env: Env,
_info: MessageInfo,
_msg: BaseExecMsg,
) -> StdResult<Response> {
Ok(Response::new())
}

pub fn query(_deps: Deps, _env: Env, msg: QueryMsg) -> StdResult<Binary> {
match msg {
QueryMsg::Minting(minting) => match minting {
MintingQueryMsg::Minter {} => to_json_binary(&MinterResponse {
minter: "minter".to_string(),
}),
},
QueryMsg::Base(base_msg) => match base_msg {
BaseQueryMsg::Balance { address: _ } => to_json_binary(&BalanceResponse {
balance: 167u128.into(),
}),
},
}
}

pub fn migrate(_deps: DepsMut, _env: Env, _msg: Empty) -> StdResult<Response> {
Ok(Response::new())
}

impl<Chain> Uploadable for Cw20<Chain> {
/// Returns a CosmWasm contract wrapper
fn wrapper() -> Box<dyn MockContract<Empty>> {
Box::new(ContractWrapper::new_with_empty(execute, instantiate, query).with_migrate(migrate))
}
}

impl<Chain> Uploadable for Cw20Minter<Chain> {
/// Returns a CosmWasm contract wrapper
fn wrapper() -> Box<dyn MockContract<Empty>> {
Box::new(
ContractWrapper::new_with_empty(minter_execute, instantiate, query)
.with_migrate(migrate),
)
}
}

impl<Chain> Uploadable for Cw20Base<Chain> {
/// Returns a CosmWasm contract wrapper
fn wrapper() -> Box<dyn MockContract<Empty>> {
Box::new(
ContractWrapper::new_with_empty(base_execute, instantiate, query).with_migrate(migrate),
)
}
}

pub fn main() -> anyhow::Result<()> {
let mock = MockBech32::new("mock");

let contract = Cw20::new("cw20", mock.clone());
contract.upload()?;
contract.instantiate(&Empty {}, None, None)?;

contract.mint(150_100u128.into(), "nicoco".to_string())?;
contract.send(
150_100u128.into(),
"nicoco".to_string(),
Binary::from_base64("cXNk")?,
)?;
let minter_response = contract.minter()?;
let balance = contract.balance("nicoco".to_string())?;
assert_eq!(minter_response.minter, "minter");
assert_eq!(balance.balance.u128(), 167);

let contract = Cw20Minter::new("cw20_minter", mock.clone());
contract.upload()?;
contract.instantiate(&Empty {}, None, None)?;
contract.mint(150_100u128.into(), "nicoco".to_string())?;

let contract = Cw20Base::new("cw20_base", mock.clone());
contract.upload()?;
contract.instantiate(&Empty {}, None, None)?;
contract.send(
150_100u128.into(),
"nicoco".to_string(),
Binary::from_base64("cXNk")?,
)?;

Ok(())
}

#[cw_serde]
pub enum ExecuteMsg {
Minting(MintingExecMsg),
Base(BaseExecMsg),
}

impl From<MintingExecMsg> for ExecuteMsg {
fn from(value: MintingExecMsg) -> Self {
Self::Minting(value)
}
}

impl From<BaseExecMsg> for ExecuteMsg {
fn from(value: BaseExecMsg) -> Self {
Self::Base(value)
}
}

#[cw_serde]
pub enum QueryMsg {
Minting(MintingQueryMsg),
Base(BaseQueryMsg),
}
impl From<MintingQueryMsg> for QueryMsg {
fn from(value: MintingQueryMsg) -> Self {
Self::Minting(value)
}
}

impl From<BaseQueryMsg> for QueryMsg {
fn from(value: BaseQueryMsg) -> Self {
Self::Base(value)
}
}

mod msg {
pub mod execute {

use cosmwasm_schema::cw_serde;
use cosmwasm_std::{Binary, Uint128};

#[cw_serde]
#[derive(cw_orch::ExecuteFns)]
pub enum MintingExecMsg {
Mint { recipient: String, amount: Uint128 },
}

#[cw_serde]
#[derive(cw_orch::ExecuteFns)]
pub enum BaseExecMsg {
Send {
contract: String,
amount: Uint128,
msg: Binary,
},
}
}

pub mod query {

use cosmwasm_schema::{cw_serde, QueryResponses};
use cosmwasm_std::Uint128;

#[cw_serde]
#[derive(QueryResponses, cw_orch::QueryFns)]
pub enum MintingQueryMsg {
#[returns(MinterResponse)]
Minter {},
}

#[cw_serde]
#[derive(QueryResponses, cw_orch::QueryFns)]
pub enum BaseQueryMsg {
#[returns(BalanceResponse)]
Balance { address: String },
}
#[cw_serde]
pub struct MinterResponse {
pub minter: String,
}
#[cw_serde]
pub struct BalanceResponse {
pub balance: Uint128,
}
}
}
11 changes: 4 additions & 7 deletions cw-orch/tests/impl_into.rs → cw-orch/tests/impl_into_legacy.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
// ANCHOR: impl_into
use cw_orch::interface;
use cw_orch::prelude::*;

Expand Down Expand Up @@ -42,12 +41,10 @@ struct Example<Chain>;

impl<Chain: CwEnv> Example<Chain> {
pub fn test_macro(&self) {
// function `bar` is available because of the `impl_into` attribute!
self.bar("hello".to_string()).unwrap();
// function `bar` is available by default and the impl_into attribute doesn't error
self.bar("hello".to_string()).unwrap_err();

// function `test` is available because of the `impl_into` attribute!
self.test(65).unwrap();
// function `test` is available by default and the impl_into attribute doesn't error
self.test(65u64).unwrap_err();
}
}

// ANCHOR_END: impl_into
50 changes: 50 additions & 0 deletions cw-orch/tests/underlying_into.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// ANCHOR: underlying_into
use cw_orch::interface;
use cw_orch::prelude::*;

// An execute message that is generic.
#[cosmwasm_schema::cw_serde]
pub enum GenericExecuteMsg<T> {
Generic(T),
Nested(NestedMessageType),
}

// This is the message that will be used on our contract
type ExecuteMsg = GenericExecuteMsg<Foo>;
#[cosmwasm_schema::cw_serde]
#[derive(cw_orch::ExecuteFns)]
pub enum Foo {
Bar { a: String },
}

impl From<Foo> for ExecuteMsg {
fn from(msg: Foo) -> Self {
ExecuteMsg::Generic(msg)
}
}

#[cosmwasm_schema::cw_serde]
#[derive(cw_orch::ExecuteFns)]
pub enum NestedMessageType {
Test { b: u64 },
}

impl From<NestedMessageType> for ExecuteMsg {
fn from(msg: NestedMessageType) -> Self {
ExecuteMsg::Nested(msg)
}
}

#[interface(Empty, ExecuteMsg, Empty, Empty)]
struct Example<Chain>;

impl<Chain: CwEnv> Example<Chain> {
pub fn test_macro(&self) {
// function `bar` is available now!
self.bar("hello".to_string()).unwrap();

// function `test` is available now!
self.test(65u64).unwrap();
}
}
// ANCHOR_END: underlying_into
11 changes: 8 additions & 3 deletions docs/src/contracts/entry-points.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,12 +121,17 @@ money_market.proxy_execute(message_to_execute_via_a_proxy)?;

This is also true for query functions.

### `impl_into` Attribute
### Nested Messages

For nested messages (execute and query) you can add an `impl_into` attribute. This expects the enum to implement the `Into` trait for the provided type. This is extremely useful when working with generic messages:
For nested messages (execute and query), you need to do 2 things:

- Derive `ExecuteFns` or `QueryFns` on the underlying structures
- Implement `From<Underlying>` for your contract message type

In general, every structure that implements the `Into` trait for the contract message will make the function available on the contract. To make that clearer, here's an example:

```rust,ignore
{{#include ../../../cw-orch/tests/impl_into.rs:impl_into}}
{{#include ../../../cw-orch/tests/underlying_into.rs:underlying_into}}
```

### `disable_fields_sorting` Attribute
Expand Down
Loading

0 comments on commit 31932f5

Please sign in to comment.