1use std::collections::HashSet;
2
3use cosmwasm_schema::cw_serde;
4use cosmwasm_std::{Addr, Api, Order, StdError, StdResult, Storage};
5use cw_storage_plus::{Bound, Map};
6
7pub const DEFAULT_LIMIT: u32 = 10;
8pub const MAX_LIMIT: u32 = 30;
9
10#[cfg(test)]
11mod tests;
12
13#[cw_serde]
14pub struct Whitelist {
15 pub address: String,
16 pub reason: Option<String>,
17}
18#[derive(thiserror::Error, Debug, PartialEq)]
20pub enum WhitelistError {
21 #[error("{0}")]
22 Std(#[from] StdError),
23 #[error("List Supplied to Whitelist is not unique")]
24 NotUnique(),
25 #[error("Whitelist entry {name} already exists")]
26 EntryExists {
27 name: String,
28 },
29 #[error("Whitelist entry {name} does not exist")]
30 EntryDoesntExist {
31 name: String,
32 },
33}
34const WHITELIST: Map<String, Option<String>> = Map::new("whitelist");
35
36#[cw_serde]
37pub struct WhitelistResponse<T> {
38 pub entries: Vec<T>,
39}
40
41pub fn initialize_whitelist(
42 storage: &mut dyn Storage,
43 api: &dyn Api,
44 addresses: Vec<Whitelist>,
45) -> Result<(), WhitelistError> {
46 let dupe_check: HashSet<String> = addresses.iter().map(|v| v.address.clone()).collect();
47 if dupe_check.len() != addresses.len() {
48 return Err(WhitelistError::NotUnique {});
49 }
50 for rec in addresses {
51 let addr = api.addr_validate(&rec.address)?;
52 WHITELIST.save(storage, addr.to_string(), &rec.reason)?;
53 }
54 Ok(())
55}
56pub fn add_entry(
57 storage: &mut dyn Storage,
58 api: &dyn Api,
59 address: String,
60 reason: Option<String>,
61) -> Result<(), WhitelistError> {
62 let addr = api.addr_validate(&address)?;
63 let entry_exists = WHITELIST.may_load(storage, address.clone())?;
64 if entry_exists.is_some() {
65 return Err(WhitelistError::EntryExists {
66 name: address,
67 });
68 }
69
70 WHITELIST.save(storage, addr.to_string(), &reason)?;
71
72 Ok(())
73}
74pub fn remove_entry(
75 storage: &mut dyn Storage,
76 api: &dyn Api,
77 name: String,
78) -> Result<(), WhitelistError> {
79 let addr = api.addr_validate(&name)?;
80 let entry_exists = WHITELIST.may_load(storage, addr.to_string())?;
81 if let Some(_entry) = entry_exists {
82 WHITELIST.remove(storage, addr.to_string());
83 Ok(())
84 } else {
85 Err(WhitelistError::EntryDoesntExist {
86 name,
87 })
88 }
89}
90pub fn query_entry(storage: &dyn Storage, address: String) -> StdResult<Option<Whitelist>> {
91 if let Some(reason) = WHITELIST.may_load(storage, address.clone())? {
92 Ok(Some(Whitelist {
93 address,
94 reason,
95 }))
96 } else {
97 Ok(None)
98 }
99}
100
101pub fn query_entries(
102 storage: &dyn Storage,
103 start_after: Option<String>,
104 limit: Option<u32>,
105) -> StdResult<WhitelistResponse<Whitelist>> {
106 let limit = limit.unwrap_or(DEFAULT_LIMIT).min(MAX_LIMIT) as usize;
107 let start = start_after.as_ref().map(Bound::exclusive);
109 let res = WHITELIST
110 .range(storage, start, None, Order::Ascending)
111 .take(limit)
112 .map(|x| {
113 x.map(|y| Whitelist {
114 address: y.0,
115 reason: y.1,
116 })
117 })
118 .collect::<StdResult<Vec<Whitelist>>>()?;
119 Ok(WhitelistResponse {
120 entries: res,
121 })
122}
123
124pub fn is_listed(storage: &dyn Storage, address: &Addr) -> StdResult<Option<Whitelist>> {
125 if let Some(entry) = WHITELIST.may_load(storage, address.to_string())? {
126 Ok(Some(Whitelist {
127 address: address.to_string(),
128 reason: entry,
129 }))
130 } else {
131 Ok(None)
132 }
133}
134pub fn assert_listed(storage: &dyn Storage, address: &Addr) -> Result<(), WhitelistError> {
135 if WHITELIST.may_load(storage, address.to_string())?.is_some() {
136 Ok(())
137 } else {
138 Err(WhitelistError::EntryDoesntExist {
139 name: address.to_string(),
140 })
141 }
142}