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

Use message composition to extend contracts #94

Closed
wants to merge 1 commit into from

Conversation

larry0x
Copy link
Collaborator

@larry0x larry0x commented Oct 22, 2022

This is an alternative approach to #42 and #91 on how to extend the CW-721 contract.

There are three approaches on how a custom NFT contract can inherit/extend the base cw721 contract:

  • 42: the base contract to provide a "wildcard" extension
  • 91: the base contract to provide a macro for extending the message enum
  • this PR: the child contract to compose message types

Comparison

In this section, we use an example to compare the three approaches.

Terminology: Let's say the contract being extended is the "parent" contract, while the contract extending it is the "child" contract.

For this example, we use cw721-base as the parent, and cw2981-royalties as the child. cw2981 wants to extend cw721-base's query message by adding a custom method, royalty_info.

#42

cw721-base/src/msg.rs

pub enum QueryMsg<Q> {
    ContractInfo { .. },
    NumTokens { .. },
    // ...

    // the wildcard extension
    Extension { query: Q },
}

cw2981-royalties/src/msg.rs

// the child defines its custom query methods as an enum...
pub enum CustomQuery {
    RoyaltyInfo {
        token_id: String,
        sale_price: Uint128,
    },
}

// ...and plug it into the parent enum as a generic
pub type QueryMsg = cw721_base::msg::QueryMsg<CustomQuery>;

cw2981-royalties/src/contract.rs

pub fn Query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult<Binary> {
    match msg {
        // if it is the extension, dispatch to the appropriate handler function
        QueryMsg::Extension { custom_query } => {
            match custom_query {
                CustomQuery::RoyaltyInfo { token_id, sale_price } => {
                    query_royalty_info(deps, token_id, sale_price)
                }
            }
        },

        // otherwise, dispatch it to the parent
        _ => {
            let parent = Cw721Contract::<Extension, CustomMsg, CustomQuery>::default();
            parent.query(deps, env, msg)
        },
    }
}

#90

cw721-base/src/msg.rs

// the parent defines a macro to "autofill" its query methods
#[proc_macro_attribute]
pub fn cw721_base_query(metadata: TokenStream, input: TokenStream) -> TokenStream {
    // ...
}

// the parent's message type
#[cw721_base_query]
#[cw_serde]
pub enum QueryMsg {}

cw2981-royalties/src/msg.rs

// the child applies the macro, and appends its custom methods below
#[cw721_base_query]
#[cw_serde]
pub enum QueryMsg {
    RoyaltyInfo {
        token_id: String,
        sale_price: Uint128,
    },
}

// the child also needs to implement a function to cast the custom message
// to the parent's one
impl TryFrom<QueryMsg> for cw721_base::QueryMsg {
    // ...
}

cw2981-royalties/src/contract.rs

pub fn Query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult<Binary> {
    match msg {
        // if it is the custom variant, dispatch to the appropriate handler function
        QueryMsg::RoyaltyInfo { token_id, sale_price } => {
            query_royalty_info(deps, token_id, sale_price)
        },

        // otherwise, try cast the message to the parent type, and dispatch to parent
        _ => {
            let parent = Cw721Contract::<Extension>::default();
            parent.query(deps, env, msg.try_into()?)
        },
    }
}

This PR

The parent does not provide any special arrangement for the child.

cw721-base/src/msg.rs

// the child defines its enum "nesting" the parent one
// note the use of serde "untagged" option
#[serde(untagged)]
pub enum QueryMsg {
    Parent(cw721_base::msg::QueryMsg),

    RoyaltyInfo {
        token_id: String,
        sale_price: Uint128,
    },
}

cw2981-royalties/src/contract.rs

pub fn Query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult<Binary> {
    match msg {
        QueryMsg::Parent(msg) => {
            let parent = Cw721Contract::<Extension>::default();
            parent.query(deps, env, msg)
        },
        QueryMsg::RoyaltyInfo { token_id, sale_price } => {
            query_royalty_info(deps, token_id, sale_price)
        },
    }
}

Overall, I feel this PR is the best solution

@larry0x
Copy link
Collaborator Author

larry0x commented Oct 22, 2022

It seems for some reason, using #[serde(untagged)] (the approach suggested by this PR) introduces float operators into the wasm build. ??

@larry0x larry0x closed this Oct 22, 2022
@AmitPr
Copy link

AmitPr commented Oct 22, 2022

@larry0x I had this issue a while back. It's because serde includes a debug/log function that handles an f64 value in one of the match arms. You can get around it by writing a custom serializer.

@larry0x
Copy link
Collaborator Author

larry0x commented Oct 22, 2022

@larry0x I had this issue a while back. It's because serde includes a debug/log function that handles an f64 value in one of the match arms. You can get around it by writing a custom serializer.

Yes this is a known issue: CosmWasm/serde-json-wasm#43

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants