Skip to main content

builder_unlock/
contract.rs

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
18// Version and name used for contract migration.
19const CONTRACT_NAME: &str = env!("CARGO_PKG_NAME");
20const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION");
21
22/// Creates a new contract with the specified parameters in the `msg` variable.
23#[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/// Exposes all the execute functions available in the contract.
49///
50/// ## Execute messages
51/// * **ExecuteMsg::CreateAllocations** Create allocations.
52///
53/// * **ExecuteMsg::Withdraw** Withdraw unlocked ASTRO.
54///
55/// * **ExecuteMsg::TransferOwnership** Transfer contract ownership.
56///
57/// * **ExecuteMsg::ProposeNewReceiver** Propose a new receiver for a specific ASTRO unlock schedule.
58///
59/// * **ExecuteMsg::DropNewReceiver** Drop the proposal to change the receiver for an unlock schedule.
60///
61/// * **ExecuteMsg::ClaimReceiver**  Claim the position as a receiver for a specific unlock schedule.
62///
63/// * **ExecuteMsg::IncreaseAllocation** Increase ASTRO allocation for receiver.
64///
65/// * **ExecuteMsg::DecreaseAllocation** Decrease ASTRO allocation for receiver.
66///
67/// * **ExecuteMsg::TransferUnallocated** Transfer unallocated tokens.
68///
69/// * **ExecuteMsg::ProposeNewOwner** Creates a new request to change contract ownership.
70///
71/// * **ExecuteMsg::DropOwnershipProposal** Removes a request to change contract ownership.
72///
73/// * **ExecuteMsg::ClaimOwnership** Claims contract ownership.
74///
75/// * **ExecuteMsg::UpdateConfig** Update contract configuration.
76#[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
152/// Admin function facilitating the creation of new allocations.
153///
154/// * **creator** allocations creator (the contract admin).
155///
156/// * **deposit_token** token being deposited (should be ASTRO).
157///
158/// * **deposit_amount** tokens sent along with the call (should equal the sum of allocation amounts)
159///
160/// * **deposit_amount** new allocations being created.
161pub 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
207/// Allow allocation recipients to withdraw unlocked ASTRO.
208pub 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
237/// Allows the current allocation receiver to propose a new receiver.
238///
239/// * **new_receiver** new proposed receiver for the allocation.
240pub 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
258/// Drop the new proposed receiver for a specific allocation.
259pub 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
275/// Allows a newly proposed allocation receiver to claim the ownership of that allocation.
276///
277/// * **prev_receiver** this is the previous receiver for the allocation.
278pub 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
307/// Decrease an address' ASTRO allocation.
308///
309/// * **receiver** address that will have its allocation decreased.
310///
311/// * **amount** ASTRO amount to decrease the allocation by.
312pub 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
347/// Increase an address' ASTRO allocation.
348///
349/// * **receiver** address that will have its allocation increased.
350///
351/// * **amount** ASTRO amount to increase the allocation by.
352///
353/// * **deposit_amount** is amount of ASTRO to increase the allocation
354pub 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
395/// Transfer unallocated ASTRO tokens to a recipient.
396///
397/// * **amount** amount ASTRO to transfer.
398///
399/// * **recipient** transfer recipient.
400pub 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
438/// Updates builder unlock contract parameters.
439pub 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
465/// Updates builder unlock schedules for specified accounts.
466pub 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}