1use astroport::asset::addr_opt_validate;
2use astroport::asset::validate_native_denom;
3use astroport::common::{claim_ownership, drop_ownership_proposal, propose_new_owner};
4#[cfg(not(feature = "library"))]
5use cosmwasm_std::entry_point;
6use cosmwasm_std::{
7 attr, coins, ensure, BankMsg, DepsMut, Env, MessageInfo, Response, StdError, Uint128,
8};
9use cw2::set_contract_version;
10use cw_utils::{may_pay, must_pay};
11
12use astroport_governance::builder_unlock::{Config, CreateAllocationParams, Schedule};
13use astroport_governance::builder_unlock::{ExecuteMsg, InstantiateMsg};
14
15use crate::error::ContractError;
16use crate::state::{Allocation, CONFIG, OWNERSHIP_PROPOSAL, PARAMS, STATE};
17
18const CONTRACT_NAME: &str = env!("CARGO_PKG_NAME");
20const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION");
21
22#[cfg_attr(not(feature = "library"), entry_point)]
24pub fn instantiate(
25 deps: DepsMut,
26 env: Env,
27 _info: MessageInfo,
28 msg: InstantiateMsg,
29) -> Result<Response, ContractError> {
30 validate_native_denom(&msg.astro_denom)?;
31
32 CONFIG.save(
33 deps.storage,
34 &Config {
35 owner: deps.api.addr_validate(&msg.owner)?,
36 astro_denom: msg.astro_denom,
37 max_allocations_amount: msg.max_allocations_amount,
38 },
39 )?;
40
41 set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?;
42
43 STATE.save(deps.storage, &Default::default(), env.block.time.seconds())?;
44
45 Ok(Response::default())
46}
47
48#[cfg_attr(not(feature = "library"), entry_point)]
77pub fn execute(
78 deps: DepsMut,
79 env: Env,
80 info: MessageInfo,
81 msg: ExecuteMsg,
82) -> Result<Response, ContractError> {
83 match msg {
84 ExecuteMsg::CreateAllocations { allocations } => {
85 execute_create_allocations(deps, env, info, allocations)
86 }
87 ExecuteMsg::Withdraw {} => execute_withdraw(deps, env, info),
88 ExecuteMsg::ProposeNewReceiver { new_receiver } => {
89 execute_propose_new_receiver(deps, env, info, new_receiver)
90 }
91 ExecuteMsg::DropNewReceiver {} => execute_drop_new_receiver(deps, env, info),
92 ExecuteMsg::ClaimReceiver { prev_receiver } => {
93 execute_claim_receiver(deps, env, info, prev_receiver)
94 }
95 ExecuteMsg::IncreaseAllocation { receiver, amount } => {
96 let config = CONFIG.load(deps.storage)?;
97 ensure!(
98 info.sender == config.owner,
99 StdError::generic_err("Only the contract owner can increase allocations")
100 );
101 let deposit_amount = may_pay(&info, &config.astro_denom)?;
102
103 execute_increase_allocation(deps, env, &config, receiver, amount, deposit_amount)
104 }
105 ExecuteMsg::DecreaseAllocation { receiver, amount } => {
106 execute_decrease_allocation(deps, env, info, receiver, amount)
107 }
108 ExecuteMsg::TransferUnallocated { amount, recipient } => {
109 execute_transfer_unallocated(deps, env, info, amount, recipient)
110 }
111 ExecuteMsg::ProposeNewOwner {
112 new_owner,
113 expires_in,
114 } => {
115 let config = CONFIG.load(deps.storage)?;
116 propose_new_owner(
117 deps,
118 info,
119 env,
120 new_owner,
121 expires_in,
122 config.owner,
123 OWNERSHIP_PROPOSAL,
124 )
125 .map_err(Into::into)
126 }
127 ExecuteMsg::DropOwnershipProposal {} => {
128 let config = CONFIG.load(deps.storage)?;
129 drop_ownership_proposal(deps, info, config.owner, OWNERSHIP_PROPOSAL)
130 .map_err(Into::into)
131 }
132 ExecuteMsg::ClaimOwnership {} => {
133 claim_ownership(deps, info, env, OWNERSHIP_PROPOSAL, |deps, new_owner| {
134 CONFIG
135 .update::<_, StdError>(deps.storage, |mut v| {
136 v.owner = new_owner;
137 Ok(v)
138 })
139 .map(|_| ())
140 })
141 .map_err(Into::into)
142 }
143 ExecuteMsg::UpdateConfig {
144 new_max_allocations_amount,
145 } => update_config(deps, info, new_max_allocations_amount),
146 ExecuteMsg::UpdateUnlockSchedules {
147 new_unlock_schedules,
148 } => update_unlock_schedules(deps, env, info, new_unlock_schedules),
149 }
150}
151
152pub fn execute_create_allocations(
162 deps: DepsMut,
163 env: Env,
164 info: MessageInfo,
165 allocations: Vec<(String, CreateAllocationParams)>,
166) -> Result<Response, ContractError> {
167 let config = CONFIG.load(deps.storage)?;
168
169 ensure!(
170 info.sender == config.owner,
171 StdError::generic_err("Only the contract owner can create allocations",)
172 );
173
174 let deposit_amount = must_pay(&info, &config.astro_denom)?;
175 let expected_deposit: Uint128 = allocations.iter().map(|(_, params)| params.amount).sum();
176 ensure!(
177 deposit_amount == expected_deposit,
178 ContractError::DepositAmountMismatch {
179 expected: expected_deposit,
180 got: deposit_amount,
181 }
182 );
183
184 let mut state = STATE.load(deps.storage)?;
185
186 state.total_astro_deposited += deposit_amount;
187 state.remaining_astro_tokens += deposit_amount;
188
189 ensure!(
190 state.total_astro_deposited <= config.max_allocations_amount,
191 ContractError::TotalAllocationExceedsAmount(config.max_allocations_amount)
192 );
193
194 let block_ts = env.block.time.seconds();
195
196 for (user_unchecked, params) in allocations {
197 let user = deps.api.addr_validate(&user_unchecked)?;
198 let allocation = Allocation::new_allocation(deps.storage, block_ts, &user, params)?;
199 allocation.save(deps.storage)?;
200 }
201
202 STATE.save(deps.storage, &state, block_ts)?;
203
204 Ok(Response::default())
205}
206
207pub fn execute_withdraw(
209 deps: DepsMut,
210 env: Env,
211 info: MessageInfo,
212) -> Result<Response, ContractError> {
213 let block_ts = env.block.time.seconds();
214 let mut allocation = Allocation::must_load(deps.storage, block_ts, &info.sender)?;
215
216 let astro_to_withdraw = allocation.withdraw_and_update()?;
217 allocation.save(deps.storage)?;
218
219 let mut state = STATE.load(deps.storage)?;
220 state.remaining_astro_tokens -= astro_to_withdraw;
221
222 STATE.save(deps.storage, &state, block_ts)?;
223
224 let bank_msg = BankMsg::Send {
225 to_address: info.sender.to_string(),
226 amount: coins(
227 astro_to_withdraw.u128(),
228 CONFIG.load(deps.storage)?.astro_denom,
229 ),
230 };
231
232 Ok(Response::new()
233 .add_message(bank_msg)
234 .add_attribute("astro_withdrawn", astro_to_withdraw))
235}
236
237pub fn execute_propose_new_receiver(
241 deps: DepsMut,
242 env: Env,
243 info: MessageInfo,
244 new_receiver: String,
245) -> Result<Response, ContractError> {
246 let mut allocation =
247 Allocation::must_load(deps.storage, env.block.time.seconds(), &info.sender)?;
248 let new_receiver = deps.api.addr_validate(&new_receiver)?;
249
250 allocation.propose_new_receiver(deps.storage, &new_receiver)?;
251 allocation.save(deps.storage)?;
252
253 Ok(Response::new()
254 .add_attribute("action", "ProposeNewReceiver")
255 .add_attribute("proposed_receiver", new_receiver))
256}
257
258pub fn execute_drop_new_receiver(
260 deps: DepsMut,
261 env: Env,
262 info: MessageInfo,
263) -> Result<Response, ContractError> {
264 let mut allocation =
265 Allocation::must_load(deps.storage, env.block.time.seconds(), &info.sender)?;
266
267 let proposed_receiver = allocation.drop_proposed_receiver()?;
268 allocation.save(deps.storage)?;
269
270 Ok(Response::new()
271 .add_attribute("action", "DropNewReceiver")
272 .add_attribute("dropped_proposed_receiver", proposed_receiver))
273}
274
275pub fn execute_claim_receiver(
279 deps: DepsMut,
280 env: Env,
281 info: MessageInfo,
282 prev_receiver: String,
283) -> Result<Response, ContractError> {
284 let prev_receiver_addr = deps.api.addr_validate(&prev_receiver)?;
285 let allocation =
286 Allocation::must_load(deps.storage, env.block.time.seconds(), &prev_receiver_addr)?;
287
288 if allocation.params.proposed_receiver == Some(info.sender.clone()) {
289 ensure!(
290 !PARAMS.has(deps.storage, &info.sender),
291 ContractError::ProposedReceiverAlreadyHasAllocation {}
292 );
293
294 let new_allocation = allocation.claim_allocation(deps.storage, &info.sender)?;
295 new_allocation.save(deps.storage)?;
296 } else {
297 return Err(ContractError::ProposedReceiverMismatch {});
298 }
299
300 Ok(Response::new().add_attributes(vec![
301 attr("action", "ClaimReceiver"),
302 attr("prev_receiver", prev_receiver),
303 attr("receiver", info.sender),
304 ]))
305}
306
307pub fn execute_decrease_allocation(
313 deps: DepsMut,
314 env: Env,
315 info: MessageInfo,
316 receiver: String,
317 amount: Uint128,
318) -> Result<Response, ContractError> {
319 let config = CONFIG.load(deps.storage)?;
320
321 ensure!(
322 info.sender == config.owner,
323 ContractError::UnauthorizedDecreaseAllocation {}
324 );
325
326 let receiver = deps.api.addr_validate(&receiver)?;
327 let block_ts = env.block.time.seconds();
328 let mut allocation = Allocation::must_load(deps.storage, block_ts, &receiver)?;
329
330 allocation.decrease_allocation(amount)?;
331 allocation.save(deps.storage)?;
332
333 let mut state = STATE.load(deps.storage)?;
334
335 state.unallocated_astro_tokens = state.unallocated_astro_tokens.checked_add(amount)?;
336 state.remaining_astro_tokens = state.remaining_astro_tokens.checked_sub(amount)?;
337
338 STATE.save(deps.storage, &state, block_ts)?;
339
340 Ok(Response::new().add_attributes(vec![
341 attr("action", "execute_decrease_allocation"),
342 attr("receiver", receiver),
343 attr("amount", amount),
344 ]))
345}
346
347pub fn execute_increase_allocation(
355 deps: DepsMut,
356 env: Env,
357 config: &Config,
358 receiver: String,
359 amount: Uint128,
360 deposit_amount: Uint128,
361) -> Result<Response, ContractError> {
362 let receiver = deps.api.addr_validate(&receiver)?;
363 let block_ts = env.block.time.seconds();
364 let mut allocation = Allocation::must_load(deps.storage, block_ts, &receiver)?;
365
366 allocation.increase_allocation(amount)?;
367 allocation.save(deps.storage)?;
368
369 let mut state = STATE.load(deps.storage)?;
370
371 state.total_astro_deposited += deposit_amount;
372 state.unallocated_astro_tokens += deposit_amount;
373
374 ensure!(
375 state.total_astro_deposited <= config.max_allocations_amount,
376 ContractError::TotalAllocationExceedsAmount(config.max_allocations_amount)
377 );
378
379 ensure!(
380 state.unallocated_astro_tokens >= amount,
381 ContractError::UnallocatedTokensExceedsTotalDeposited(state.unallocated_astro_tokens)
382 );
383
384 state.unallocated_astro_tokens = state.unallocated_astro_tokens.checked_sub(amount)?;
385 state.remaining_astro_tokens += amount;
386
387 STATE.save(deps.storage, &state, block_ts)?;
388
389 Ok(Response::new()
390 .add_attribute("action", "execute_increase_allocation")
391 .add_attribute("amount", amount)
392 .add_attribute("receiver", receiver))
393}
394
395pub fn execute_transfer_unallocated(
401 deps: DepsMut,
402 env: Env,
403 info: MessageInfo,
404 amount: Uint128,
405 recipient: Option<String>,
406) -> Result<Response, ContractError> {
407 let config = CONFIG.load(deps.storage)?;
408
409 ensure!(
410 config.owner == info.sender,
411 ContractError::UnallocatedTransferUnauthorized {}
412 );
413
414 let mut state = STATE.load(deps.storage)?;
415
416 ensure!(
417 state.unallocated_astro_tokens >= amount,
418 ContractError::InsufficientUnallocatedTokens(state.unallocated_astro_tokens)
419 );
420
421 state.unallocated_astro_tokens = state.unallocated_astro_tokens.checked_sub(amount)?;
422 state.total_astro_deposited = state.total_astro_deposited.checked_sub(amount)?;
423
424 let recipient = addr_opt_validate(deps.api, &recipient)?.unwrap_or_else(|| info.sender.clone());
425 let bank_msg = BankMsg::Send {
426 to_address: recipient.to_string(),
427 amount: coins(amount.u128(), config.astro_denom),
428 };
429
430 STATE.save(deps.storage, &state, env.block.time.seconds())?;
431
432 Ok(Response::new()
433 .add_attribute("action", "execute_transfer_unallocated")
434 .add_attribute("amount", amount)
435 .add_message(bank_msg))
436}
437
438pub fn update_config(
440 deps: DepsMut,
441 info: MessageInfo,
442 new_max_allocations_amount: Uint128,
443) -> Result<Response, ContractError> {
444 let mut config = CONFIG.load(deps.storage)?;
445
446 ensure!(info.sender == config.owner, ContractError::Unauthorized {});
447
448 let state = STATE.load(deps.storage)?;
449
450 if new_max_allocations_amount < state.total_astro_deposited {
451 return Err(StdError::generic_err(format!(
452 "The new max allocations amount {new_max_allocations_amount} can not be less than currently deposited {}",
453 state.total_astro_deposited,
454 )).into());
455 }
456
457 config.max_allocations_amount = new_max_allocations_amount;
458 CONFIG.save(deps.storage, &config)?;
459
460 Ok(Response::new()
461 .add_attribute("action", "update_config")
462 .add_attribute("new_max_allocations_amount", new_max_allocations_amount))
463}
464
465pub fn update_unlock_schedules(
467 deps: DepsMut,
468 env: Env,
469 info: MessageInfo,
470 new_unlock_schedules: Vec<(String, Schedule)>,
471) -> Result<Response, ContractError> {
472 let config = CONFIG.load(deps.storage)?;
473
474 ensure!(info.sender == config.owner, ContractError::Unauthorized {});
475
476 let block_ts = env.block.time.seconds();
477
478 for (account, new_schedule) in new_unlock_schedules {
479 let account_addr = deps.api.addr_validate(&account)?;
480 let mut allocation = Allocation::must_load(deps.storage, block_ts, &account_addr)?;
481 allocation.update_unlock_schedule(&new_schedule)?;
482 allocation.save(deps.storage)?;
483 }
484
485 Ok(Response::new().add_attribute("action", "update_unlock_schedules"))
486}