CosmWasm / sylvia

CosmWasm smart contract framework
Apache License 2.0
96 stars 16 forks source link

Support defining custom entry points #189

Closed jawoznia closed 1 year ago

jawoznia commented 1 year ago

Motivation

As described in #139 although user can define custom entry_points and use them in the generated contract it is not possible to use it in generated multitests.

Solution

We could either use macro attribute pointing to the custom entry_point function or add method attribute inside of the macro. Macro attribute approach will be more flexible for the user.

Add missing Context type

pub struct SudoCtx<'a, C: CustomQuery = Empty> {
    pub deps: DepsMut<'a, C>,
    pub env: Env,
}

Detect attribute sv::custom_entry_point on contract

This attribute should be used both by contract macro to use the custom entry point in multitest helpers and by entry_points macro so that there will be no duplication after generation.

pub mod entry_points {
    #[entry_point]
    pub fn sudo(_deps: DepsMut, _env: Env) -> ... {
    }
}

#[cfg_attr(not(feature = "library"), entry_points)]
#[contract]
#[sv::custom_entry_point(sudo=entry_points::sudo)]
impl ReceiverContract {
    pub const fn new() -> Self {
        Self {}
    }

    #[msg(instantiate)]
    pub fn instantiate(&self, _ctx: InstantiateCtx) -> StdResult<Response> {
        todo!()    
    }
}

Allow overriding all entry points

This is the last step which aim is to make sylvia more flexible for the user. In case of some missing feature or individual need user should be able to override all of the entry points.

Alternative

Although support for sudo is not yet implemented we could allow defining sudo msg as it is done in case of reply. If detected sylvia will generate appropriate entry_point and implement cw_multi_test::Contract with this entry_point.

hashedone commented 1 year ago

To make it complete - it should be possible to overwrite all of the entry points to possibly overwrite some sylvia behaviour in case of need (in particular: missing features).

jawoznia commented 1 year ago

In case of overwriting entry points other than sudo it shouldn't cause any issues.

In case of sudo we still don't generate the SudoMsg. This causes an issue with dispatch.

For cw_multitest::Contract implementation on user contract we need to define the type of the incoming message.

For f.e. query we will cast the received byte array to generated `ContractQueryMsg' type.

fn query(
    &self,
    deps: sylvia ::cw_std::Deps<sylvia ::cw_std::Empty>,
    env: sylvia ::cw_std::Env,
    msg: Vec<u8>,
) -> sylvia ::anyhow::Result<sylvia ::cw_std::Binary> {
    sylvia ::cw_std::from_slice::<ContractQueryMsg>(&msg)?
        .dispatch(self, (deps, env))
        .map_err(Into::into)
}

For sudo we don't have a message to base on so we would have to either require the user to use message with standarized name SudoMsg with dispatch method or we could generate the SudoMsg and thus provide support for multitest and entry points (please refer to the Alternative section in original description).

With all this I would suggest to first implement basic support for sudo and then on top of that introduce entry point overwriting with restriction that entry points need to use messages generated using #[msg(...)] attribute.

hashedone commented 1 year ago

See your point here - you need to know the type of the message to properly parse it in particular places.

Using "standardized" name is always a wrong decision - you should have as minimal restrictions on code shape as possible. It might be reasonable default to reduce the simple case boilerplate, but there should always be a way to provide the custom typename.

What I would do here is just alter the syntax a bit, to provide space for naming the type, like:

#[cfg_attr(not(feature = "library"), entry_points)]
#[contract]
#[sv::custom_entry_point(sudo=entry_points::sudo(SudoMsg))]
impl ReceiverContract {
    pub const fn new() -> Self {
        Self {}
    }

    #[msg(instantiate)]
    pub fn instantiate(&self, _ctx: InstantiateCtx) -> StdResult<Response> {
        todo!()    
    }
}

This is just an example; if you have an idea for better syntax feel free to suggest it. In this case I would not provide "default" name - the syntax is to be used rather rarely for cases where Sylvia is behind needs, So it is already "strange", and default - even if reasonable - is not super obvious. Because of that default would probably only bring confusion, and I would make providing the typename required.