abstract_os/
abstract_token.rs

1//! # Abstract Token
2//!
3//! `abstract_os::abstract_token` is a custom token that's only transferable between abstract-os instances.
4//!
5//! ## Description
6//! An app is a contract that is allowed to perform actions on a [proxy](crate::proxy) contract while also being migratable.
7//!
8use cosmwasm_schema::QueryResponses;
9use cosmwasm_std::{Binary, StdError, StdResult, Uint128};
10#[allow(unused)]
11use cw20::{
12    AllAccountsResponse, AllAllowancesResponse, AllowanceResponse, BalanceResponse,
13    DownloadLogoResponse, MarketingInfoResponse, TokenInfoResponse,
14};
15pub use cw20::{Cw20Coin, Cw20ExecuteMsg, Expiration, Logo, MinterResponse};
16pub use cw20_base::msg::QueryMsg as Cw20QueryMsg;
17use std::convert::TryInto;
18
19/// ## Description
20/// This structure describes the basic settings for creating a token contract.
21/// TokenContract InstantiateMsg
22#[cosmwasm_schema::cw_serde]
23pub struct InstantiateMsg {
24    /// the name
25    pub name: String,
26    /// the symbol
27    pub symbol: String,
28    /// the precision after the decimal point
29    pub decimals: u8,
30    /// the initial balance of token
31    pub initial_balances: Vec<Cw20Coin>,
32    /// the controls configs of type [`MinterResponse`]
33    pub mint: Option<MinterResponse>,
34    /// address of version control contract.
35    pub version_control_address: String,
36}
37
38/// ## Description
39/// This structure describes a migration message.
40/// We currently take no arguments for migrations.
41#[cosmwasm_schema::cw_serde]
42pub struct MigrateMsg {}
43
44impl InstantiateMsg {
45    pub fn get_cap(&self) -> Option<Uint128> {
46        self.mint.as_ref().and_then(|v| v.cap)
47    }
48
49    pub fn validate(&self) -> StdResult<()> {
50        // Check name, symbol, decimals
51        if !is_valid_name(&self.name) {
52            return Err(StdError::generic_err(
53                "Name is not in the expected format (3-50 UTF-8 bytes)",
54            ));
55        }
56        if !is_valid_symbol(&self.symbol) {
57            return Err(StdError::generic_err(
58                "Ticker symbol is not in expected format [a-zA-Z\\-]{3,12}",
59            ));
60        }
61        if self.decimals > 18 {
62            return Err(StdError::generic_err("Decimals must not exceed 18"));
63        }
64        Ok(())
65    }
66}
67
68/// ## Description
69/// Checks the validity of the token name
70/// ## Params
71/// * **name** is the object of type [`str`]. the name to check
72fn is_valid_name(name: &str) -> bool {
73    let bytes = name.as_bytes();
74    if bytes.len() < 3 || bytes.len() > 50 {
75        return false;
76    }
77    true
78}
79
80/// ## Description
81/// Checks the validity of the token symbol
82/// ## Params
83/// * **symbol** is the object of type [`str`]. the symbol to check
84fn is_valid_symbol(symbol: &str) -> bool {
85    let bytes = symbol.as_bytes();
86    if bytes.len() < 3 || bytes.len() > 12 {
87        return false;
88    }
89    for byte in bytes.iter() {
90        if (*byte != 45) && (*byte < 65 || *byte > 90) && (*byte < 97 || *byte > 122) {
91            return false;
92        }
93    }
94    true
95}
96
97#[cosmwasm_schema::cw_serde]
98#[cfg_attr(feature = "boot", derive(boot_core::ExecuteFns))]
99pub enum ExecuteMsg {
100    UpdateWhitelist {
101        to_add: Vec<String>,
102        to_remove: Vec<String>,
103        restrict_transfers: Option<bool>,
104    },
105    UpdateAdmin {
106        new_admin: String,
107    },
108    /// Transfer is a base message to move tokens to another account without triggering actions
109    Transfer {
110        recipient: String,
111        amount: Uint128,
112    },
113    /// Burn is a base message to destroy tokens forever
114    Burn {
115        amount: Uint128,
116    },
117    /// Send is a base message to transfer tokens to a contract and trigger an action
118    /// on the receiving contract.
119    Send {
120        contract: String,
121        amount: Uint128,
122        msg: Binary,
123    },
124    /// Only with "approval" api. Allows spender to access an additional amount tokens
125    /// from the owner's (env.sender) account. If expires is Some(), overwrites current allowance
126    /// expiration with this one.
127    IncreaseAllowance {
128        spender: String,
129        amount: Uint128,
130        expires: Option<Expiration>,
131    },
132    /// Only with "approval" api. Lowers the spender's access of tokens
133    /// from the owner's (env.sender) account by amount. If expires is Some(), overwrites current
134    /// allowance expiration with this one.
135    DecreaseAllowance {
136        spender: String,
137        amount: Uint128,
138        expires: Option<Expiration>,
139    },
140    /// Only with "approval" api. Transfers amount tokens from owner -> recipient
141    /// if `env.sender` has sufficient pre-approval.
142    TransferFrom {
143        owner: String,
144        recipient: String,
145        amount: Uint128,
146    },
147    /// Only with "approval" api. Sends amount tokens from owner -> contract
148    /// if `env.sender` has sufficient pre-approval.
149    SendFrom {
150        owner: String,
151        contract: String,
152        amount: Uint128,
153        msg: Binary,
154    },
155    /// Only with "approval" api. Destroys tokens forever
156    BurnFrom {
157        owner: String,
158        amount: Uint128,
159    },
160    /// Only with the "mintable" api. If authorized, creates amount new tokens
161    /// and adds to the recipient balance.
162    Mint {
163        recipient: String,
164        amount: Uint128,
165    },
166    /// Only with the "marketing" api. If authorized, updates marketing metadata.
167    /// Setting None/null for any of these will leave it unchanged.
168    /// Setting Some("") will clear this field on the contract storage
169    UpdateMarketing {
170        // A URL pointing to the project behind this token.
171        project: Option<String>,
172        // A longer description of the token and it's utility. Designed for tooltips or such
173        description: Option<String>,
174        // The address (if any) who can update this data structure
175        marketing: Option<String>,
176    },
177    /// If set as the "marketing" role on the contract, upload a new URL, SVG, or PNG for the token
178    UploadLogo(Logo),
179}
180
181impl TryInto<Cw20ExecuteMsg> for ExecuteMsg {
182    type Error = StdError;
183
184    fn try_into(self) -> Result<Cw20ExecuteMsg, Self::Error> {
185        match self {
186            ExecuteMsg::UpdateWhitelist {
187                to_add: _,
188                to_remove: _,
189                restrict_transfers: _,
190            } => Err(StdError::generic_err("can't parse into cw20 msg")),
191            ExecuteMsg::UpdateAdmin { new_admin: _ } => {
192                Err(StdError::generic_err("can't parse into cw20 msg"))
193            }
194            ExecuteMsg::Transfer { recipient, amount } => {
195                Ok(Cw20ExecuteMsg::Transfer { recipient, amount })
196            }
197            ExecuteMsg::Burn { amount } => Ok(Cw20ExecuteMsg::Burn { amount }),
198            ExecuteMsg::Send {
199                contract,
200                amount,
201                msg,
202            } => Ok(Cw20ExecuteMsg::Send {
203                contract,
204                amount,
205                msg,
206            }),
207            ExecuteMsg::IncreaseAllowance {
208                spender,
209                amount,
210                expires,
211            } => Ok(Cw20ExecuteMsg::IncreaseAllowance {
212                spender,
213                amount,
214                expires,
215            }),
216            ExecuteMsg::DecreaseAllowance {
217                spender,
218                amount,
219                expires,
220            } => Ok(Cw20ExecuteMsg::DecreaseAllowance {
221                spender,
222                amount,
223                expires,
224            }),
225            ExecuteMsg::TransferFrom {
226                owner,
227                recipient,
228                amount,
229            } => Ok(Cw20ExecuteMsg::TransferFrom {
230                owner,
231                recipient,
232                amount,
233            }),
234            ExecuteMsg::SendFrom {
235                owner,
236                contract,
237                amount,
238                msg,
239            } => Ok(Cw20ExecuteMsg::SendFrom {
240                owner,
241                contract,
242                amount,
243                msg,
244            }),
245            ExecuteMsg::BurnFrom { owner, amount } => {
246                Ok(Cw20ExecuteMsg::BurnFrom { owner, amount })
247            }
248            ExecuteMsg::Mint { recipient, amount } => {
249                Ok(Cw20ExecuteMsg::Mint { recipient, amount })
250            }
251            ExecuteMsg::UpdateMarketing {
252                project,
253                description,
254                marketing,
255            } => Ok(Cw20ExecuteMsg::UpdateMarketing {
256                project,
257                description,
258                marketing,
259            }),
260            ExecuteMsg::UploadLogo(l) => Ok(Cw20ExecuteMsg::UploadLogo(l)),
261        }
262    }
263}
264#[cosmwasm_schema::cw_serde]
265#[derive(QueryResponses)]
266#[cfg_attr(feature = "boot", derive(boot_core::QueryFns))]
267pub enum QueryMsg {
268    #[returns(ConfigResponse)]
269    Config {},
270    /// Returns the current balance of the given address, 0 if unset.
271    /// Return type: BalanceResponse.
272    #[returns(BalanceResponse)]
273    Balance { address: String },
274    /// Returns metadata on the contract - name, decimals, supply, etc.
275    /// Return type: TokenInfoResponse.
276    #[returns(TokenInfoResponse)]
277    TokenInfo {},
278    /// Only with "mintable" api.
279    /// Returns who can mint and the hard cap on maximum tokens after minting.
280    /// Return type: MinterResponse.
281    #[returns(MinterResponse)]
282    Minter {},
283    /// Only with "allowance" api.
284    /// Returns how much spender can use from owner account, 0 if unset.
285    /// Return type: AllowanceResponse.
286    #[returns(AllowanceResponse)]
287    Allowance { owner: String, spender: String },
288    /// Only with "enumerable" api (and "allowances")
289    /// Returns all allowances this owner has approved. Supports pagination.
290    /// Return type: AllAllowancesResponse.
291    #[returns(AllAllowancesResponse)]
292    AllAllowances {
293        owner: String,
294        start_after: Option<String>,
295        limit: Option<u32>,
296    },
297    /// Only with "enumerable" api
298    /// Returns all accounts that have balances. Supports pagination.
299    /// Return type: AllAccountsResponse.
300    #[returns(AllAccountsResponse)]
301    AllAccounts {
302        start_after: Option<String>,
303        limit: Option<u32>,
304    },
305    /// Only with "marketing" api
306    /// Returns more metadata on the contract to display in the client:
307    /// - description, logo, project url, etc.
308    /// Return type: MarketingInfoResponse
309    #[returns(MarketingInfoResponse)]
310    MarketingInfo {},
311    /// Only with "marketing" api
312    /// Downloads the embedded logo data (if stored on chain). Errors if no logo data is stored for this
313    /// contract.
314    /// Return type: DownloadLogoResponse.
315    #[returns(DownloadLogoResponse)]
316    DownloadLogo {},
317}
318
319impl TryInto<Cw20QueryMsg> for QueryMsg {
320    type Error = StdError;
321
322    fn try_into(self) -> Result<Cw20QueryMsg, Self::Error> {
323        match self {
324            Self::Balance { address } => Ok(Cw20QueryMsg::Balance { address }),
325            Self::TokenInfo {} => Ok(Cw20QueryMsg::TokenInfo {}),
326            Self::Minter {} => Ok(Cw20QueryMsg::Minter {}),
327            Self::Allowance { owner, spender } => Ok(Cw20QueryMsg::Allowance { owner, spender }),
328            Self::AllAllowances {
329                owner,
330                start_after,
331                limit,
332            } => Ok(Cw20QueryMsg::AllAllowances {
333                owner,
334                start_after,
335                limit,
336            }),
337            Self::AllAccounts { start_after, limit } => {
338                Ok(Cw20QueryMsg::AllAccounts { start_after, limit })
339            }
340            Self::MarketingInfo {} => Ok(Cw20QueryMsg::MarketingInfo {}),
341            Self::DownloadLogo {} => Ok(Cw20QueryMsg::DownloadLogo {}),
342            QueryMsg::Config {} => Err(StdError::generic_err("could not convert into cw20 query")),
343        }
344    }
345}
346#[cosmwasm_schema::cw_serde]
347pub struct ConfigResponse {
348    pub transfers_restricted: bool,
349    pub version_control_address: String,
350    pub whitelisted_addr: Vec<String>,
351    pub admin: String,
352}