Skip to main content

juno_tokenfactory_core/
contract.rs

1#[cfg(not(feature = "library"))]
2use cosmwasm_std::entry_point;
3use cosmwasm_std::{
4    to_binary, AllBalanceResponse, BalanceResponse, BankMsg, BankQuery, Binary, Coin, Deps,
5    DepsMut, Env, MessageInfo, Response, StdResult,
6};
7use cw2::set_contract_version;
8
9use crate::error::ContractError;
10use crate::helpers::{
11    create_denom_msg, is_contract_manager, is_whitelisted, mint_factory_token_messages,
12    mint_tokens_msg, pretty_denoms_output,
13};
14use crate::msg::{ExecuteMsg, InstantiateMsg, QueryMsg};
15use crate::state::{Config, CONFIG};
16
17use token_bindings::{TokenFactoryMsg, TokenMsg};
18
19// version info for migration info
20const CONTRACT_NAME: &str = "crates.io:tokenfactory-core";
21const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION");
22
23#[cfg_attr(not(feature = "library"), entry_point)]
24pub fn instantiate(
25    deps: DepsMut,
26    env: Env,
27    _info: MessageInfo,
28    msg: InstantiateMsg,
29) -> Result<Response<TokenFactoryMsg>, ContractError> {
30    set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?;
31
32    // Validate existing denoms.
33    let mut denoms = msg.existing_denoms.unwrap_or_default();
34    for d in denoms.iter() {
35        if !d.starts_with("factory/") {
36            return Err(ContractError::InvalidDenom {
37                denom: d.clone(),
38                message: "Denom must start with 'factory/'".to_string(),
39            });
40        }
41    }
42
43    // Create new denoms.
44    let mut new_denom_msgs: Vec<TokenMsg> = vec![];
45    let mut new_mint_msgs: Vec<TokenMsg> = vec![];
46
47    if let Some(new_denoms) = msg.new_denoms {
48        if !new_denoms.is_empty() {
49            for denom in new_denoms {
50                let subdenom = denom.symbol.to_lowercase();
51                let full_denom = format!("factory/{}/{}", env.contract.address, subdenom);
52
53                // Add creation message.
54                new_denom_msgs.push(create_denom_msg(
55                    subdenom.clone(),
56                    full_denom.clone(),
57                    denom.clone(),
58                ));
59
60                // Add initial balance mint messages.
61                if let Some(initial_balances) = denom.initial_balances {
62                    if !initial_balances.is_empty() {
63                        // Validate addresses.
64                        for initial in initial_balances.iter() {
65                            deps.api.addr_validate(&initial.address)?;
66                        }
67
68                        for b in initial_balances {
69                            new_mint_msgs.push(mint_tokens_msg(
70                                b.address.clone(),
71                                full_denom.clone(),
72                                b.amount,
73                            ));
74                        }
75                    }
76                }
77
78                // Add to existing denoms.
79                denoms.push(full_denom);
80            }
81        }
82    }
83
84    if denoms.is_empty() {
85        return Err(ContractError::NoDenomsProvided {});
86    }
87
88    let manager = deps
89        .api
90        .addr_validate(&msg.manager.unwrap_or_else(|| _info.sender.to_string()))?;
91
92    let config = Config {
93        manager: manager.to_string(),
94        allowed_mint_addresses: msg.allowed_mint_addresses,
95        denoms,
96    };
97    CONFIG.save(deps.storage, &config)?;
98
99    Ok(Response::new()
100        .add_attribute("method", "instantiate")
101        .add_messages(new_denom_msgs)
102        .add_messages(new_mint_msgs))
103}
104
105#[cfg_attr(not(feature = "library"), entry_point)]
106pub fn execute(
107    deps: DepsMut,
108    env: Env,
109    info: MessageInfo,
110    msg: ExecuteMsg,
111) -> Result<Response<TokenFactoryMsg>, ContractError> {
112    match msg {
113        // == ANYONE ==
114        ExecuteMsg::Burn {} => execute_burn(deps, env, info),
115
116        // == WHITELIST ==
117        ExecuteMsg::Mint { address, denom } => execute_mint(deps, info, address, denom),
118
119        // == MANAGER ==
120        ExecuteMsg::BurnFrom { from, denom } => {
121            let config = CONFIG.load(deps.storage)?;
122            is_contract_manager(config, info.sender)?;
123
124            let balance = deps.querier.query_all_balances(from.clone())?;
125
126            let mut found = false;
127            for coin in balance.iter() {
128                if coin.denom == denom.denom {
129                    found = true;
130                }
131            }
132
133            if !found {
134                return Err(ContractError::InvalidDenom {
135                    denom: denom.denom,
136                    message: "Denom not found in balance".to_string(),
137                });
138            }
139
140            // burn from from_address
141            let msg: TokenMsg = TokenMsg::BurnTokens {
142                denom: denom.denom.clone(),
143                amount: denom.amount,
144                burn_from_address: from,
145            };
146
147            Ok(Response::new()
148                .add_attribute("method", "execute_burn_from")
149                .add_attribute("denom", denom.denom)
150                .add_message(msg))
151        }
152
153        ExecuteMsg::TransferAdmin { denom, new_address } => {
154            execute_transfer_admin(deps, info, denom, new_address)
155        }
156
157        ExecuteMsg::ForceTransfer { from, to, denom } => {
158            let config = CONFIG.load(deps.storage)?;
159            is_contract_manager(config, info.sender)?;
160
161            let msg: TokenMsg = TokenMsg::ForceTransfer {
162                denom: denom.denom.clone(),
163                amount: denom.amount,
164                from_address: from,
165                to_address: to,
166            };
167
168            Ok(Response::new()
169                .add_attribute("method", "execute_force_transfer")
170                .add_attribute("denom", denom.denom)
171                .add_message(msg))
172        }
173
174        ExecuteMsg::SetMetadata { denom, metadata } => {
175            let config = CONFIG.load(deps.storage)?;
176            is_contract_manager(config, info.sender)?;
177
178            let msg: TokenMsg = TokenMsg::SetMetadata {
179                denom: denom.clone(),
180                metadata,
181            };
182
183            Ok(Response::new()
184                .add_attribute("method", "execute_set_metadata")
185                .add_attribute("denom", denom)
186                .add_message(msg))
187        }
188
189        // Merge these into a modify whitelist
190        ExecuteMsg::AddWhitelist { addresses } => {
191            let config = CONFIG.load(deps.storage)?;
192            is_contract_manager(config.clone(), info.sender)?;
193
194            // add addresses if it is not in config.allowed_mint_addresses
195            let mut updated = config.allowed_mint_addresses;
196            for new in addresses {
197                if !updated.contains(&new) {
198                    updated.push(new);
199                }
200            }
201
202            CONFIG.update(deps.storage, |mut config| -> StdResult<_> {
203                config.allowed_mint_addresses = updated;
204                Ok(config)
205            })?;
206
207            Ok(Response::new().add_attribute("method", "add_whitelist"))
208        }
209        ExecuteMsg::RemoveWhitelist { addresses } => {
210            let config = CONFIG.load(deps.storage)?;
211            is_contract_manager(config.clone(), info.sender)?;
212
213            let mut updated = config.allowed_mint_addresses;
214            for remove in addresses {
215                updated.retain(|a| a != &remove);
216            }
217
218            CONFIG.update(deps.storage, |mut config| -> StdResult<_> {
219                config.allowed_mint_addresses = updated;
220                Ok(config)
221            })?;
222            Ok(Response::new().add_attribute("method", "remove_whitelist"))
223        }
224
225        ExecuteMsg::AddDenom { denoms } => {
226            let config = CONFIG.load(deps.storage)?;
227            is_contract_manager(config.clone(), info.sender)?;
228
229            let mut updated_denoms = config.denoms;
230            for new in denoms {
231                if !updated_denoms.contains(&new) {
232                    updated_denoms.push(new);
233                }
234            }
235
236            CONFIG.update(deps.storage, |mut config| -> StdResult<_> {
237                config.denoms = updated_denoms;
238                Ok(config)
239            })?;
240
241            Ok(Response::new().add_attribute("method", "add_denom"))
242        }
243        ExecuteMsg::RemoveDenom { denoms } => {
244            let config = CONFIG.load(deps.storage)?;
245            is_contract_manager(config.clone(), info.sender)?;
246
247            let mut updated_denoms = config.denoms;
248            for remove in denoms {
249                updated_denoms.retain(|a| a != &remove);
250            }
251
252            CONFIG.update(deps.storage, |mut config| -> StdResult<_> {
253                config.denoms = updated_denoms;
254                Ok(config)
255            })?;
256            Ok(Response::new().add_attribute("method", "remove_denom"))
257        }
258    }
259}
260
261pub fn execute_transfer_admin(
262    deps: DepsMut,
263    info: MessageInfo,
264    denom: String,
265    new_addr: String,
266) -> Result<Response<TokenFactoryMsg>, ContractError> {
267    let config = CONFIG.load(deps.storage)?;
268    is_contract_manager(config.clone(), info.sender)?;
269
270    // it is possible to transfer admin in without adding to contract config. So devs need a way to reclaim admin without adding it to denoms config
271    let config_denom: Option<&String> = config.denoms.iter().find(|d| d.to_string() == denom);
272
273    if let Some(config_denom) = config_denom {
274        // remove it from config
275        let updated_config: Vec<String> = config
276            .denoms
277            .iter()
278            .filter(|d| d.to_string() != *config_denom)
279            .map(|d| d.to_string())
280            .collect();
281
282        CONFIG.update(deps.storage, |mut config| -> StdResult<_> {
283            config.denoms = updated_config;
284            Ok(config)
285        })?;
286    }
287
288    let msg = TokenMsg::ChangeAdmin {
289        denom: denom.to_string(),
290        new_admin_address: new_addr.to_string(),
291    };
292
293    Ok(Response::new()
294        .add_attribute("method", "execute_transfer_admin")
295        .add_attribute("new_admin", new_addr)
296        .add_message(msg))
297}
298
299pub fn execute_mint(
300    deps: DepsMut,
301    info: MessageInfo,
302    address: String,
303    denoms: Vec<Coin>,
304) -> Result<Response<TokenFactoryMsg>, ContractError> {
305    let config = CONFIG.load(deps.storage)?;
306
307    is_whitelisted(config, info.sender)?;
308
309    let mint_msgs: Vec<TokenMsg> = mint_factory_token_messages(&address, &denoms)?;
310
311    Ok(Response::new()
312        .add_attribute("method", "execute_mint")
313        .add_attribute("to_address", address)
314        .add_attribute("denoms", pretty_denoms_output(&denoms))
315        .add_messages(mint_msgs))
316}
317
318pub fn execute_burn(
319    deps: DepsMut,
320    env: Env,
321    info: MessageInfo,
322) -> Result<Response<TokenFactoryMsg>, ContractError> {
323    // Anyone can burn funds since they have to send them in.
324    if info.funds.is_empty() {
325        return Err(ContractError::InvalidFunds {});
326    }
327
328    let config = CONFIG.load(deps.storage)?;
329
330    let (factory_denoms, send_back): (Vec<Coin>, Vec<Coin>) = info
331        .funds
332        .iter()
333        .cloned()
334        .partition(|coin| config.denoms.iter().any(|d| *d == coin.denom));
335
336    let burn_msgs: Vec<TokenMsg> = factory_denoms
337        .iter()
338        .map(|coin| TokenMsg::BurnTokens {
339            denom: coin.denom.clone(),
340            amount: coin.amount,
341            burn_from_address: env.contract.address.to_string(),
342        })
343        .collect();
344
345    let bank_return_msg = BankMsg::Send {
346        to_address: info.sender.to_string(),
347        amount: send_back,
348    };
349
350    Ok(Response::new()
351        .add_attribute("method", "execute_burn")
352        .add_message(bank_return_msg)
353        .add_messages(burn_msgs))
354}
355
356#[cfg_attr(not(feature = "library"), entry_point)]
357pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult<Binary> {
358    match msg {
359        QueryMsg::GetConfig {} => {
360            let config = CONFIG.load(deps.storage)?;
361            to_binary(&config)
362        }
363        QueryMsg::GetBalance { address, denom } => {
364            let v = BankQuery::Balance { address, denom };
365            let res: BalanceResponse = deps.querier.query(&v.into())?;
366            to_binary(&res.amount)
367        }
368
369        // Since RPC's do not like to return factory/ denoms. We allow that through this query
370        QueryMsg::GetAllBalances { address } => {
371            let v = BankQuery::AllBalances { address };
372
373            let res: AllBalanceResponse = deps.querier.query(&v.into())?;
374
375            to_binary(&res.amount)
376        }
377    }
378}