cw20_vesting/
state.rs

1use schemars::JsonSchema;
2use serde::{Deserialize, Serialize};
3
4use cosmwasm_std::{Addr, BlockInfo, Env, Storage, Uint128};
5use cw_storage_plus::{Item, Map};
6
7use crate::ContractError;
8use cw20::{AllowanceResponse, Logo, MarketingInfoResponse};
9use wynd_utils::Curve;
10
11#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)]
12#[serde(rename_all = "snake_case")]
13pub struct TokenInfo {
14    pub name: String,
15    pub symbol: String,
16    pub decimals: u8,
17    pub total_supply: Uint128,
18    pub mint: Option<MinterData>,
19}
20
21#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)]
22pub struct MinterData {
23    pub minter: Addr,
24    /// cap is how many more tokens can be issued by the minter
25    pub cap: Option<Curve>,
26}
27
28impl TokenInfo {
29    pub fn get_cap(&self, block: &BlockInfo) -> Option<Uint128> {
30        self.mint
31            .as_ref()
32            .and_then(|v| v.cap.as_ref().map(|v| v.value(block.time.seconds())))
33    }
34}
35
36pub const ALLOWLIST: Item<Vec<Addr>> = Item::new("allowlist");
37pub const TOKEN_INFO: Item<TokenInfo> = Item::new("token_info");
38pub const MARKETING_INFO: Item<MarketingInfoResponse> = Item::new("marketing_info");
39pub const LOGO: Item<Logo> = Item::new("logo");
40pub const BALANCES: Map<&Addr, Uint128> = Map::new("balance");
41pub const ALLOWANCES: Map<(&Addr, &Addr), AllowanceResponse> = Map::new("allowance");
42/// existing vesting schedules for each account
43pub const VESTING: Map<&Addr, Curve> = Map::new("vesting");
44
45/// This reduces the account by the given amount, but it also checks the vesting schedule to
46/// ensure there is enough liquidity to do the transfer.
47/// (Always use this to enforce the vesting schedule)
48pub fn deduct_coins(
49    storage: &mut dyn Storage,
50    env: &Env,
51    sender: &Addr,
52    amount: Uint128,
53) -> Result<Uint128, ContractError> {
54    // vesting is how much is currently vesting
55    let vesting = VESTING
56        .may_load(storage, sender)?
57        .map(|v| v.value(env.block.time.seconds()));
58
59    // this occurs when there is a curve defined, but it is now at 0 (eg. fully vested)
60    // in this case, we can safely delete it (as it will remain 0 forever)
61    if vesting == Some(Uint128::zero()) {
62        VESTING.remove(storage, sender);
63    }
64
65    BALANCES.update(storage, sender, |balance: Option<Uint128>| {
66        let remainder = balance.unwrap_or_default().checked_sub(amount)?;
67        // enforce vesting (must have at least this much available)
68        if let Some(vest) = vesting {
69            if vest > remainder {
70                return Err(ContractError::CantMoveVestingTokens);
71            }
72        }
73        Ok(remainder)
74    })
75}