1use crate::{
2 adapters::{asset_gauge::AssetGauge, global_config_adapter::ConfigExt},
3 constants::AT_ASSET_GAUGE,
4 error::SharedError,
5 helpers::{assets::Assets, time::Time},
6};
7use cosmwasm_schema::{cw_serde, QueryResponses};
8use cosmwasm_std::{Addr, QuerierWrapper, Uint128};
9use cw_asset::{Asset, AssetInfo, AssetInfoUnchecked, AssetUnchecked};
10
11#[cw_serde]
12pub struct InstantiateMsg {
13 pub global_config_addr: String,
14 pub whitelist: Vec<AssetInfoUnchecked>,
15 pub fee: AssetUnchecked,
16}
17
18#[cw_serde]
19pub enum BribeDistribution {
20 Func {
21 start: Option<u64>,
22 end: u64,
23 func_type: FuncType,
24 },
25 Next,
26 Specific(Vec<(u64, Uint128)>),
27}
28
29#[cw_serde]
30pub enum FuncType {
31 Linear,
32 Bezier,
33 EaseInOutCubic,
34 EaseInCubic,
35 EaseOutCubic,
36 Parametric,
37}
38
39#[cw_serde]
40pub struct Config {
41 pub whitelist: Vec<AssetInfo>,
42 pub allow_any: bool,
43 pub fee: Asset,
44 pub global_config_addr: Addr,
45}
46
47impl Config {
48 pub fn asset_gauge(&self, querier: &QuerierWrapper) -> Result<AssetGauge, SharedError> {
49 self.global_config().get_address(querier, AT_ASSET_GAUGE).map(AssetGauge)
50 }
51}
52
53#[cw_serde]
54#[derive(Default)]
55pub struct BribeBucket {
56 pub gauge: String,
57 pub asset: Option<AssetInfo>,
58 pub assets: Assets,
59}
60
61#[cw_serde]
62#[derive(Default)]
63pub struct BribeBuckets {
64 pub buckets: Vec<BribeBucket>,
65}
66
67impl BribeBuckets {
68 pub fn get(&mut self, gauge: &str, asset: &AssetInfo) -> &mut BribeBucket {
69 self.get_index(gauge, asset).1
70 }
71
72 pub fn get_index(&mut self, gauge: &str, asset: &AssetInfo) -> (usize, &mut BribeBucket) {
73 if let Some(i) =
74 self.buckets.iter().position(|a| a.gauge == gauge && a.asset.as_ref() == Some(asset))
75 {
76 (i, &mut self.buckets[i])
77 } else {
78 self.buckets.push(BribeBucket {
79 gauge: gauge.to_string(),
80 asset: Some(asset.clone()),
81 assets: Assets::default(),
82 });
83 let i = self.buckets.len() - 1;
84 (i, &mut self.buckets[i])
85 }
86 }
87
88 pub fn add(&mut self, gauge: &str, asset: &AssetInfo, bribe: &Asset) {
89 self.get(gauge, asset).assets.add(bribe);
90 }
91
92 pub fn remove(
93 &mut self,
94 gauge: &str,
95 asset: &AssetInfo,
96 bribe: &Asset,
97 ) -> Result<(), SharedError> {
98 let (index, bucket) = self.get_index(gauge, asset);
99 bucket.assets.remove(bribe)?;
100 if bucket.assets.is_empty() {
101 self.buckets.remove(index);
102 }
103 Ok(())
104 }
105
106 pub fn is_empty(&self) -> bool {
107 self.buckets.len() == 0
108 }
109}
110
111#[cw_serde]
112pub enum ExecuteMsg {
113 AddBribe {
114 bribe: AssetUnchecked,
115 gauge: String,
116 for_info: AssetInfoUnchecked,
117 distribution: BribeDistribution,
118 },
119
120 WithdrawBribes {
121 period: u64,
122 },
123
124 ClaimBribes {
125 periods: Option<Vec<u64>>,
126 },
127
128 WhitelistAssets(Vec<AssetInfoUnchecked>),
130 RemoveAssets(Vec<AssetInfoUnchecked>),
131
132 UpdateConfig {
133 fee: Option<AssetUnchecked>,
134 allow_any: Option<bool>,
135 },
136}
137
138#[cw_serde]
139#[derive(QueryResponses)]
140pub enum QueryMsg {
141 #[returns(Config)]
142 Config {},
143
144 #[returns(NextClaimPeriodResponse)]
145 NextClaimPeriod {
146 user: String,
147 },
148
149 #[returns(BribesResponse)]
150 Bribes {
151 period: Option<Time>,
152 },
153
154 #[returns(UserClaimableResponse)]
155 UserClaimable {
156 user: String,
157 periods: Option<Vec<u64>>,
158 },
159}
160
161#[cw_serde]
162pub struct NextClaimPeriodResponse {
163 pub period: u64,
164}
165
166pub type BribesResponse = BribeBuckets;
167
168#[cw_serde]
169pub struct UserClaimableResponse {
170 pub start: u64,
171 pub end: u64,
172 pub buckets: Vec<BribeBucket>,
173}
174
175#[cw_serde]
176pub struct MigrateMsg {}
177
178#[cfg(test)]
179mod test {
180 use cosmwasm_std::Addr;
181 use cw_asset::{Asset, AssetInfo};
182
183 use crate::{error::SharedError, msgs_bribe_manager::BribeBuckets};
184
185 use super::BribeBucket;
186
187 #[test]
188 fn test_bribe_bucket() {
189 let asset = AssetInfo::cw20(Addr::unchecked("ampLUNA"));
190 let gauge = "test".to_string();
191
192 let mut bucket = BribeBucket {
193 asset: Some(asset.clone()),
194 gauge: gauge.clone(),
195 assets: vec![].into(),
196 };
197
198 bucket.assets.add(&Asset::native("uluna", 1000u128));
199 bucket.assets.add(&Asset::native("uluna", 500u128));
200 bucket.assets.add(&Asset::cw20(Addr::unchecked("test"), 500u128));
201 bucket.assets.remove(&Asset::native("uluna", 1000u128)).unwrap();
202
203 assert_eq!(
204 bucket,
205 BribeBucket {
206 asset: Some(asset.clone()),
207 gauge: gauge.clone(),
208 assets: vec![
209 Asset::native("uluna", 500u128),
210 Asset::cw20(Addr::unchecked("test"), 500u128)
211 ]
212 .into()
213 }
214 );
215
216 bucket.assets.remove(&Asset::native("uluna", 500u128)).unwrap();
217
218 assert_eq!(
219 bucket,
220 BribeBucket {
221 asset: Some(asset.clone()),
222 gauge,
223 assets: vec![Asset::cw20(Addr::unchecked("test"), 500u128)].into()
224 }
225 );
226 }
227
228 #[test]
229 fn test_bribe_bucket_fail_not_existing() {
230 let asset = AssetInfo::cw20(Addr::unchecked("ampLUNA"));
231 let gauge = "test".to_string();
232
233 let mut bucket = BribeBucket {
234 asset: Some(asset.clone()),
235 gauge,
236 assets: vec![].into(),
237 };
238
239 let err = bucket.assets.remove(&Asset::native("uluna", 1000u128)).unwrap_err();
240
241 assert_eq!(
242 err,
243 SharedError::NotFound(format!("asset {0}", AssetInfo::native("uluna".to_string())))
244 )
245 }
246
247 #[test]
248 fn test_bribe_bucket_fail_insufficient_balance() {
249 let asset = AssetInfo::cw20(Addr::unchecked("ampLUNA"));
250 let gauge = "test".to_string();
251 let mut bucket = BribeBucket {
252 asset: Some(asset.clone()),
253 gauge,
254 assets: vec![].into(),
255 };
256
257 bucket.assets.add(&Asset::native("uluna", 500u128));
258 let err = bucket.assets.remove(&Asset::native("uluna", 1000u128)).unwrap_err();
259
260 assert_eq!(
261 err,
262 SharedError::InsufficientBalance(format!(
263 "existing: {0} withdrawing: {1}",
264 Asset::native("uluna", 500u128),
265 Asset::native("uluna", 1000u128)
266 ),)
267 )
268 }
269
270 #[test]
271 fn test_bribe_buckets() {
272 let mut bucket = BribeBuckets::default();
273
274 let asset = AssetInfo::cw20(Addr::unchecked("ampLUNA"));
275 let gauge = "test".to_string();
276
277 bucket.get(&gauge, &asset).assets.add(&Asset::native("uluna", 1000u128));
278 bucket.get(&gauge, &asset).assets.add(&Asset::native("uluna", 500u128));
279 bucket.get(&gauge, &asset).assets.add(&Asset::cw20(Addr::unchecked("test"), 500u128));
280 bucket.get(&gauge, &asset).assets.remove(&Asset::native("uluna", 1000u128)).unwrap();
281
282 assert_eq!(
283 bucket,
284 BribeBuckets {
285 buckets: vec![BribeBucket {
286 asset: Some(asset.clone()),
287 gauge: gauge.clone(),
288 assets: vec![
289 Asset::native("uluna", 500u128),
290 Asset::cw20(Addr::unchecked("test"), 500u128)
291 ]
292 .into()
293 }]
294 }
295 );
296
297 bucket.remove(&gauge, &asset, &Asset::native("uluna", 500u128)).unwrap();
298
299 assert_eq!(
300 bucket,
301 BribeBuckets {
302 buckets: vec![BribeBucket {
303 asset: Some(asset.clone()),
304 gauge: gauge.clone(),
305 assets: vec![Asset::cw20(Addr::unchecked("test"), 500u128)].into()
306 }]
307 }
308 );
309 }
310}