1use astroport::common::OwnershipProposal;
2use cosmwasm_schema::cw_serde;
3use cosmwasm_std::{ensure, Addr, StdResult, Storage, Uint128};
4use cw_storage_plus::{Item, Map, SnapshotItem, SnapshotMap, Strategy};
5
6use astroport_governance::builder_unlock::{
7 AllocationParams, AllocationStatus, Config, CreateAllocationParams, Schedule,
8 SimulateWithdrawResponse, State,
9};
10
11use crate::error::ContractError;
12
13pub const CONFIG: Item<Config> = Item::new("config");
15pub const STATE: SnapshotItem<State> = SnapshotItem::new(
17 "state",
18 "state__checkpoint",
19 "state__changelog",
20 Strategy::EveryBlock,
21);
22pub const PARAMS: Map<&Addr, AllocationParams> = Map::new("params");
24pub const STATUS: SnapshotMap<&Addr, AllocationStatus> = SnapshotMap::new(
26 "status",
27 "status__checkpoint",
28 "status__changelog",
29 Strategy::EveryBlock,
30);
31pub const OWNERSHIP_PROPOSAL: Item<OwnershipProposal> = Item::new("ownership_proposal");
33
34#[cw_serde]
35pub struct Allocation {
36 pub params: AllocationParams,
38 pub status: AllocationStatus,
40 pub user: Addr,
42 pub block_ts: u64,
44}
45
46impl Allocation {
47 pub fn must_load(
48 storage: &dyn Storage,
49 block_ts: u64,
50 user: &Addr,
51 ) -> Result<Self, ContractError> {
52 let params = PARAMS
53 .load(storage, user)
54 .map_err(|_| ContractError::NoAllocation {
55 address: user.to_string(),
56 })?;
57 let status = STATUS.may_load(storage, user)?.unwrap_or_default();
58
59 Ok(Self {
60 params,
61 status,
62 user: user.clone(),
63 block_ts,
64 })
65 }
66
67 pub fn save(self, storage: &mut dyn Storage) -> StdResult<()> {
68 PARAMS.save(storage, &self.user, &self.params)?;
69 STATUS.save(storage, &self.user, &self.status, self.block_ts)
70 }
71
72 pub fn new_allocation(
73 storage: &mut dyn Storage,
74 block_ts: u64,
75 user: &Addr,
76 params: CreateAllocationParams,
77 ) -> Result<Self, ContractError> {
78 ensure!(
79 !PARAMS.has(storage, user),
80 ContractError::AllocationExists {
81 user: user.to_string()
82 }
83 );
84
85 params.validate(user.as_str())?;
86
87 Ok(Self {
88 params: AllocationParams {
89 unlock_schedule: params.unlock_schedule,
90 proposed_receiver: None,
91 },
92 status: AllocationStatus {
93 amount: params.amount,
94 astro_withdrawn: Default::default(),
95 unlocked_amount_checkpoint: Default::default(),
96 },
97 user: user.clone(),
98 block_ts,
99 })
100 }
101
102 pub fn withdraw_and_update(&mut self) -> Result<Uint128, ContractError> {
103 ensure!(
104 self.params.proposed_receiver.is_none(),
105 ContractError::WithdrawErrorWhenProposedReceiver {}
106 );
107
108 let SimulateWithdrawResponse { astro_to_withdraw } =
109 self.compute_withdraw_amount(self.block_ts);
110
111 ensure!(
112 !astro_to_withdraw.is_zero(),
113 ContractError::NoUnlockedAstro {}
114 );
115
116 self.status.astro_withdrawn += astro_to_withdraw;
117
118 Ok(astro_to_withdraw)
119 }
120
121 pub fn propose_new_receiver(
122 &mut self,
123 storage: &dyn Storage,
124 new_receiver: &Addr,
125 ) -> Result<(), ContractError> {
126 match &self.params.proposed_receiver {
127 Some(proposed_receiver) => Err(ContractError::ProposedReceiverAlreadySet {
128 proposed_receiver: proposed_receiver.clone(),
129 }),
130 None => {
131 ensure!(
132 !PARAMS.has(storage, new_receiver),
133 ContractError::ProposedReceiverAlreadyHasAllocation {}
134 );
135
136 self.params.proposed_receiver = Some(new_receiver.clone());
137
138 Ok(())
139 }
140 }
141 }
142
143 pub fn drop_proposed_receiver(&mut self) -> Result<Addr, ContractError> {
144 match self.params.proposed_receiver.clone() {
145 Some(proposed_receiver) => {
146 self.params.proposed_receiver = None;
147 Ok(proposed_receiver)
148 }
149 None => Err(ContractError::ProposedReceiverNotSet {}),
150 }
151 }
152
153 pub fn claim_allocation(
155 self,
156 storage: &mut dyn Storage,
157 new_receiver: &Addr,
158 ) -> Result<Self, ContractError> {
159 PARAMS.remove(storage, &self.user);
160 STATUS.remove(storage, &self.user, self.block_ts)?;
161
162 Ok(Self {
163 user: new_receiver.clone(),
164 params: AllocationParams {
165 proposed_receiver: None,
166 ..self.params
167 },
168 ..self
169 })
170 }
171
172 pub fn compute_unlocked_amount(&self, timestamp: u64) -> Uint128 {
174 let (schedule, unlock_checkpoint, total_amount) = (
175 &self.params.unlock_schedule,
176 self.status.unlocked_amount_checkpoint,
177 self.status.amount,
178 );
179
180 if timestamp < schedule.start_time + schedule.cliff {
182 unlock_checkpoint
183 } else if (timestamp < schedule.start_time + schedule.duration) && schedule.duration != 0 {
184 let unlocked_amount = if let Some(percent_at_cliff) = schedule.percent_at_cliff {
187 let amount_at_cliff = total_amount * percent_at_cliff;
188
189 amount_at_cliff
190 + total_amount.saturating_sub(amount_at_cliff).multiply_ratio(
191 timestamp - schedule.start_time - schedule.cliff,
192 schedule.duration - schedule.cliff,
193 )
194 } else {
195 total_amount.multiply_ratio(timestamp - schedule.start_time, schedule.duration)
197 };
198
199 if unlocked_amount > unlock_checkpoint {
200 unlocked_amount
201 } else {
202 unlock_checkpoint
203 }
204 }
205 else {
207 total_amount
208 }
209 }
210
211 pub fn compute_withdraw_amount(&self, timestamp: u64) -> SimulateWithdrawResponse {
213 let astro_unlocked = self.compute_unlocked_amount(timestamp);
214
215 SimulateWithdrawResponse {
217 astro_to_withdraw: astro_unlocked - self.status.astro_withdrawn,
218 }
219 }
220
221 pub fn decrease_allocation(&mut self, amount: Uint128) -> Result<(), ContractError> {
222 let unlocked_amount = self.compute_unlocked_amount(self.block_ts);
223 let locked_amount = self.status.amount - unlocked_amount;
224
225 ensure!(
226 locked_amount >= amount,
227 ContractError::InsufficientLockedAmount { locked_amount }
228 );
229
230 self.status.amount = self.status.amount.checked_sub(amount)?;
231 self.status.unlocked_amount_checkpoint = unlocked_amount;
232
233 Ok(())
234 }
235
236 pub fn increase_allocation(&mut self, amount: Uint128) -> Result<(), ContractError> {
237 self.status.amount += amount;
238 Ok(())
239 }
240
241 pub fn update_unlock_schedule(&mut self, new_schedule: &Schedule) -> StdResult<()> {
242 let unlocked_amount_checkpoint = self.compute_unlocked_amount(self.block_ts);
243
244 if unlocked_amount_checkpoint > self.status.unlocked_amount_checkpoint {
245 self.status.unlocked_amount_checkpoint = unlocked_amount_checkpoint;
246 }
247
248 self.params
249 .update_schedule(new_schedule.clone(), self.user.as_str())
250 }
251}