Skip to main content

Execute

In this chapter, we will write core execution logic for our contract.

Define ExecuteMsg

Alright, it's time to head back to a file we're familiar with src/msg.rs. Our ExecuteMsg looks like this:

src/msg.rs
pub enum ExecuteMsg {
Receive(Cw20ReceiveMsg),
}

Cw20ReceiveMsg should be de/serialized under Receive() variant in a ExecuteMsg. Receive must be implemented by any contract that wishes to manage CW20 tokens.

pub struct Cw20ReceiveMsg {
pub sender: String,
pub amount: Uint128,
pub msg: Binary,
}

About Cw20ReceiveMsg, it is a struct that has three fields: sender, amount and msg. The sender is the original account requesting to move the tokens and msg is a Binary data that can be decoded into a contract-specific message. amount is the amount of Cw20 tokens sent from sender to the contract.

So what's the idea here? Users will send some Cw20 tokens to our contract with a msg requesting the contract to mint one NFT for them.

Write execution logic

So now we implemented our execute messages, let's take a look at src/contract.rs to write execute logic.

src/contract.rs
#[cfg_attr(not(feature = "library"), entry_point)]
pub fn execute(
deps: DepsMut,
_env: Env,
info: MessageInfo,
msg: ExecuteMsg,
) -> Result<Response, ContractError> {
match msg {
ExecuteMsg::Receive(Cw20ReceiveMsg {
sender,
amount,
msg,
}) => execute_receive(deps, info, sender, amount, msg),
}
}

pub fn execute_receive(
deps: DepsMut,
info: MessageInfo,
sender: String,
amount: Uint128,
_msg: Binary,
) -> Result<Response, ContractError> {
let mut config = CONFIG.load(deps.storage)?;
if config.cw20_address != info.sender {
return Err(ContractError::UnauthorizedTokenContract {});
}

if config.cw721_address == None {
return Err(ContractError::Uninitialized {});
}

if config.unused_token_id >= config.max_tokens {
return Err(ContractError::SoldOut {});
}

if amount != config.unit_price {
return Err(ContractError::WrongPaymentAmount {});
}

let mint_msg = Cw721ExecuteMsg::<Extension, Empty>::Mint(MintMsg::<Extension> {
token_id: config.unused_token_id.to_string(),
owner: sender,
token_uri: config.token_uri.clone().into(),
extension: config.extension.clone(),
});

match config.cw721_address.clone() {
Some(cw721) => {
let callback =
Cw721Contract::<Empty, Empty>(cw721, PhantomData, PhantomData).call(mint_msg)?;
config.unused_token_id += 1;
CONFIG.save(deps.storage, &config)?;

Ok(Response::new().add_message(callback))
}
None => Err(ContractError::Cw721NotLinked {}),
}
}

In execute function, we use Rust's enum pattern matching to match the msg variable for all its different types. Here, our ExecuteMsg has only one variant is a tuple struct: Receive(Cw20ReceiveMsg{sender,amount,msg}). After determining type of the msg message variable is Cw20ReceiveMsg, we will use => to call the execute_receive function with its input is deps, info, sender, amount, msg.

Now, let's define the logic of execute_receive function.

In the first line, by using load(), we get the CONFIG Item data previously stored in the store and assign it to a config variable. Note that the mut keyword is used here because in this function we will modify the data of config.

The next four if statements is the logic to validate four fields: config.cw20_address, config.cw721_address, config.unused_token_id, and amount.

  • Firstly, if config.cw20_address is not equal to info.sender, the UnauthorizedTokenContract error is returned because our contract only accepts Cw20 tokens with token contract address specified in config.

  • Secondy, if config.cw721_address is None, the Uninitialized error will be returned.

  • Thirdly, if config.unused_token_id is greater than or equal to config.max_tokens, the SoldOut error will be returned. Because it means that the number of NFTs minted has reached the maximum, and the contract cannot receive Cw20 tokens to mint any more NFTs.

  • Fourth, if amount is not equal to config.unit_price, the WrongPaymentAmount will be returned because the fixed minting fee of one NFT must be equal to unit_price.

    Let define these 4 errors in src/error.rs.

src/error.rs
pub enum ContractError {

// Previous code omitted

#[error("SoldOut")]
SoldOut {},

#[error("UnauthorizedTokenContract")]
UnauthorizedTokenContract {},

#[error("Uninitialized")]
Uninitialized {},

#[error("WrongPaymentAmount")]
WrongPaymentAmount {},

#[error("Cw721NotLinked")]
Cw721NotLinked {},
// Following code omitted
}

In the next line, we define mint_msg message variable.

let mint_msg = Cw721ExecuteMsg::<Extension, Empty>::Mint(MintMsg::<Extension> {
token_id: config.unused_token_id.to_string(),
owner: sender,
token_uri: config.token_uri.clone().into(),
extension: config.extension.clone(),
});

mint_msg is used to mint NFT so it will be a message of type Cw721ExecuteMsg::Mint. Cw721ExecuteMsg is defined in cw721_base, it is a tuple struct with the inner struct is MintMsg (also defined in cw721_base). We define its fields (token_id, owner, token_uri, extension) like in the code above.

And finally, we again use the Rust enum pattern to define the callback message (callback message is the message sent by the contract after receiving an activation message).

match config.cw721_address.clone() {
Some(cw721) => {
let callback =
Cw721Contract::<Empty, Empty>(cw721, PhantomData, PhantomData).call(mint_msg)?;
config.unused_token_id += 1;
CONFIG.save(deps.storage, &config)?;

Ok(Response::new().add_message(callback))
}
None => Err(ContractError::Cw721NotLinked {}),
}

callback in here is a CosmosMsg message to trigger the mint function on the Cw721 contract that is link to our contract after instantiation. If the NFT is minted, its total number of NFTs will be increased by one, it is applied by updating config.unused_token_id and saving the config to storage.

If cw721_address is already specified in config (it means there is a cw721 contract linked to our contract), then we will use for execute_receive function a return line with Ok structure and add_message(callback). Otherwise, return Cw721NotLinked error.

Ok nice, in this chapter, we have defined the execute entry point for our contract. In the next chapter, we will define the last required entry point of a CosmWasm contract - query.