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:
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.
#[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 toinfo.sender
, theUnauthorizedTokenContract
error is returned because our contract only accepts Cw20 tokens with token contract address specified inconfig
.Secondy, if
config.cw721_address
is None, theUninitialized
error will be returned.Thirdly, if
config.unused_token_id
is greater than or equal toconfig.max_tokens
, theSoldOut
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 toconfig.unit_price
, theWrongPaymentAmount
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
.
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
.