levana_perpswap_cosmos/contracts/cw20/
entry.rs

1//! Entrypoint messages for the CW20 contract.
2use super::Cw20Coin;
3use crate::prelude::*;
4use cosmwasm_schema::{cw_serde, QueryResponses};
5use cosmwasm_std::{Addr, Binary, Uint128};
6use cw_utils::Expiration;
7
8#[cw_serde]
9pub struct InstantiateMsg {
10    /************** Cw20 spec *******************/
11    pub name: String,
12    pub symbol: String,
13    pub decimals: u8,
14    pub initial_balances: Vec<Cw20Coin>,
15    /// We make this mandatory since we always need an owner for these CW20s.
16    pub minter: InstantiateMinter,
17    pub marketing: Option<InstantiateMarketingInfo>,
18}
19
20impl InstantiateMsg {
21    pub fn get_cap(&self) -> Option<Uint128> {
22        self.minter.cap
23    }
24
25    pub fn validate(&self) -> anyhow::Result<()> {
26        // Check name, symbol, decimals
27        if !self.has_valid_name() {
28            bail!(PerpError::new(
29                ErrorId::MsgValidation,
30                ErrorDomain::Cw20,
31                "Name is not in the expected format (3-50 UTF-8 bytes)"
32            ))
33        }
34        if !self.has_valid_symbol() {
35            bail!(PerpError::new(
36                ErrorId::MsgValidation,
37                ErrorDomain::Cw20,
38                "Ticker symbol is not in expected format [a-zA-Z\\-]{{3,12}}"
39            ))
40        }
41        if self.decimals > 18 {
42            bail!(PerpError::new(
43                ErrorId::MsgValidation,
44                ErrorDomain::Cw20,
45                "Decimals must not exceed 18"
46            ))
47        }
48        if !self.has_valid_balances() {
49            bail!(PerpError::new(
50                ErrorId::MsgValidation,
51                ErrorDomain::Cw20,
52                "duplicate account balances"
53            ))
54        }
55        Ok(())
56    }
57
58    fn has_valid_name(&self) -> bool {
59        let bytes = self.name.as_bytes();
60        if bytes.len() < 3 || bytes.len() > 50 {
61            return false;
62        }
63        true
64    }
65
66    fn has_valid_symbol(&self) -> bool {
67        let bytes = self.symbol.as_bytes();
68        if bytes.len() < 3 || bytes.len() > 12 {
69            return false;
70        }
71        for byte in bytes.iter() {
72            if (*byte != 45) && (*byte < 65 || *byte > 90) && (*byte < 97 || *byte > 122) {
73                return false;
74            }
75        }
76        true
77    }
78
79    fn has_valid_balances(&self) -> bool {
80        let mut addresses = self
81            .initial_balances
82            .iter()
83            .map(|c| &c.address)
84            .collect::<Vec<_>>();
85        addresses.sort();
86        addresses.dedup();
87
88        // check for duplicates
89        addresses.len() == self.initial_balances.len()
90    }
91}
92
93#[cw_serde]
94pub enum ExecuteMsg {
95    /************** Cw20 spec *******************/
96    /// Transfer is a base message to move tokens to another account without triggering actions
97    Transfer { recipient: RawAddr, amount: Uint128 },
98    /// Burn is a base message to destroy tokens forever
99    Burn { amount: Uint128 },
100    /// Send is a base message to transfer tokens to a contract and trigger an action
101    /// on the receiving contract.
102    Send {
103        contract: RawAddr,
104        amount: Uint128,
105        msg: Binary,
106    },
107    /// Allows spender to access an additional amount tokens
108    /// from the owner's (env.sender) account. If expires is Some(), overwrites current allowance
109    /// expiration with this one.
110    IncreaseAllowance {
111        spender: RawAddr,
112        amount: Uint128,
113        expires: Option<Expiration>,
114    },
115    /// Lowers the spender's access of tokens
116    /// from the owner's (env.sender) account by amount. If expires is Some(), overwrites current
117    /// allowance expiration with this one.
118    DecreaseAllowance {
119        spender: RawAddr,
120        amount: Uint128,
121        expires: Option<Expiration>,
122    },
123    /// Transfers amount tokens from owner -> recipient
124    /// if `env.sender` has sufficient pre-approval.
125    TransferFrom {
126        owner: RawAddr,
127        recipient: RawAddr,
128        amount: Uint128,
129    },
130    /// Sends amount tokens from owner -> contract
131    /// if `env.sender` has sufficient pre-approval.
132    SendFrom {
133        owner: RawAddr,
134        contract: RawAddr,
135        amount: Uint128,
136        msg: Binary,
137    },
138    /// Destroys tokens forever
139    BurnFrom { owner: RawAddr, amount: Uint128 },
140    /// If authorized, creates amount new tokens
141    /// and adds to the recipient balance.
142    Mint { recipient: RawAddr, amount: Uint128 },
143    /// This variant is according to spec. The current minter may set
144    /// a new minter. Setting the minter to None will remove the
145    /// token's minter forever.
146    /// there is deliberately *not* a way to set the proprietary MinterKind
147    /// so the only way to set the minter to MinterKind::MarketId is at
148    /// instantiation
149    ///
150    /// Note: we require that there always be a minter, so this is not optional!
151    UpdateMinter { new_minter: RawAddr },
152    /// If authorized, updates marketing metadata.
153    /// Setting None/null for any of these will leave it unchanged.
154    /// Setting Some("") will clear this field on the contract storage
155    UpdateMarketing {
156        /// A URL pointing to the project behind this token.
157        project: Option<String>,
158        /// A longer description of the token and it's utility. Designed for tooltips or such
159        description: Option<String>,
160        /// The address (if any) who can update this data structure
161        marketing: Option<String>,
162    },
163    /// If set as the "marketing" role on the contract, upload a new URL, SVG, or PNG for the token
164    UploadLogo(Logo),
165    /************** Proprietary *******************/
166    /// Set factory addr
167    SetMarket { addr: RawAddr },
168}
169
170#[cw_serde]
171#[derive(QueryResponses)]
172pub enum QueryMsg {
173    /************** Cw20 spec *******************/
174    /// * returns [BalanceResponse]
175    ///
176    /// The current balance of the given address, 0 if unset.
177    #[returns(BalanceResponse)]
178    Balance { address: RawAddr },
179
180    /// * returns [TokenInfoResponse]
181    ///
182    /// Returns metadata on the contract - name, decimals, supply, etc.
183    #[returns(TokenInfoResponse)]
184    TokenInfo {},
185
186    /// * returns [MinterResponse]
187    ///
188    /// Returns who can mint and the hard cap on maximum tokens after minting.
189    #[returns(Option<MinterResponse>)]
190    Minter {},
191
192    /// * returns [AllowanceResponse]
193    ///
194    /// Returns how much spender can use from owner account, 0 if unset.
195    #[returns(AllowanceResponse)]
196    Allowance { owner: RawAddr, spender: RawAddr },
197
198    /// * returns [AllAllowancesResponse]
199    ///
200    /// Returns all allowances this owner has approved. Supports pagination.
201    #[returns(AllAllowancesResponse)]
202    AllAllowances {
203        owner: RawAddr,
204        start_after: Option<RawAddr>,
205        limit: Option<u32>,
206    },
207
208    /// * returns [AllSpenderAllowancesResponse]
209    ///
210    /// Returns all allowances this spender has been granted. Supports pagination.
211    #[returns(AllSpenderAllowancesResponse)]
212    AllSpenderAllowances {
213        spender: RawAddr,
214        start_after: Option<RawAddr>,
215        limit: Option<u32>,
216    },
217
218    /// * returns [AllAccountsResponse]
219    ///
220    /// Returns all accounts that have balances. Supports pagination.
221    #[returns(AllAccountsResponse)]
222    AllAccounts {
223        start_after: Option<RawAddr>,
224        limit: Option<u32>,
225    },
226
227    /// * returns [MarketingInfoResponse]
228    ///
229    /// Returns more metadata on the contract to display in the client:
230    /// - description, logo, project url, etc.
231    #[returns(MarketingInfoResponse)]
232    MarketingInfo {},
233
234    /// * returns [DownloadLogoResponse]
235    ///
236    /// Downloads the embedded logo data (if stored on chain). Errors if no logo data is stored for this
237    /// contract.
238    #[returns(DownloadLogoResponse)]
239    DownloadLogo {},
240
241    /************** Proprietary *******************/
242    /// * returns [cw2::ContractVersion]
243    #[returns(cw2::ContractVersion)]
244    Version {},
245}
246
247/// Placeholder migration message
248#[cw_serde]
249pub struct MigrateMsg {}
250
251#[cw_serde]
252#[derive(Eq)]
253pub struct InstantiateMinter {
254    pub minter: RawAddr,
255    pub cap: Option<Uint128>,
256}
257
258/************** Proprietary but doesn't affect interop *******************/
259/************** since queries are according to spec *******************/
260/************** and only return addresses *******************/
261#[cw_serde]
262pub struct InstantiateMarketingInfo {
263    pub project: Option<String>,
264    pub description: Option<String>,
265    pub marketing: Option<Addr>,
266    pub logo: Option<Logo>,
267}
268
269#[cw_serde]
270#[derive(Eq)]
271pub struct BalanceResponse {
272    pub balance: Uint128,
273}
274
275#[cw_serde]
276#[derive(Eq)]
277pub struct TokenInfoResponse {
278    pub name: String,
279    pub symbol: String,
280    pub decimals: u8,
281    pub total_supply: Uint128,
282}
283
284#[cw_serde]
285#[derive(Default)]
286pub struct AllowanceResponse {
287    pub allowance: Uint128,
288    pub expires: Expiration,
289}
290
291#[cw_serde]
292#[derive(Eq)]
293pub struct MinterResponse {
294    pub minter: Addr,
295    /// cap is a hard cap on total supply that can be achieved by minting.
296    /// Note that this refers to total_supply.
297    /// If None, there is unlimited cap.
298    pub cap: Option<Uint128>,
299}
300
301#[cw_serde]
302#[derive(Default)]
303pub struct MarketingInfoResponse {
304    /// A URL pointing to the project behind this token.
305    pub project: Option<String>,
306    /// A longer description of the token and it's utility. Designed for tooltips or such
307    pub description: Option<String>,
308    /// A link to the logo, or a comment there is an on-chain logo stored
309    pub logo: Option<LogoInfo>,
310    /// The address (if any) who can update this data structure
311    pub marketing: Option<Addr>,
312}
313
314/// When we download an embedded logo, we get this response type.
315/// We expect a SPA to be able to accept this info and display it.
316#[cw_serde]
317pub struct DownloadLogoResponse {
318    pub mime_type: String,
319    pub data: Binary,
320}
321
322#[cw_serde]
323pub struct AllowanceInfo {
324    pub spender: Addr,
325    pub allowance: Uint128,
326    pub expires: Expiration,
327}
328
329#[cw_serde]
330#[derive(Default)]
331pub struct AllAllowancesResponse {
332    pub allowances: Vec<AllowanceInfo>,
333}
334
335#[cw_serde]
336pub struct SpenderAllowanceInfo {
337    pub owner: Addr,
338    pub allowance: Uint128,
339    pub expires: Expiration,
340}
341
342#[cw_serde]
343#[derive(Default)]
344pub struct AllSpenderAllowancesResponse {
345    pub allowances: Vec<SpenderAllowanceInfo>,
346}
347
348#[cw_serde]
349#[derive(Default)]
350pub struct AllAccountsResponse {
351    pub accounts: Vec<Addr>,
352}
353
354/// This is used for uploading logo data, or setting it in InstantiateData
355#[cw_serde]
356pub enum Logo {
357    /// A reference to an externally hosted logo. Must be a valid HTTP or HTTPS URL.
358    Url(String),
359    /// Logo content stored on the blockchain. Enforce maximum size of 5KB on all variants
360    Embedded(EmbeddedLogo),
361}
362
363/// This is used to store the logo on the blockchain in an accepted format.
364/// Enforce maximum size of 5KB on all variants.
365#[cw_serde]
366pub enum EmbeddedLogo {
367    /// Store the Logo as an SVG file. The content must conform to the spec
368    /// at <https://en.wikipedia.org/wiki/Scalable_Vector_Graphics>
369    ///
370    /// (The contract should do some light-weight sanity-check validation)
371    Svg(Binary),
372    /// Store the Logo as a PNG file. This will likely only support up to 64x64 or so
373    /// within the 5KB limit.
374    Png(Binary),
375}
376
377/// This is used to display logo info, provide a link or inform there is one
378/// that can be downloaded from the blockchain itself
379#[cw_serde]
380pub enum LogoInfo {
381    /// A reference to an externally hosted logo. Must be a valid HTTP or HTTPS URL.
382    Url(String),
383    /// There is an embedded logo on the chain, make another call to download it.
384    Embedded,
385}
386
387impl From<&Logo> for LogoInfo {
388    fn from(logo: &Logo) -> Self {
389        match logo {
390            Logo::Url(url) => LogoInfo::Url(url.clone()),
391            Logo::Embedded(_) => LogoInfo::Embedded,
392        }
393    }
394}