var img = document.createElement('img'); img.src = "https://terradocs.matomo.cloud//piwik.php?idsite=1&rec=1&url=https://docs.terra.money" + location.pathname; img.style = "border:0"; img.alt = "tracker"; var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(img,s);
Skip to main content

Build a CW20 token factory

In this tutorial, you'll build a CW20 token factory. A token factory allows any Cosmos SDK address (including a contract) to mint a new fungible token.

Prerequisites

Use the following guides to set up your environment:

You'll also need:

  • An IDE or text editor of your choice. This tutorial will use Visual Studio Code.
  • A command line interface.

Instantiate a new app using Terrain

  1. Instantiate a new app using Terrain.
Terminal
Copy

_1
terrain new token_factory

  1. When the app is generated, the following displays.
Terminal
Output
Copy

_8
terrain new token_factory
_8
_8
generating app token_factory:
_8
- workspace... done
_8
- contract... done
_8
- frontend... done
_8
- installing app dependencies... done
_8
- installing frontend dependencies... done

  1. Instantiate a new app using Terrain.
  1. When the app is generated, the following displays.
Terminal
CopyExpandClose

_1
terrain new token_factory

Generate the cw20_factory_token contract

  1. Navigate to the token_factory directory.
Terminal
Copy

_1
cd token_factory

  1. Create the cw20_factory_token contract.
Terminal
Copy

_2
cd token_factory
_2
terrain contract:new cw20_factory_token

  1. When the contract is generated, the following displays:
Terminal
Output
Copy

_3
terrain contract:new cw20_factory_token
_3
generating contract cw20_factory_token:
_3
- contract... done

  1. Navigate to the token_factory directory.
  1. Create the cw20_factory_token contract.
  1. When the contract is generated, the following displays:
Terminal
CopyExpandClose

_1
cd token_factory

Modify the mnemonic passphrase

Before editing the smart contracts you created in step 2, modify the mnemonic you'll use to deploy the contract to LocalTerra:

  1. Open /keys.terrain.js and set the mnemonic to the following.

_1
notice oak worry limit wrap speak medal online prefer cluster roof addict wrist behave treat actual wasp year salad speed social layer crew genius

💡tip

Because the wallet contains funds, it is recommended that you also import the passphrase listed below into the Station Extension. You can view other example mnemonics on Github:


_1
notice oak worry limit wrap speak medal online prefer cluster roof addict wrist behave treat actual wasp year salad speed social layer crew genius

The module.exports section of your keys.terrain.js file should now look similar to the following.


_6
module.exports = {
_6
test: {
_6
mnemonic:
_6
'notice oak worry limit wrap speak medal online prefer cluster roof addict wrist behave treat actual wasp year salad speed social layer crew genius',
_6
},
_6
};

Deploy the smart contracts

The token_factory contract mints new cw20_factory_token tokens, increases the supply of minted tokens, burns tokens to return LUNA, and tracks all tokens created.

Deploy each smart contract to ensure that the development environment is configured correctly:

  1. Deploy the token_factory contract.
Terminal
Copy

_1
terrain deploy token_factory --signer test

  1. Deploy the cw20_factory_token contract.
Terminal
Copy

_3
terrain deploy token_factory --signer test
_3
_3
terrain deploy cw20_factory_token --signer test

  1. Deploy the token_factory contract.
  1. Deploy the cw20_factory_token contract.
Terminal
CopyExpandClose

_1
terrain deploy token_factory --signer test

Modify the CW20 Factory Token smart contract

In this section, you will modify the cw20_factory_token contract that you instantiated. This contract implements the CW20 Base, along with several custom files.

To modify the cw20_factory_token contract, follow the procedure below.

1. Add the the CW20 base

First, add the CW20 Base, which implements the base CW20 token functionalities. This allows for:

To add the CW20 Base to the cw20_factory_token contract, do the following.

  1. Navigate to the /cw20_factory_token/ directory.
Terminal
Copy

_1
cd /token_factory/contracts/cw20_factory_token/

  1. Open cargo.toml and add this to the dependencies:
Terminal
cargo.toml
Copy

_6
# ...
_6
_6
[dependencies]
_6
cw20-base = { version = "0.13.2", features = ["library"] }
_6
_6
# ...

  1. Navigate to the /cw20_factory_token/ directory.
  1. Open cargo.toml and add this to the dependencies:
Terminal
CopyExpandClose

_1
cd /token_factory/contracts/cw20_factory_token/

2. Modify the contract files

Now that you've added the CW20 Base to implement the base CW20 token logic, modify the following files:

  • msg.rs
  • lib.rs
  • contract.rs
  • schemas.rs

To modify the contract files, follow the procedure below.

msg.rs

  1. Navigate to the /cw20_factory_token/src directory.
Terminal
Copy

_2
_2
cd /token_factory/contracts/cw20_factory_token/src

  1. Open msg.rs in your code editor and paste the following.
Terminal
msg.rs
Copy

_5
use schemars::JsonSchema;
_5
use serde::{Deserialize, Serialize};
_5
_5
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
_5
pub struct MigrateMsg {}

  1. Save and close msg.rs.
  1. Navigate to the /cw20_factory_token/src directory.
  1. Open msg.rs in your code editor and paste the following.
  1. Save and close msg.rs.
Terminal
CopyExpandClose

_2
_2
cd /token_factory/contracts/cw20_factory_token/src

lib.rs

  1. Open lib.rs and paste the following.
lib.rs
Copy

_2
pub mod contract;
_2
pub mod msg;

  1. Save and close lib.rs.
  1. Open lib.rs and paste the following.
  1. Save and close lib.rs.
lib.rs
CopyExpandClose

_2
pub mod contract;
_2
pub mod msg;

contract.rs

  1. Open contract.rs and paste the following.
contract.rs
Copy

_116
#[cfg(not(feature = "library"))]
_116
use cosmwasm_std::entry_point;
_116
use cosmwasm_std::{
_116
to_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult,
_116
};
_116
use cw20_base::ContractError;
_116
use cw20_base::enumerable::{query_all_allowances, query_all_accounts};
_116
use cw20_base::msg::{QueryMsg,ExecuteMsg};
_116
_116
use crate::msg::MigrateMsg;
_116
use cw2::set_contract_version;
_116
use cw20_base::allowances::{
_116
execute_decrease_allowance, execute_increase_allowance, execute_send_from,
_116
execute_transfer_from, query_allowance, execute_burn_from,
_116
};
_116
use cw20_base::contract::{
_116
execute_mint, execute_send, execute_transfer, execute_update_marketing,
_116
execute_upload_logo, query_balance, query_token_info, query_minter, query_download_logo, query_marketing_info, execute_burn,
_116
};
_116
_116
// version info for migration info
_116
const CONTRACT_NAME: &str = "crates.io:cw20_factory_token";
_116
const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION");
_116
_116
#[cfg_attr(not(feature = "library"), entry_point)]
_116
pub fn instantiate(
_116
deps: DepsMut,
_116
env: Env,
_116
info: MessageInfo,
_116
msg: cw20_base::msg::InstantiateMsg,
_116
) -> Result<Response, ContractError> {
_116
set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?;
_116
_116
/* Execute the instantiate method from cw_20_base as the code from that
_116
library is already battle tested we do not have to re-write the full
_116
functionality: https://github.com/CosmWasm/cw-plus/tree/main/contracts/cw20-base*/
_116
Ok(cw20_base::contract::instantiate(deps, env, info, msg)?)
_116
}
_116
_116
#[cfg_attr(not(feature = "library"), entry_point)]
_116
pub fn execute(
_116
deps: DepsMut,
_116
env: Env,
_116
info: MessageInfo,
_116
msg: ExecuteMsg,
_116
) -> Result<Response, cw20_base::ContractError> {
_116
match msg {
_116
ExecuteMsg::Transfer { recipient, amount } => {
_116
execute_transfer(deps, env, info, recipient, amount)
_116
}
_116
ExecuteMsg::Burn { amount } => execute_burn(deps, env, info, amount),
_116
ExecuteMsg::Send {
_116
contract,
_116
amount,
_116
msg,
_116
} => execute_send(deps, env, info, contract, amount, msg),
_116
ExecuteMsg::Mint { recipient, amount } => execute_mint(deps, env, info, recipient, amount),
_116
ExecuteMsg::IncreaseAllowance {
_116
spender,
_116
amount,
_116
expires,
_116
} => execute_increase_allowance(deps, env, info, spender, amount, expires),
_116
ExecuteMsg::DecreaseAllowance {
_116
spender,
_116
amount,
_116
expires,
_116
} => execute_decrease_allowance(deps, env, info, spender, amount, expires),
_116
ExecuteMsg::TransferFrom {
_116
owner,
_116
recipient,
_116
amount,
_116
} => execute_transfer_from(deps, env, info, owner, recipient, amount),
_116
ExecuteMsg::BurnFrom { owner, amount } => execute_burn_from(deps, env, info, owner, amount),
_116
ExecuteMsg::SendFrom {
_116
owner,
_116
contract,
_116
amount,
_116
msg,
_116
} => execute_send_from(deps, env, info, owner, contract, amount, msg),
_116
ExecuteMsg::UpdateMarketing {
_116
project,
_116
description,
_116
marketing,
_116
} => execute_update_marketing(deps, env, info, project, description, marketing),
_116
ExecuteMsg::UploadLogo(logo) => execute_upload_logo(deps, env, info, logo),
_116
}
_116
}
_116
_116
#[cfg_attr(not(feature = "library"), entry_point)]
_116
pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult<Binary> {
_116
match msg {
_116
/* Default methods from CW20 Standard with no modifications:
_116
https://github.com/CosmWasm/cw-plus/tree/main/contracts/cw20-base */
_116
QueryMsg::Balance { address } => to_binary(&query_balance(deps, address)?),
_116
QueryMsg::TokenInfo {} => to_binary(&query_token_info(deps)?),
_116
QueryMsg::Minter {} => to_binary(&query_minter(deps)?),
_116
QueryMsg::Allowance { owner, spender } => {
_116
to_binary(&query_allowance(deps, owner, spender)?)
_116
}
_116
QueryMsg::AllAllowances {
_116
owner,
_116
start_after,
_116
limit,
_116
} => to_binary(&query_all_allowances(deps, owner, start_after, limit)?),
_116
QueryMsg::AllAccounts { start_after, limit } => {
_116
to_binary(&query_all_accounts(deps, start_after, limit)?)
_116
}
_116
QueryMsg::MarketingInfo {} => to_binary(&query_marketing_info(deps)?),
_116
QueryMsg::DownloadLogo {} => to_binary(&query_download_logo(deps)?),
_116
}
_116
}
_116
_116
#[cfg_attr(not(feature = "library"), entry_point)]
_116
pub fn migrate(_deps: DepsMut, _env: Env, _msg: MigrateMsg) -> StdResult<Response> {
_116
Ok(Response::default())
_116
}

  1. Save and close contract.rs.
  1. Open contract.rs and paste the following.
  1. Save and close contract.rs.
contract.rs
CopyExpandClose

_116
#[cfg(not(feature = "library"))]
_116
use cosmwasm_std::entry_point;
_116
use cosmwasm_std::{
_116
to_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult,
_116
};
_116
use cw20_base::ContractError;
_116
use cw20_base::enumerable::{query_all_allowances, query_all_accounts};
_116
use cw20_base::msg::{QueryMsg,ExecuteMsg};
_116
_116
use crate::msg::MigrateMsg;
_116
use cw2::set_contract_version;
_116
use cw20_base::allowances::{
_116
execute_decrease_allowance, execute_increase_allowance, execute_send_from,
_116
execute_transfer_from, query_allowance, execute_burn_from,
_116
};
_116
use cw20_base::contract::{
_116
execute_mint, execute_send, execute_transfer, execute_update_marketing,
_116
execute_upload_logo, query_balance, query_token_info, query_minter, query_download_logo, query_marketing_info, execute_burn,
_116
};
_116
_116
// version info for migration info
_116
const CONTRACT_NAME: &str = "crates.io:cw20_factory_token";
_116
const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION");
_116
_116
#[cfg_attr(not(feature = "library"), entry_point)]
_116
pub fn instantiate(
_116
deps: DepsMut,
_116
env: Env,
_116
info: MessageInfo,
_116
msg: cw20_base::msg::InstantiateMsg,
_116
) -> Result<Response, ContractError> {
_116
set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?;
_116
_116
/* Execute the instantiate method from cw_20_base as the code from that
_116
library is already battle tested we do not have to re-write the full
_116
functionality: https://github.com/CosmWasm/cw-plus/tree/main/contracts/cw20-base*/
_116
Ok(cw20_base::contract::instantiate(deps, env, info, msg)?)
_116
}
_116
_116
#[cfg_attr(not(feature = "library"), entry_point)]
_116
pub fn execute(
_116
deps: DepsMut,
_116
env: Env,
_116
info: MessageInfo,
_116
msg: ExecuteMsg,
_116
) -> Result<Response, cw20_base::ContractError> {
_116
match msg {
_116
ExecuteMsg::Transfer { recipient, amount } => {
_116
execute_transfer(deps, env, info, recipient, amount)
_116
}
_116
ExecuteMsg::Burn { amount } => execute_burn(deps, env, info, amount),
_116
ExecuteMsg::Send {
_116
contract,
_116
amount,
_116
msg,
_116
} => execute_send(deps, env, info, contract, amount, msg),
_116
ExecuteMsg::Mint { recipient, amount } => execute_mint(deps, env, info, recipient, amount),
_116
ExecuteMsg::IncreaseAllowance {
_116
spender,
_116
amount,
_116
expires,
_116
} => execute_increase_allowance(deps, env, info, spender, amount, expires),
_116
ExecuteMsg::DecreaseAllowance {
_116
spender,
_116
amount,
_116
expires,
_116
} => execute_decrease_allowance(deps, env, info, spender, amount, expires),
_116
ExecuteMsg::TransferFrom {
_116
owner,
_116
recipient,
_116
amount,
_116
} => execute_transfer_from(deps, env, info, owner, recipient, amount),
_116
ExecuteMsg::BurnFrom { owner, amount } => execute_burn_from(deps, env, info, owner, amount),
_116
ExecuteMsg::SendFrom {
_116
owner,
_116
contract,
_116
amount,
_116
msg,
_116
} => execute_send_from(deps, env, info, owner, contract, amount, msg),
_116
ExecuteMsg::UpdateMarketing {
_116
project,
_116
description,
_116
marketing,
_116
} => execute_update_marketing(deps, env, info, project, description, marketing),
_116
ExecuteMsg::UploadLogo(logo) => execute_upload_logo(deps, env, info, logo),
_116
}
_116
}
_116
_116
#[cfg_attr(not(feature = "library"), entry_point)]
_116
pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult<Binary> {
_116
match msg {
_116
/* Default methods from CW20 Standard with no modifications:
_116
https://github.com/CosmWasm/cw-plus/tree/main/contracts/cw20-base */
_116
QueryMsg::Balance { address } => to_binary(&query_balance(deps, address)?),
_116
QueryMsg::TokenInfo {} => to_binary(&query_token_info(deps)?),
_116
QueryMsg::Minter {} => to_binary(&query_minter(deps)?),
_116
QueryMsg::Allowance { owner, spender } => {
_116
to_binary(&query_allowance(deps, owner, spender)?)
_116
}
_116
QueryMsg::AllAllowances {
_116
owner,
_116
start_after,
_116
limit,
_116
} => to_binary(&query_all_allowances(deps, owner, start_after, limit)?),
_116
QueryMsg::AllAccounts { start_after, limit } => {
_116
to_binary(&query_all_accounts(deps, start_after, limit)?)
_116
}
_116
QueryMsg::MarketingInfo {} => to_binary(&query_marketing_info(deps)?),
_116
QueryMsg::DownloadLogo {} => to_binary(&query_download_logo(deps)?),
_116
}
_116
}
_116
_116
#[cfg_attr(not(feature = "library"), entry_point)]
_116
pub fn migrate(_deps: DepsMut, _env: Env, _msg: MigrateMsg) -> StdResult<Response> {
_116
Ok(Response::default())
_116
}

schema.rs

  1. Open schemas.rs and paste the following.
schemas.rs
Copy

_17
use std::env::current_dir;
_17
use std::fs::create_dir_all;
_17
_17
use cosmwasm_schema::{export_schema, remove_schemas, schema_for};
_17
use cw20_base::msg::{InstantiateMsg, QueryMsg, ExecuteMsg};
_17
_17
_17
fn main() {
_17
let mut out_dir = current_dir().unwrap();
_17
out_dir.push("schema");
_17
create_dir_all(&out_dir).unwrap();
_17
remove_schemas(&out_dir).unwrap();
_17
_17
export_schema(&schema_for!(InstantiateMsg), &out_dir);
_17
export_schema(&schema_for!(ExecuteMsg), &out_dir);
_17
export_schema(&schema_for!(QueryMsg), &out_dir);
_17
}

  1. Save and close schemas.rs.
  1. Open schemas.rs and paste the following.
  1. Save and close schemas.rs.
schemas.rs
CopyExpandClose

_17
use std::env::current_dir;
_17
use std::fs::create_dir_all;
_17
_17
use cosmwasm_schema::{export_schema, remove_schemas, schema_for};
_17
use cw20_base::msg::{InstantiateMsg, QueryMsg, ExecuteMsg};
_17
_17
_17
fn main() {
_17
let mut out_dir = current_dir().unwrap();
_17
out_dir.push("schema");
_17
create_dir_all(&out_dir).unwrap();
_17
remove_schemas(&out_dir).unwrap();
_17
_17
export_schema(&schema_for!(InstantiateMsg), &out_dir);
_17
export_schema(&schema_for!(ExecuteMsg), &out_dir);
_17
export_schema(&schema_for!(QueryMsg), &out_dir);
_17
}

3. Generate and test the schema

  1. Navigate to /token_factory/contracts/cw20_factory_token.
Terminal
Copy

_1
cd /token_factory/contracts/cw20_factory_token

  1. Generate the new schema.
Terminal
Copy

_2
cd /token_factory/contracts/cw20_factory_token
_2
cargo schema

  1. Test the schema.
Terminal
Copy

_3
cd /token_factory/contracts/cw20_factory_token
_3
cargo schema
_3
cargo test

  1. Navigate to /token_factory/contracts/cw20_factory_token.
  1. Generate the new schema.
  1. Test the schema.
Terminal
CopyExpandClose

_1
cd /token_factory/contracts/cw20_factory_token

4. Modify config.terrain.json

  1. Open config.terrain.json.

  2. Modify the InstantiateMsg property in the config.terrain.json so that it contains the name, symbol, decimals, and initial_balances shown in the example. This allows you to send the correct data to the smart contract upon instantiation.

config.terrain.json
Copy

_19
{
_19
"_global": {
_19
"_base": {
_19
"instantiation": {
_19
"instantiateMsg": {
_19
"name": "Bit Money",
_19
"symbol": "BTM",
_19
"decimals": 2,
_19
"initial_balances": [
_19
{
_19
"amount": "123",
_19
"address": "terra1x46rqay4d3cssq8gxxvqz8xt6nwlz4td20k38v"
_19
}
_19
]
_19
}
_19
}
_19
}
_19
} // ...
_19
}

  1. Open config.terrain.json.

  2. Modify the InstantiateMsg property in the config.terrain.json so that it contains the name, symbol, decimals, and initial_balances shown in the example. This allows you to send the correct data to the smart contract upon instantiation.

config.terrain.json
CopyExpandClose

_19
{
_19
"_global": {
_19
"_base": {
_19
"instantiation": {
_19
"instantiateMsg": {
_19
"name": "Bit Money",
_19
"symbol": "BTM",
_19
"decimals": 2,
_19
"initial_balances": [
_19
{
_19
"amount": "123",
_19
"address": "terra1x46rqay4d3cssq8gxxvqz8xt6nwlz4td20k38v"
_19
}
_19
]
_19
}
_19
}
_19
}
_19
} // ...
_19
}

5. Test the smart contract deployment

Deploy the contract again to confirm that the workplace still compiles.

Terminal
Copy

_1
terrain deploy cw20_factory_token --signer test

Deploy the contract again to confirm that the workplace still compiles.

Terminal
CopyExpandClose

_1
terrain deploy cw20_factory_token --signer test

💡tip

If your code is not working as expected, you can clone the repo with all the changes described above so that you can continue with the tutorial. To clone the repo, do the following.


_3
git clone -n https://github.com/emidev98/token-factory
_3
cd token-factory
_3
git checkout 8da7892486704c54e33442b156d63178f5137527

6. Use crate.io to implement the CW20 Token Factory as a dependency

For the purpose of this tutorial, crates.io is used to implement the CW20 Token Factory as a dependency. This ensures that the CW20 Token Factory is platform agnostic, so you can use Linux, Windows or Mac.

As the deployment to crates.io is out of scope for this tutorial, you can find the CW20 Token Factory package deployed to crates.io. You can use this deployment when you add the CW20 Token Factory contract as a dependency of the Token Factory contract in the the next section.

Create the Token Factory smart contract

To set up the contract, follow the procedure below:

1. Add the dependencies

In this section, you will add the following dependencies to cargo.toml:

  • cw2
  • cw20
  • cw20-base
  • cw20_factory_token

To add the dependencies, do the following.

  1. Navigate to /token_factory/contracts/token_factory.

  2. Open cargo.toml and add the dependencies inside the header.

cargo.toml
Copy

_7
# ...
_7
[dependencies]
_7
cw2 = "0.13.2"
_7
cw20 = "0.13.2"
_7
cw20-base = { version = "0.13.2", features = ["library"] }
_7
cw20-factory-token = { version = "0.6.0", features = ["library"] }
_7
# ...

  1. Navigate to /token_factory/contracts/token_factory.

  2. Open cargo.toml and add the dependencies inside the header.

cargo.toml
CopyExpandClose

_7
# ...
_7
[dependencies]
_7
cw2 = "0.13.2"
_7
cw20 = "0.13.2"
_7
cw20-base = { version = "0.13.2", features = ["library"] }
_7
cw20-factory-token = { version = "0.6.0", features = ["library"] }
_7
# ...

2. Modify the contract files

Now that you've added the dependencies, you will need to modify the following files:

  • error.rs
  • msg.rs
  • lib.rs
  • state.rs
  • contract.rs
  • test.rs

To modify the contract files, follow the procedure below:

  1. Navigate to /token_factory/contracts/token_factory/src.

  2. Open error.rs and add the following.

error.rs
Copy

_37
use cosmwasm_std::{StdError, Uint128};
_37
use thiserror::Error;
_37
_37
#[derive(Error, Debug, PartialEq)]
_37
pub enum ContractError {
_37
#[error("{0}")]
_37
Std(#[from] StdError),
_37
_37
#[error("Unauthorized")]
_37
Unauthorized {},
_37
_37
#[error("NotReceivedFunds")]
_37
NotReceivedFunds {},
_37
_37
#[error("NotAllowZeroAmount")]
_37
NotAllowZeroAmount {},
_37
_37
_37
#[error("NotAllowedDenom")]
_37
NotAllowedDenom {
_37
denom: String
_37
},
_37
_37
_37
#[error("NotAllowedMultipleDenoms")]
_37
NotAllowedMultipleDenoms {},
_37
_37
#[error("TokenAddressMustBeWhitelisted")]
_37
TokenAddressMustBeWhitelisted {},
_37
_37
#[error("ReceivedFundsMismatchWithMintAmount")]
_37
ReceivedFundsMismatchWithMintAmount {
_37
received_amount: Uint128,
_37
expected_amount: Uint128
_37
},
_37
_37
}

  1. Save and close error.rs.
  1. Open msg.rs and add the following.
error.rs
msg.rs
Copy

_59
use cosmwasm_std::Uint128;
_59
use schemars::JsonSchema;
_59
use serde::{Deserialize, Serialize};
_59
_59
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
_59
pub struct InstantiateMsg {
_59
/* Denomination of the stable asset
_59
https://docs.terra.money/docs/develop/module-specifications/spec-market.html#market */
_59
pub stable_denom: String,
_59
_59
/* Id of the contract uploaded for the first time to the chain
_59
https://docs.terra.money/docs/develop/module-specifications/spec-wasm.html#code-id */
_59
pub token_contract_code_id: u64,
_59
}
_59
_59
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
_59
#[serde(rename_all = "snake_case")]
_59
pub enum ExecuteMsg {
_59
/* Handle the deposits of native tokens into the smart contract to mint
_59
the new pegged token 1:1 with LUNA or to increase circulation supply. */
_59
Deposit(DepositType),
_59
_59
/* Handle burn of pegged tokens 1:1 with LUNA which are added to
_59
MINTED_TOKENS list and return the LUNA stored into the contract. */
_59
Burn {
_59
amount: Uint128,
_59
token_address: String,
_59
},
_59
}
_59
_59
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
_59
#[serde(rename_all = "snake_case")]
_59
pub enum DepositType {
_59
/* Instantiate a CW20_base token */
_59
Instantiate(cw20_base::msg::InstantiateMsg),
_59
_59
/* Create new tokens based on token_address, amount of LUNA send to
_59
this contract and recipient address */
_59
Mint {
_59
token_address: String,
_59
recipient: String,
_59
},
_59
}
_59
_59
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
_59
#[serde(rename_all = "snake_case")]
_59
pub enum QueryMsg {
_59
/* Returns the list of token addresses that were created with this contract */
_59
GetMintedTokens {},
_59
}
_59
_59
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
_59
#[serde(rename_all = "snake_case")]
_59
pub struct MintedTokens {
_59
pub minted_tokens: Vec<String>,
_59
}
_59
_59
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
_59
pub struct MigrateMsg {}

  1. Save and close msg.rs.
  1. Open state.rs and add the following.
error.rs
msg.rs
state.rs
Copy

_18
use schemars::JsonSchema;
_18
use serde::{Deserialize, Serialize};
_18
use cw_storage_plus::Item;
_18
_18
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
_18
pub struct Config {
_18
/* Denomination of the stable asset
_18
https://docs.terra.money/docs/develop/module-specifications/spec-market.html#market */
_18
pub stable_denom: String,
_18
_18
/* Id of the contract uploaded to the chain used to instantiate
_18
the different tokens
_18
https://docs.terra.money/docs/develop/module-specifications/spec-wasm.html#code-id */
_18
pub token_contract_code_id: u64,
_18
}
_18
_18
pub const CONFIG: Item<Config> = Item::new("config");
_18
pub const MINTED_TOKENS: Item<Vec<String>> = Item::new("minted_tokens");

  1. Save and close state.rs.
  1. Open lib.rs and add the following.
error.rs
msg.rs
state.rs
lib.rs
Copy

_6
pub mod contract;
_6
pub mod msg;
_6
pub mod state;
_6
pub mod error;
_6
mod test;
_6
pub use crate::error::ContractError;

  1. Save and close lib.rs.
  1. Open contract.rs and add the following.
error.rs
msg.rs
state.rs
lib.rs
contract.rs
Copy

_344
use crate::error::ContractError;
_344
use crate::msg::{DepositType, ExecuteMsg, InstantiateMsg, MigrateMsg, MintedTokens, QueryMsg};
_344
use std::vec;
_344
use crate::state::{Config, CONFIG, MINTED_TOKENS};
_344
_344
#[cfg(not(feature = "library"))]
_344
use cosmwasm_std::entry_point;
_344
use cosmwasm_std::{
_344
coins, to_binary, BankMsg, Binary, Coin, CosmosMsg, Deps, DepsMut, Env, MessageInfo, Reply,
_344
Response, StdError, StdResult, SubMsg, Uint128, WasmMsg,
_344
};
_344
use cw2::set_contract_version;
_344
_344
/* Define contract name and version */
_344
_344
const CONTRACT_NAME: &str = "crates.io:token_factory";
_344
const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION");
_344
const INSTANTIATE_REPLY_ID: u64 = 1;
_344
_344
#[cfg_attr(not(feature = "library"), entry_point)]
_344
pub fn instantiate(
_344
deps: DepsMut,
_344
_env: Env,
_344
_info: MessageInfo,
_344
msg: InstantiateMsg,
_344
) -> Result<Response, ContractError> {
_344
/* Define the initial configuration for this contract that way you can
_344
limit the type of coin you want to accept each time a token_factory is
_344
created and also which kind of token would you like to mint based on
_344
the code id of the contract deployed */
_344
let state = Config {
_344
stable_denom: msg.stable_denom.to_string(),
_344
token_contract_code_id: msg.token_contract_code_id,
_344
};
_344
_344
set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?;
_344
CONFIG.save(deps.storage, &state)?;
_344
MINTED_TOKENS.save(deps.storage, &Vec::new())?;
_344
_344
Ok(Response::new()
_344
.add_attribute("method", "instantiate")
_344
.add_attribute(
_344
"token_contract_code_id",
_344
msg.token_contract_code_id.to_string(),
_344
))
_344
}
_344
_344
#[cfg_attr(not(feature = "library"), entry_point)]
_344
_344
pub fn execute(
_344
deps: DepsMut,
_344
env: Env,
_344
info: MessageInfo,
_344
msg: ExecuteMsg,
_344
) -> Result<Response, ContractError> {
_344
match msg {
_344
/* Method executed each time someone send funds to the contract to mint
_344
a new token or to increase already existent tokens circulating supply */
_344
ExecuteMsg::Deposit(deposit_type) => match_deposit(deps.as_ref(), env, info, deposit_type),
_344
_344
/* Method used to burn an existent token created thru this contract
_344
and send the LUNA back to the address that burn these tokens.*/
_344
ExecuteMsg::Burn {
_344
amount,
_344
token_address,
_344
} => execute_burn_from(deps, info, amount, token_address),
_344
}
_344
}
_344
_344
pub fn match_deposit(
_344
deps: Deps,
_344
env: Env,
_344
info: MessageInfo,
_344
deposit_type: DepositType,
_344
) -> Result<Response, ContractError> {
_344
match deposit_type {
_344
/* When the InstantiateMsg struct is send the factory will
_344
execute this code and a new token with the defined properties
_344
will be minted */
_344
DepositType::Instantiate(token_data) => {
_344
execute_instantiate_token(deps, env, info, token_data)
_344
}
_344
_344
/* If a token_address and recipient is received along with a
_344
deposit this method will increase the supply of an already
_344
existent token by the defined units of LUNA received */
_344
DepositType::Mint {
_344
token_address,
_344
_344
recipient,
_344
} => execute_mint(deps, info, token_address, recipient),
_344
}
_344
}
_344
_344
pub fn execute_instantiate_token(
_344
deps: Deps,
_344
env: Env,
_344
info: MessageInfo,
_344
mut token_data: cw20_base::msg::InstantiateMsg,
_344
) -> Result<Response, ContractError> {
_344
let config = CONFIG.load(deps.storage)?;
_344
let received_funds = get_received_funds(&deps, &info)?;
_344
let mut expected_amount = Uint128::zero();
_344
_344
/* Add all initial token supply */
_344
token_data
_344
.initial_balances
_344
.iter()
_344
.for_each(|t| expected_amount += t.amount);
_344
_344
/* Check if received_funds is different than
_344
initial token supply and throw an error */
_344
if expected_amount.ne(&received_funds.amount) {
_344
return Err(ContractError::ReceivedFundsMismatchWithMintAmount {
_344
received_amount: received_funds.amount,
_344
_344
expected_amount,
_344
});
_344
}
_344
_344
/* If a minter exists replace the minter address with
_344
the token_factory address that way the minting is only
_344
allowed thru this smart contract. */
_344
token_data.mint = match token_data.mint {
_344
None => None,
_344
Some(mut e) => {
_344
e.minter = env.contract.address.to_string();
_344
_344
Some(e)
_344
}
_344
};
_344
_344
/* Create a WasmMsg to mint new CW20-base token.
_344
https://github.com/CosmWasm/cw-plus/tree/0.9.x/contracts/cw20-base */
_344
let instantiate_message = WasmMsg::Instantiate {
_344
admin: Some(env.contract.address.to_string()),
_344
code_id: config.token_contract_code_id,
_344
msg: to_binary(&token_data)?,
_344
funds: vec![],
_344
label: token_data.name,
_344
};
_344
_344
/* Define the SubMessage on CosmWasm API to allow a callback on reply
_344
entry point. This call will be executed with INSTANTIATE_REPLY_ID if
_344
the call succeed after being executed by the method add_submessage(response)
_344
from Response implementation.
_344
More Info: https://docs.cosmwasm.com/docs/1.0/smart-contracts/message/submessage */
_344
let sub_msg = SubMsg::reply_on_success(instantiate_message, INSTANTIATE_REPLY_ID);
_344
_344
/* Respond with the method name and SubMsg.
_344
SubMsg will be executed to callback on reply
_344
method with INSTANTIATE_REPLY_ID as identifier
_344
to complete further operations */
_344
Ok(Response::new()
_344
.add_attribute("method", "instantiate_token")
_344
.add_submessage(sub_msg))
_344
}
_344
_344
pub fn get_received_funds(deps: &Deps, info: &MessageInfo) -> Result<Coin, ContractError> {
_344
let config = CONFIG.load(deps.storage)?;
_344
_344
match info.funds.get(0) {
_344
None => return Err(ContractError::NotReceivedFunds {}),
_344
Some(received) => {
_344
/* Amount of tokens received cannot be zero */
_344
if received.amount.is_zero() {
_344
return Err(ContractError::NotAllowZeroAmount {});
_344
}
_344
/* Allow to receive only token denomination defined
_344
on contract instantiation "config.stable_denom" */
_344
if received.denom.ne(&config.stable_denom) {
_344
return Err(ContractError::NotAllowedDenom {
_344
denom: received.denom.to_string(),
_344
});
_344
}
_344
_344
/* Only one token can be received */
_344
if info.funds.len() > 1 {
_344
return Err(ContractError::NotAllowedMultipleDenoms {});
_344
}
_344
_344
Ok(received.clone())
_344
}
_344
}
_344
}
_344
_344
pub fn execute_mint(
_344
deps: Deps,
_344
info: MessageInfo,
_344
token_address: String,
_344
recipient: String,
_344
) -> Result<Response, ContractError> {
_344
let received_funds = get_received_funds(&deps, &info)?;
_344
_344
let token_addr_from_list = MINTED_TOKENS
_344
.load(deps.storage)
_344
.unwrap()
_344
.into_iter()
_344
.find(|t| t == &token_address);
_344
_344
/* Check if the token to be minted exists in the list, otherwise
_344
throw an error because minting must not be allowed for a token
_344
that was not created with this factory */
_344
if token_addr_from_list == None {
_344
return Err(ContractError::TokenAddressMustBeWhitelisted {});
_344
}
_344
_344
/* Create an execute message to mint new units of an existent token */
_344
let execute_mint = WasmMsg::Execute {
_344
contract_addr: token_address.clone(),
_344
_344
msg: to_binary(&cw20_base::msg::ExecuteMsg::Mint {
_344
amount: received_funds.amount,
_344
_344
recipient: recipient.clone(),
_344
})?,
_344
_344
funds: vec![],
_344
};
_344
_344
/* This type of SubMessage will never reply as no further operation is needed,
_344
but for sure the mint call to instantiated cw20_base contract needs to be done.
_344
More Info: https://docs.cosmwasm.com/docs/1.0/smart-contracts/message/submessage */
_344
let mint_sub_msg = SubMsg::new(execute_mint);
_344
_344
Ok(Response::new()
_344
.add_attribute("method", "mint")
_344
.add_submessage(mint_sub_msg))
_344
}
_344
_344
pub fn execute_burn_from(
_344
deps: DepsMut,
_344
info: MessageInfo,
_344
amount: Uint128,
_344
token_address: String,
_344
) -> Result<Response, ContractError> {
_344
let config = CONFIG.load(deps.storage)?;
_344
_344
let token_addr_from_list = MINTED_TOKENS
_344
.load(deps.storage)
_344
.unwrap()
_344
.into_iter()
_344
.find(|t| t == &token_address);
_344
_344
/* Check if the token to be burned exists in the list, otherwise
_344
throw an error because minting must not be allowed for a token
_344
that was not created thru the factory */
_344
if token_addr_from_list == None {
_344
return Err(ContractError::TokenAddressMustBeWhitelisted {});
_344
}
_344
_344
/* Amount of tokens to be burned must not be zero */
_344
if amount.is_zero() {
_344
return Err(ContractError::NotAllowZeroAmount {});
_344
}
_344
_344
/* Create a SubMessage to decrease the circulating supply of existent
_344
CW20 Tokens from the token_address.
_344
https://github.com/CosmWasm/cosmwasm/blob/main/SEMANTICS.md#submessages */
_344
let sub_msg_burn = SubMsg::new(WasmMsg::Execute {
_344
contract_addr: token_address.clone(),
_344
msg: to_binary(&cw20_base::msg::ExecuteMsg::BurnFrom {
_344
owner: info.sender.to_string(),
_344
amount,
_344
})?,
_344
funds: vec![],
_344
});
_344
_344
/* Create a SubMessage to transfer fund from this smart contract to
_344
the address that burns the CW20 Tokens*/
_344
let sub_msg_send = SubMsg::new(CosmosMsg::Bank(BankMsg::Send {
_344
to_address: info.sender.to_string(),
_344
_344
amount: coins(amount.u128(), config.stable_denom),
_344
}));
_344
_344
Ok(Response::new()
_344
.add_attribute("method", "burn")
_344
.add_submessages(vec![sub_msg_burn, sub_msg_send]))
_344
}
_344
_344
/* In order to handle any callback from previous SubMessages "reply"
_344
function must be implemented and iterate over "msg.id" to allow
_344
the completion of the callback.*/
_344
#[cfg_attr(not(feature = "library"), entry_point)]
_344
pub fn reply(deps: DepsMut, _env: Env, msg: Reply) -> StdResult<Response> {
_344
match msg.id {
_344
INSTANTIATE_REPLY_ID => handle_instantiate_reply(deps, msg),
_344
_344
id => Err(StdError::generic_err(format!("Unknown reply id: {}", id))),
_344
}
_344
}
_344
_344
fn handle_instantiate_reply(deps: DepsMut, msg: Reply) -> StdResult<Response> {
_344
let result = msg.result.into_result().map_err(StdError::generic_err)?;
_344
_344
/* Find the event type instantiate_contract which contains the contract_address*/
_344
let event = result
_344
.events
_344
.iter()
_344
.find(|event| event.ty == "instantiate_contract")
_344
.ok_or_else(|| StdError::generic_err("cannot find `instantiate_contract` event"))?;
_344
_344
/* Find the contract_address from instantiate_contract event*/
_344
let contract_address = &event
_344
.attributes
_344
.iter()
_344
.find(|attr| attr.key == "contract_address")
_344
.ok_or_else(|| StdError::generic_err("cannot find `contract_address` attribute"))?
_344
.value;
_344
_344
/* Update the state of the contract adding the new generated MINTED_TOKEN */
_344
MINTED_TOKENS.update(deps.storage, |mut tokens| -> StdResult<Vec<String>> {
_344
tokens.push(contract_address.to_string());
_344
_344
Ok(tokens)
_344
})?;
_344
_344
Ok(Response::new()
_344
.add_attribute("method", "handle_instantiate_reply")
_344
.add_attribute("contract_address", contract_address))
_344
}
_344
_344
#[cfg_attr(not(feature = "library"), entry_point)]
_344
pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult<Binary> {
_344
match msg {
_344
/* Return the list of all tokens that were minted thru this contract */
_344
QueryMsg::GetMintedTokens {} => to_binary(&query_minted_tokens(deps)?),
_344
}
_344
}
_344
_344
fn query_minted_tokens(deps: Deps) -> StdResult<MintedTokens> {
_344
Ok(MintedTokens {
_344
minted_tokens: MINTED_TOKENS.load(deps.storage)?,
_344
})
_344
}
_344
_344
/* In case you want to upgrade this contract you can find information about
_344
how to migrate the contract in the following link:
_344
https://docs.terra.money/docs/develop/dapp/quick-start/contract-migration.html*/
_344
#[cfg_attr(not(feature = "library"), entry_point)]
_344
pub fn migrate(_deps: DepsMut, _env: Env, _msg: MigrateMsg) -> StdResult<Response> {
_344
Ok(Response::default())
_344
}

  1. Open test.rs and add the following.
error.rs
msg.rs
state.rs
lib.rs
contract.rs
test.rs
Copy

_261
#[cfg(test)]
_261
mod tests {
_261
use crate::{
_261
contract::{execute, instantiate, query, reply},
_261
msg::{DepositType, ExecuteMsg, InstantiateMsg, MintedTokens, QueryMsg},
_261
};
_261
use cosmwasm_std::{
_261
coins, from_binary,
_261
testing::{mock_dependencies, mock_env, mock_info},
_261
to_binary, Attribute, BankMsg, Coin, CosmosMsg, DepsMut, Event, Reply, Response, SubMsg,
_261
SubMsgResponse, Uint128, WasmMsg,
_261
};
_261
use cw20::{Cw20Coin, MinterResponse};
_261
_261
#[test]
_261
fn test_instantiate() {
_261
// GIVEN
_261
let mut deps = mock_dependencies();
_261
_261
// WHEN
_261
let res = do_instantiate(deps.as_mut());
_261
_261
// THEN
_261
let attrs = res.attributes;
_261
assert_eq!(
_261
vec![
_261
Attribute {
_261
key: "method".to_string(),
_261
value: "instantiate".to_string()
_261
},
_261
Attribute {
_261
key: "token_contract_code_id".to_string(),
_261
value: "1".to_string()
_261
}
_261
],
_261
attrs
_261
);
_261
}
_261
_261
#[test]
_261
fn test_mint_token() {
_261
// GIVEN
_261
let mut deps = mock_dependencies();
_261
_261
// WHEN
_261
do_instantiate(deps.as_mut());
_261
let res = do_mint_new_token(deps.as_mut());
_261
_261
// THEN
_261
let res_attr = res.attributes;
_261
assert_eq!(1, res_attr.len());
_261
assert_eq!("instantiate_token", res_attr.get(0).unwrap().value);
_261
_261
let res_message = res.messages;
_261
assert_eq!(1, res_message.len());
_261
let success_reply = SubMsg::reply_on_success(
_261
CosmosMsg::Wasm(WasmMsg::Instantiate {
_261
admin: Some("cosmos2contract".to_string()),
_261
code_id: 1,
_261
funds: vec![],
_261
msg: to_binary(&cw20_base::msg::InstantiateMsg {
_261
name: "Bit Money".to_string(),
_261
symbol: "BTM".to_string(),
_261
decimals: 2,
_261
mint: Some(MinterResponse {
_261
minter: "cosmos2contract".to_string(),
_261
cap: Some(Uint128::new(1234)),
_261
}),
_261
initial_balances: vec![Cw20Coin {
_261
amount: Uint128::new(123),
_261
address: "creator".to_string(),
_261
}],
_261
marketing: None,
_261
})
_261
.unwrap(),
_261
label: "Bit Money".to_string(),
_261
}),
_261
1,
_261
);
_261
assert_eq!(&success_reply, res_message.get(0).unwrap());
_261
}
_261
_261
#[test]
_261
fn test_reply_instantiate_event() {
_261
// GIVEN
_261
let mut deps = mock_dependencies();
_261
let env = mock_env();
_261
let query_minted_tokens = QueryMsg::GetMintedTokens {};
_261
_261
// WHEN
_261
do_instantiate(deps.as_mut());
_261
do_mint_new_token(deps.as_mut());
_261
let do_instantiate_res = do_reply_instantiate_event(deps.as_mut());
_261
let query_res = query(deps.as_ref(), env, query_minted_tokens).unwrap();
_261
let query_res: MintedTokens = from_binary(&query_res).unwrap();
_261
_261
// THEN
_261
assert_eq!(
_261
Response::new()
_261
.add_attribute("method", "handle_instantiate_reply")
_261
.add_attribute("contract_address", "bit_money_contract_address"),
_261
do_instantiate_res
_261
);
_261
assert_eq!(
_261
MintedTokens {
_261
minted_tokens: vec!["bit_money_contract_address".to_string()]
_261
},
_261
query_res
_261
);
_261
}
_261
_261
#[test]
_261
fn test_mint_existent_token() {
_261
// GIVEN
_261
let mut deps = mock_dependencies();
_261
let env = mock_env();
_261
let info = mock_info(
_261
"creator",
_261
&vec![Coin {
_261
denom: "uluna".to_string(),
_261
amount: Uint128::new(1),
_261
}],
_261
);
_261
let msg = ExecuteMsg::Deposit(DepositType::Mint {
_261
token_address: "bit_money_contract_address".to_string(),
_261
recipient: "creator".to_string(),
_261
});
_261
_261
// WHEN
_261
do_instantiate(deps.as_mut());
_261
do_mint_new_token(deps.as_mut());
_261
do_reply_instantiate_event(deps.as_mut());
_261
let execute_res = execute(deps.as_mut(), env, info, msg).unwrap();
_261
_261
// THEN
_261
assert_eq!(
_261
Response::new()
_261
.add_attribute("method", "mint")
_261
.add_messages(vec![CosmosMsg::Wasm(WasmMsg::Execute {
_261
contract_addr: "bit_money_contract_address".to_string(),
_261
msg: to_binary(&cw20_base::msg::ExecuteMsg::Mint {
_261
amount: Uint128::new(1),
_261
recipient: "creator".to_string()
_261
})
_261
.unwrap(),
_261
funds: vec![],
_261
})]),
_261
execute_res
_261
);
_261
}
_261
_261
#[test]
_261
fn test_burn_tokens() {
_261
// GIVEN
_261
let mut deps = mock_dependencies();
_261
let env = mock_env();
_261
let info = mock_info("creator", &[]);
_261
let exec_burn_tokens = ExecuteMsg::Burn {
_261
amount: Uint128::new(123),
_261
token_address: "bit_money_contract_address".to_string(),
_261
};
_261
_261
// WHEN
_261
do_instantiate(deps.as_mut());
_261
do_reply_instantiate_event(deps.as_mut());
_261
do_mint_new_token(deps.as_mut());
_261
_261
let res = execute(deps.as_mut(), env, info, exec_burn_tokens).unwrap();
_261
_261
// THEN
_261
assert_eq!(1, res.attributes.len());
_261
assert_eq!("burn", res.attributes.get(0).unwrap().value);
_261
assert_eq!(2, res.messages.len());
_261
assert_eq!(
_261
vec![
_261
SubMsg::new(CosmosMsg::Wasm(WasmMsg::Execute {
_261
contract_addr: "bit_money_contract_address".to_string(),
_261
msg: to_binary(&cw20_base::msg::ExecuteMsg::BurnFrom {
_261
owner: "creator".to_string(),
_261
amount: Uint128::new(123),
_261
})
_261
.unwrap(),
_261
funds: vec![],
_261
})),
_261
SubMsg::new(CosmosMsg::Bank(BankMsg::Send {
_261
to_address: "creator".to_string(),
_261
amount: coins(123 as u128, "uluna")
_261
}))
_261
],
_261
res.messages
_261
);
_261
}
_261
_261
/*
_261
* HELPER METHODS TO DO NOT REPEAT CODE MANY TIMES
_261
*/
_261
_261
fn do_instantiate(deps: DepsMut) -> Response {
_261
let instantiate_msg = InstantiateMsg {
_261
stable_denom: "uluna".to_string(),
_261
token_contract_code_id: 1,
_261
};
_261
let info = mock_info("creator", &[]);
_261
let env = mock_env();
_261
_261
instantiate(deps, env, info, instantiate_msg).unwrap()
_261
}
_261
_261
fn do_mint_new_token(deps: DepsMut) -> Response {
_261
let env = mock_env();
_261
let info = mock_info(
_261
"i_am_the_sender",
_261
&vec![Coin {
_261
denom: "uluna".to_string(),
_261
amount: Uint128::new(123),
_261
}],
_261
);
_261
let token_msg = cw20_base::msg::InstantiateMsg {
_261
name: "Bit Money".to_string(),
_261
symbol: "BTM".to_string(),
_261
decimals: 2,
_261
mint: Some(MinterResponse {
_261
minter: "creator".to_string(),
_261
cap: Some(Uint128::new(1234)),
_261
}),
_261
initial_balances: vec![Cw20Coin {
_261
amount: Uint128::new(123),
_261
address: "creator".to_string(),
_261
}],
_261
marketing: None,
_261
};
_261
let msg = ExecuteMsg::Deposit(DepositType::Instantiate(token_msg.clone()));
_261
_261
execute(deps, env.clone(), info.clone(), msg).unwrap()
_261
}
_261
_261
/* Confirm reply event from instantiate method. That way
_261
the minted_tokens addresses can be whitelisted in factory.*/
_261
fn do_reply_instantiate_event(deps: DepsMut) -> Response {
_261
let env = mock_env();
_261
_261
let event = Event::new("instantiate_contract")
_261
.add_attribute("creator", "token_factory_addr")
_261
.add_attribute("admin", "i_am_the_sender")
_261
.add_attribute("code_id", "1")
_261
.add_attribute("contract_address", "bit_money_contract_address");
_261
_261
reply(
_261
deps,
_261
env,
_261
Reply {
_261
id: 1,
_261
result: cosmwasm_std::SubMsgResult::Ok(SubMsgResponse {
_261
events: vec![event],
_261
data: None,
_261
}),
_261
},
_261
)
_261
.unwrap()
_261
}
_261
}

  1. Save and Close test.rs.
  1. Navigate to token_factory/contracts/token_factory/examples.
  1. Open schema.rs and paste the following.
error.rs
msg.rs
state.rs
lib.rs
contract.rs
test.rs
schema.rs
Copy

_16
use std::env::current_dir;
_16
use std::fs::create_dir_all;
_16
use cosmwasm_schema::{export_schema, remove_schemas, schema_for};
_16
use token_factory::msg::{ExecuteMsg, QueryMsg};
_16
_16
fn main() {
_16
let mut out_dir = current_dir().unwrap();
_16
out_dir.push("schema");
_16
create_dir_all(&out_dir).unwrap();
_16
_16
remove_schemas(&out_dir).unwrap();
_16
_16
export_schema(&schema_for!(token_factory::msg::InstantiateMsg), &out_dir);
_16
export_schema(&schema_for!(ExecuteMsg), &out_dir);
_16
export_schema(&schema_for!(QueryMsg), &out_dir);
_16
}

  1. Save and close schema.rs.
  1. Navigate to /token_factory/contracts/token_factory/src.

  2. Open error.rs and add the following.

  1. Save and close error.rs.
  1. Open msg.rs and add the following.
  1. Save and close msg.rs.
  1. Open state.rs and add the following.
  1. Save and close state.rs.
  1. Open lib.rs and add the following.
  1. Save and close lib.rs.
  1. Open contract.rs and add the following.
  1. Open test.rs and add the following.
  1. Save and Close test.rs.
  1. Navigate to token_factory/contracts/token_factory/examples.
  1. Open schema.rs and paste the following.
  1. Save and close schema.rs.
error.rs
CopyExpandClose

_37
use cosmwasm_std::{StdError, Uint128};
_37
use thiserror::Error;
_37
_37
#[derive(Error, Debug, PartialEq)]
_37
pub enum ContractError {
_37
#[error("{0}")]
_37
Std(#[from] StdError),
_37
_37
#[error("Unauthorized")]
_37
Unauthorized {},
_37
_37
#[error("NotReceivedFunds")]
_37
NotReceivedFunds {},
_37
_37
#[error("NotAllowZeroAmount")]
_37
NotAllowZeroAmount {},
_37
_37
_37
#[error("NotAllowedDenom")]
_37
NotAllowedDenom {
_37
denom: String
_37
},
_37
_37
_37
#[error("NotAllowedMultipleDenoms")]
_37
NotAllowedMultipleDenoms {},
_37
_37
#[error("TokenAddressMustBeWhitelisted")]
_37
TokenAddressMustBeWhitelisted {},
_37
_37
#[error("ReceivedFundsMismatchWithMintAmount")]
_37
ReceivedFundsMismatchWithMintAmount {
_37
received_amount: Uint128,
_37
expected_amount: Uint128
_37
},
_37
_37
}

3. Generate and test the schema

Now that you have modified the token_factory contract, generate the schema and run the tests to validate that the code works as expected:

  1. Navigate to /token_factory/contracts/token_factory.
Terminal
Copy

_1
cd /token_factory/contracts/token_factory

  1. Generate the schema.
Terminal
Copy

_2
cd /token_factory/contracts/token_factory
_2
cargo schema

  1. You should see output similar to the following.
Terminal
Output
Copy

_8
Finished dev [unoptimized + debuginfo] target(s) in 0.02s
_8
Running `target/debug/examples/schema`
_8
Removing "~/Documents/github/token_factory/contracts/token_factory/schema/execute_msg.json" …
_8
Removing "~/Documents/github/token_factory/contracts/token_factory/schema/instantiate_msg.json" …
_8
Removing "~/Documents/github/token_factory/contracts/token_factory/schema/query_msg.json" …
_8
Created ~/Documents/github/token_factory/contracts/token_factory/schema/instantiate_msg.json
_8
Created ~/Documents/github/token_factory/contracts/token_factory/schema/execute_msg.json
_8
Created ~/Documents/github/token_factory/contracts/token_factory/schema/query_msg.json

  1. Run the tests.
Terminal
Output
Copy

_3
cd /token_factory/contracts/token_factory
_3
cargo schema
_3
cargo test

  1. You will see output similar to the following.
Terminal
Output
Copy

_10
Finished test [unoptimized + debuginfo] target(s) in 0.02s
_10
Running unittests (target/debug/deps/token_factory-03f77bf897cd72b7)
_10
_10
running 5 tests
_10
test test::tests::test_instantiate ... ok
_10
test test::tests::test_burn_tokens ... ok
_10
test test::tests::test_mint_token ... ok
_10
test test::tests::test_mint_existent_token ... ok
_10
test test::tests::test_reply_instantiate_event ... ok
_10
test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

  1. Navigate to /token_factory/contracts/token_factory.
  1. Generate the schema.
  1. You should see output similar to the following.
  1. Run the tests.
  1. You will see output similar to the following.
Terminal
CopyExpandClose

_1
cd /token_factory/contracts/token_factory

4. Modify config.terrain.json

  1. Open config.terrain.json.

  2. Modify the property InstantiateMsg, using your <token_contract_code_id>. The <token_contract_code_id> should not be surrounded by quotes.

💡tip

To determine which <token_contract_code_id>, check the file refs.terrain.json from the workspace's root under the cw20-token_factory object.


_12
{
_12
"_global": {
_12
"_base": {
_12
"instantiation": {
_12
"instantiateMsg": {
_12
"stable_denom": "uluna",
_12
"token_contract_code_id": <token_contract_id>
_12
}
_12
}
_12
}
_12
}, // ...
_12
}

Deploy the smart contract to LocalTerra

Now that you've created, modified and tested each smart contract, deploy the token_factory to your LocalTerra instance using Terrain:


_1
terrain deploy token_factory --signer test

💡tip

If your code is not working as expected, you can clone the repo with all changes done until now.

A hosted website for the token factory can be found here.