The Fadroma Guide
Homepage Documentation Github Crates.io NPM Hack.bg

Writing smart contracts with Fadroma DSL

The fadroma-dsl crate defines a simple procedural macro-based DSL that helps you write composable, boilerplate-free CosmWasm smart contracts.

Fadroma DSL sets out to achieve two main goals:

Any misuse of the macro will result in simple compile errors -- not some hidden or unexpected runtime behavior. Additionally, the macro error messages should tell you exactly what you need to add or change in order for the code to compile.

Prerequisites

Using Fadroma DSL assumes you're familiar with the general architecture and operational model of CosmWasm smart contracts.

Examples

For a quick comparison of DSL-based code vs. "raw" CosmWasm, see Comparison.

See also: the examples directory in the Fadroma repo. These examples showcase how to use the DSL and the pre-defined DSL-based features in Fadroma. Furthermode, the DSL-based features can be used in non-DSL contracts.

Usage

Attribute macros

The DSL has the following attributes defined:

Note that you don't you don't have to remember all the attributes and rules around them because the compiler will guide you.

#[contract] mod

#[contract]
pub mod contract {
  /* ... */
}

#[interface] mod

#[init] fn

Meta arguments

#[execute] fn

#[query] fn

#[reply] fn

#[execute_guard] fn

#[auto_impl] impl

#[auto_impl(ImplementingStuct)]
impl MyInterface for Contract {
  // Here we leave the body empty and it will delegate the implementation to ImplementingStuct
  #[execute]
  fn first_method(some_arg: u32) -> Result<Response, Self::Error> {
    // The macro inserts the following code here
    // <ImplementingStuct as MyInterface>::first_method(deps, env, info, some_arg)
  }

  // Here we provide our own implementation so ImplementingStuct is not used at all.
  #[execute]
  fn second_method() -> Result<Response, Self::Error> {
    Ok(Response::default())
  }
}

Meta arguments

Attribute Generated signature
#[init] deps: DepsMut, env: Env, info: MessageInfo, ...msg
#[execute] deps: DepsMut, env: Env, info: MessageInfo, ...msg
#[query] deps: Deps, env: Env, ...msg
#[reply] deps: DepsMut, env: Env, ...msg
#[execute_guard] deps: DepsMut, env: &Env, info: &MessageInfo, ...msg

Comparison

To better understand what the macro generates, here's a simple contract:

#[fadroma::dsl::contract]
pub mod counter_contract {
    use fadroma::{
        dsl::*,
        admin::{self, Admin, Mode},
        schemars,
        cosmwasm_std::{self, Response, Addr, StdError}
    };

    impl Contract {
        #[init(entry_wasm)]
        pub fn new(initial_value: u64) -> Result<Response, StdError> {
            Ok(Response::default())
        }
        #[execute]
        pub fn add(value: u64) -> Result<Response, StdError> {
            Ok(Response::default())
        }
        #[query]
        pub fn value() -> Result<u64, StdError> {
            Ok(0)
        }
    }

    #[auto_impl(admin::DefaultImpl)]
    impl Admin for Contract {
        #[execute]
        fn change_admin(mode: Option<Mode>) -> Result<Response, Self::Error> { }
        #[query]
        fn admin() -> Result<Option<Addr>, Self::Error> { }
    }
}

And here's what the expanded code ends up looking like:

pub mod counter_contract {
    use fadroma::{
        dsl::*,
        admin::{self, Admin, Mode},
        schemars,
        cosmwasm_std::{self, Response, Addr, StdError}
    };

    #[derive(Clone, Copy)]
    pub struct Contract;

    impl Contract {
        pub fn new(
            mut deps: cosmwasm_std::DepsMut,
            env: cosmwasm_std::Env,
            info: cosmwasm_std::MessageInfo,
            initial_value: u64,
        ) -> Result<Response, StdError> {
            Ok(Response::default())
        }

        pub fn add(
            mut deps: cosmwasm_std::DepsMut,
            env: cosmwasm_std::Env,
            info: cosmwasm_std::MessageInfo,
            value: u64,
        ) -> Result<Response, StdError> {
            Ok(Response::default())
        }

        pub fn value(
            deps: cosmwasm_std::Deps,
            env: cosmwasm_std::Env
        ) -> Result<u64, StdError> {
            Ok(0)
        }
    }

    impl Admin for Contract {
        type Error = <admin::DefaultImpl as Admin>::Error;

        fn change_admin(
            mut deps: cosmwasm_std::DepsMut,
            env: cosmwasm_std::Env,
            info: cosmwasm_std::MessageInfo,
            mode: Option<Mode>,
        ) -> Result<Response, Self::Error> {
            <admin::DefaultImpl as Admin>::change_admin(deps, env, info, mode)
        }
        fn admin(
            deps: cosmwasm_std::Deps,
            env: cosmwasm_std::Env,
        ) -> Result<Option<Addr>, Self::Error> {
            <admin::DefaultImpl as Admin>::admin(deps, env)
        }
    }

    #[derive(serde::Serialize, serde::Deserialize, schemars::JsonSchema, Debug)]
    pub struct InstantiateMsg {
        pub initial_value: u64,
    }

    #[derive(serde::Serialize, serde::Deserialize, schemars::JsonSchema, Debug)]
    #[serde(rename_all = "snake_case")]
    pub enum ExecuteMsg {
        Add { value: u64 },
        ChangeAdmin { mode: Option<Mode> },
    }

    #[derive(serde::Serialize, serde::Deserialize, schemars::JsonSchema, Debug)]
    #[serde(rename_all = "snake_case")]
    pub enum QueryMsg {
        Value {},
        Admin {},
    }

    #[derive(Debug)]
    pub enum Error {
        // The macro needs this to signal errors when calling cosmwasm_std::to_binary
        // in the query function that it generates when the call fails.
        #[doc(hidden)]
        QueryResponseSerialize(String),
        // We call this for every method inside the impl Contract block.
        Base(StdError),
        // One for each interface implemented.
        Admin(<Contract as Admin>::Error),
    }

    impl std::fmt::Display for Error {
        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
            match self {
                Self::QueryResponseSerialize(msg) => f.write_fmt(
                    format_args!("Error serializing query response: {}", msg)
                ),
                Self::Base(x) => std::fmt::Display::fmt(x, f),
                Self::Admin(x) => std::fmt::Display::fmt(x, f),
            }
        }
    }

    impl std::error::Error for Error {}

    pub fn instantiate(
        mut deps: cosmwasm_std::DepsMut,
        env: cosmwasm_std::Env,
        info: cosmwasm_std::MessageInfo,
        msg: InstantiateMsg,
    ) -> Result<Response, StdError> {
        Contract::new(deps, env, info, msg.initial_value)
    }

    pub fn execute(
        mut deps: cosmwasm_std::DepsMut,
        env: cosmwasm_std::Env,
        info: cosmwasm_std::MessageInfo,
        msg: ExecuteMsg,
    ) -> std::result::Result<cosmwasm_std::Response, Error> {
        match msg {
            ExecuteMsg::Add { value } => {
                Contract::add(deps, env, info, value).map_err(|x| Error::Base(x))
            }
            ExecuteMsg::ChangeAdmin { mode } => {
                Contract::change_admin(deps, env, info, mode).map_err(|x| Error::Admin(x))
            }
        }
    }

    pub fn query(
        deps: cosmwasm_std::Deps,
        env: cosmwasm_std::Env,
        msg: QueryMsg,
    ) -> std::result::Result<cosmwasm_std::Binary, Error> {
        match msg {
            QueryMsg::Value {} => {
                let result = Contract::value(deps, env).map_err(|x| Error::Base(x))?;
                cosmwasm_std::to_binary(&result)
                    .map_err(|x| Error::QueryResponseSerialize(x.to_string()))
            }
            QueryMsg::Admin {} => {
                let result = Contract::admin(deps, env).map_err(|x| Error::Admin(x))?;
                cosmwasm_std::to_binary(&result)
                    .map_err(|x| Error::QueryResponseSerialize(x.to_string()))
            }
        }
    }

    #[cfg(target_arch = "wasm32")]
    mod wasm_entry {
        use super::cosmwasm_std::{do_instantiate, do_execute, do_query};

        #[no_mangle]
        extern "C" fn instantiate(env_ptr: u32, info_ptr: u32, msg_ptr: u32) -> u32 {
            do_instantiate(&super::instantiate, env_ptr, info_ptr, msg_ptr)
        }

        #[no_mangle]
        extern "C" fn execute(env_ptr: u32, info_ptr: u32, msg_ptr: u32) -> u32 {
            do_execute(&super::execute, env_ptr, info_ptr, msg_ptr)
        }

        #[no_mangle]
        extern "C" fn query(env_ptr: u32, msg_ptr: u32) -> u32 {
            do_query(&super::query, env_ptr, msg_ptr)
        }
    }
}