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
19const 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 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 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 new_denom_msgs.push(create_denom_msg(
55 subdenom.clone(),
56 full_denom.clone(),
57 denom.clone(),
58 ));
59
60 if let Some(initial_balances) = denom.initial_balances {
62 if !initial_balances.is_empty() {
63 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 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 ExecuteMsg::Burn {} => execute_burn(deps, env, info),
115
116 ExecuteMsg::Mint { address, denom } => execute_mint(deps, info, address, denom),
118
119 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 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 ExecuteMsg::AddWhitelist { addresses } => {
191 let config = CONFIG.load(deps.storage)?;
192 is_contract_manager(config.clone(), info.sender)?;
193
194 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 let config_denom: Option<&String> = config.denoms.iter().find(|d| d.to_string() == denom);
272
273 if let Some(config_denom) = config_denom {
274 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 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 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}