1use crate::contract::{ensure_staking_permission, validate_funds};
5use crate::errors::ContractError;
6use crate::storage::{
7 account_from_address, save_account, ADMIN, MIXNET_CONTRACT_ADDRESS, MIX_DENOM,
8};
9use crate::traits::{
10 DelegatingAccount, GatewayBondingAccount, MixnodeBondingAccount, NodeFamilies, VestingAccount,
11};
12use crate::vesting::{populate_vesting_periods, Account};
13use contracts_common::signing::MessageSignature;
14use cosmwasm_std::{coin, BankMsg, Coin, DepsMut, Env, MessageInfo, Response, Timestamp};
15use mixnet_contract_common::families::FamilyHead;
16use mixnet_contract_common::{
17 Gateway, GatewayConfigUpdate, MixId, MixNode, MixNodeConfigUpdate, MixNodeCostParams,
18};
19use vesting_contract_common::events::{
20 new_ownership_transfer_event, new_periodic_vesting_account_event,
21 new_staking_address_update_event, new_track_gateway_unbond_event,
22 new_track_mixnode_pledge_decrease_event, new_track_mixnode_unbond_event,
23 new_track_reward_event, new_track_undelegation_event, new_vested_coins_withdraw_event,
24};
25use vesting_contract_common::messages::VestingSpecification;
26use vesting_contract_common::PledgeCap;
27
28pub fn try_create_family(
29 info: MessageInfo,
30 deps: DepsMut,
31 label: String,
32) -> Result<Response, ContractError> {
33 let account = account_from_address(info.sender.as_ref(), deps.storage, deps.api)?;
34 account.try_create_family(deps.storage, label)
35}
36pub fn try_join_family(
37 info: MessageInfo,
38 deps: DepsMut,
39 join_permit: MessageSignature,
40 family_head: FamilyHead,
41) -> Result<Response, ContractError> {
42 let account = account_from_address(info.sender.as_ref(), deps.storage, deps.api)?;
43 account.try_join_family(deps.storage, join_permit, family_head)
44}
45pub fn try_leave_family(
46 info: MessageInfo,
47 deps: DepsMut,
48 family_head: FamilyHead,
49) -> Result<Response, ContractError> {
50 let account = account_from_address(info.sender.as_ref(), deps.storage, deps.api)?;
51 account.try_leave_family(deps.storage, family_head)
52}
53pub fn try_kick_family_member(
54 info: MessageInfo,
55 deps: DepsMut,
56 member: String,
57) -> Result<Response, ContractError> {
58 let account = account_from_address(info.sender.as_ref(), deps.storage, deps.api)?;
59 account.try_head_kick_member(deps.storage, &member)
60}
61
62pub fn try_update_locked_pledge_cap(
66 address: String,
67 cap: PledgeCap,
68 info: MessageInfo,
69 deps: DepsMut,
70) -> Result<Response, ContractError> {
71 if info.sender != ADMIN.load(deps.storage)? {
72 return Err(ContractError::NotAdmin(info.sender.as_str().to_string()));
73 }
74 let mut account = account_from_address(&address, deps.storage, deps.api)?;
75
76 account.pledge_cap = Some(cap);
77 save_account(&account, deps.storage)?;
78 Ok(Response::default())
79}
80
81pub fn try_update_mixnode_config(
83 new_config: MixNodeConfigUpdate,
84 info: MessageInfo,
85 deps: DepsMut,
86) -> Result<Response, ContractError> {
87 let account = account_from_address(info.sender.as_str(), deps.storage, deps.api)?;
88 account.try_update_mixnode_config(new_config, deps.storage)
89}
90
91pub fn try_update_gateway_config(
92 new_config: GatewayConfigUpdate,
93 info: MessageInfo,
94 deps: DepsMut,
95) -> Result<Response, ContractError> {
96 let account = account_from_address(info.sender.as_str(), deps.storage, deps.api)?;
97 account.try_update_gateway_config(new_config, deps.storage)
98}
99
100pub fn try_update_mixnode_cost_params(
101 new_costs: MixNodeCostParams,
102 info: MessageInfo,
103 deps: DepsMut,
104) -> Result<Response, ContractError> {
105 let account = account_from_address(info.sender.as_str(), deps.storage, deps.api)?;
106 account.try_update_mixnode_cost_params(new_costs, deps.storage)
107}
108
109pub fn try_update_mixnet_address(
113 address: String,
114 info: MessageInfo,
115 deps: DepsMut<'_>,
116) -> Result<Response, ContractError> {
117 if info.sender != ADMIN.load(deps.storage)? {
118 return Err(ContractError::NotAdmin(info.sender.as_str().to_string()));
119 }
120 let mixnet_contract_address = deps.api.addr_validate(&address)?;
121
122 MIXNET_CONTRACT_ADDRESS.save(deps.storage, &mixnet_contract_address)?;
123 Ok(Response::default())
124}
125
126pub fn try_withdraw_vested_coins(
128 amount: Coin,
129 env: Env,
130 info: MessageInfo,
131 deps: DepsMut<'_>,
132) -> Result<Response, ContractError> {
133 let mix_denom = MIX_DENOM.load(deps.storage)?;
134 if amount.denom != mix_denom {
135 return Err(ContractError::WrongDenom(amount.denom, mix_denom));
136 }
137
138 let address = info.sender.clone();
139 let account = account_from_address(info.sender.as_str(), deps.storage, deps.api)?;
140 if address != account.owner_address() {
141 return Err(ContractError::NotOwner(account.owner_address().to_string()));
142 }
143 let spendable_coins = account.spendable_coins(None, &env, deps.storage)?;
144 if amount.amount <= spendable_coins.amount {
145 let new_balance = account.withdraw(&amount, deps.storage)?;
146
147 let send_tokens = BankMsg::Send {
148 to_address: account.owner_address().as_str().to_string(),
149 amount: vec![amount.clone()],
150 };
151
152 Ok(Response::new()
153 .add_message(send_tokens)
154 .add_event(new_vested_coins_withdraw_event(
155 &address,
156 &amount,
157 &coin(new_balance, &amount.denom),
158 )))
159 } else {
160 Err(ContractError::InsufficientSpendable(
161 account.owner_address().as_str().to_string(),
162 spendable_coins.amount.u128(),
163 ))
164 }
165}
166
167pub fn try_transfer_ownership(
169 to_address: String,
170 info: MessageInfo,
171 deps: DepsMut<'_>,
172) -> Result<Response, ContractError> {
173 let address = info.sender.clone();
174 let to_address = deps.api.addr_validate(&to_address)?;
175 let mut account = account_from_address(info.sender.as_str(), deps.storage, deps.api)?;
176 if address == account.owner_address() {
177 account.transfer_ownership(&to_address, deps.storage)?;
178 Ok(Response::new().add_event(new_ownership_transfer_event(&address, &to_address)))
179 } else {
180 Err(ContractError::NotOwner(account.owner_address().to_string()))
181 }
182}
183
184pub fn try_update_staking_address(
186 to_address: Option<String>,
187 info: MessageInfo,
188 deps: DepsMut<'_>,
189) -> Result<Response, ContractError> {
190 if let Some(ref to_address) = to_address {
191 if account_from_address(to_address, deps.storage, deps.api).is_ok() {
192 return Err(ContractError::StakingAccountExists(to_address.to_string()));
194 }
195 }
196
197 let address = info.sender.clone();
198 let to_address = to_address.and_then(|x| deps.api.addr_validate(&x).ok());
199 let mut account = account_from_address(info.sender.as_str(), deps.storage, deps.api)?;
200 if address == account.owner_address() {
201 let old = account.staking_address().cloned();
202 account.update_staking_address(to_address.clone(), deps.storage)?;
203 Ok(Response::new().add_event(new_staking_address_update_event(&old, &to_address)))
204 } else {
205 Err(ContractError::NotOwner(account.owner_address().to_string()))
206 }
207}
208
209pub fn try_bond_gateway(
211 gateway: Gateway,
212 owner_signature: MessageSignature,
213 amount: Coin,
214 info: MessageInfo,
215 env: Env,
216 deps: DepsMut<'_>,
217) -> Result<Response, ContractError> {
218 let mix_denom = MIX_DENOM.load(deps.storage)?;
219 let pledge = validate_funds(&[amount], mix_denom)?;
220 let account = account_from_address(info.sender.as_str(), deps.storage, deps.api)?;
221 account.try_bond_gateway(gateway, owner_signature, pledge, &env, deps.storage)
222}
223
224pub fn try_unbond_gateway(info: MessageInfo, deps: DepsMut<'_>) -> Result<Response, ContractError> {
226 let account = account_from_address(info.sender.as_str(), deps.storage, deps.api)?;
227 account.try_unbond_gateway(deps.storage)
228}
229
230pub fn try_track_unbond_gateway(
232 owner: &str,
233 amount: Coin,
234 info: MessageInfo,
235 deps: DepsMut<'_>,
236) -> Result<Response, ContractError> {
237 if info.sender != MIXNET_CONTRACT_ADDRESS.load(deps.storage)? {
238 return Err(ContractError::NotMixnetContract(info.sender));
239 }
240 let account = account_from_address(owner, deps.storage, deps.api)?;
241 account.try_track_unbond_gateway(amount, deps.storage)?;
242 Ok(Response::new().add_event(new_track_gateway_unbond_event()))
243}
244
245pub fn try_bond_mixnode(
247 mix_node: MixNode,
248 cost_params: MixNodeCostParams,
249 owner_signature: MessageSignature,
250 amount: Coin,
251 info: MessageInfo,
252 env: Env,
253 deps: DepsMut<'_>,
254) -> Result<Response, ContractError> {
255 let mix_denom = MIX_DENOM.load(deps.storage)?;
256 let pledge = validate_funds(&[amount], mix_denom)?;
257 let account = account_from_address(info.sender.as_str(), deps.storage, deps.api)?;
258 account.try_bond_mixnode(
259 mix_node,
260 cost_params,
261 owner_signature,
262 pledge,
263 &env,
264 deps.storage,
265 )
266}
267
268pub fn try_pledge_more(
269 deps: DepsMut<'_>,
270 env: Env,
271 info: MessageInfo,
272 amount: Coin,
273) -> Result<Response, ContractError> {
274 let mix_denom = MIX_DENOM.load(deps.storage)?;
275 let additional_pledge = validate_funds(&[amount], mix_denom)?;
276
277 let account = account_from_address(info.sender.as_str(), deps.storage, deps.api)?;
278 account.try_pledge_additional_tokens(additional_pledge, &env, deps.storage)
279}
280
281pub fn try_decrease_pledge(
282 deps: DepsMut<'_>,
283 info: MessageInfo,
284 amount: Coin,
285) -> Result<Response, ContractError> {
286 let mix_denom = MIX_DENOM.load(deps.storage)?;
287 let decrease = validate_funds(&[amount], mix_denom)?;
289
290 let account = account_from_address(info.sender.as_str(), deps.storage, deps.api)?;
291 account.try_decrease_mixnode_pledge(decrease, deps.storage)
292}
293
294pub fn try_unbond_mixnode(info: MessageInfo, deps: DepsMut<'_>) -> Result<Response, ContractError> {
296 let account = account_from_address(info.sender.as_str(), deps.storage, deps.api)?;
297 account.try_unbond_mixnode(deps.storage)
298}
299
300pub fn try_track_unbond_mixnode(
302 owner: &str,
303 amount: Coin,
304 info: MessageInfo,
305 deps: DepsMut<'_>,
306) -> Result<Response, ContractError> {
307 if info.sender != MIXNET_CONTRACT_ADDRESS.load(deps.storage)? {
308 return Err(ContractError::NotMixnetContract(info.sender));
309 }
310 let account = account_from_address(owner, deps.storage, deps.api)?;
311 account.try_track_unbond_mixnode(amount, deps.storage)?;
312 Ok(Response::new().add_event(new_track_mixnode_unbond_event()))
313}
314
315pub fn try_track_decrease_mixnode_pledge(
318 owner: &str,
319 amount: Coin,
320 info: MessageInfo,
321 deps: DepsMut<'_>,
322) -> Result<Response, ContractError> {
323 if info.sender != MIXNET_CONTRACT_ADDRESS.load(deps.storage)? {
324 return Err(ContractError::NotMixnetContract(info.sender));
325 }
326 let account = account_from_address(owner, deps.storage, deps.api)?;
327 account.try_track_decrease_mixnode_pledge(amount, deps.storage)?;
328 Ok(Response::new().add_event(new_track_mixnode_pledge_decrease_event()))
329}
330
331pub fn try_track_reward(
333 deps: DepsMut<'_>,
334 info: MessageInfo,
335 amount: Coin,
336 address: &str,
337) -> Result<Response, ContractError> {
338 if info.sender != MIXNET_CONTRACT_ADDRESS.load(deps.storage)? {
339 return Err(ContractError::NotMixnetContract(info.sender));
340 }
341 let account = account_from_address(address, deps.storage, deps.api)?;
342 account.track_reward(amount, deps.storage)?;
343 Ok(Response::new().add_event(new_track_reward_event()))
344}
345
346pub fn try_track_undelegation(
348 address: &str,
349 mix_id: MixId,
350 amount: Coin,
351 info: MessageInfo,
352 deps: DepsMut<'_>,
353) -> Result<Response, ContractError> {
354 if info.sender != MIXNET_CONTRACT_ADDRESS.load(deps.storage)? {
355 return Err(ContractError::NotMixnetContract(info.sender));
356 }
357 let account = account_from_address(address, deps.storage, deps.api)?;
358
359 account.track_undelegation(mix_id, amount, deps.storage)?;
360 Ok(Response::new().add_event(new_track_undelegation_event()))
361}
362
363pub fn try_delegate_to_mixnode(
365 mix_id: MixId,
366 amount: Coin,
367 on_behalf_of: Option<String>,
368 info: MessageInfo,
369 env: Env,
370 deps: DepsMut<'_>,
371) -> Result<Response, ContractError> {
372 let mix_denom = MIX_DENOM.load(deps.storage)?;
381 let amount = validate_funds(&[amount], mix_denom)?;
382
383 let account = match on_behalf_of {
384 Some(account_owner) => {
385 let account = account_from_address(&account_owner, deps.storage, deps.api)?;
386 ensure_staking_permission(&info.sender, &account)?;
387 account
388 }
389 None => account_from_address(info.sender.as_str(), deps.storage, deps.api)?,
391 };
392
393 account.try_delegate_to_mixnode(mix_id, amount, &env, deps.storage)
394}
395
396pub fn try_claim_operator_reward(
398 deps: DepsMut<'_>,
399 info: MessageInfo,
400) -> Result<Response, ContractError> {
401 let account = account_from_address(info.sender.as_str(), deps.storage, deps.api)?;
402 account.try_claim_operator_reward(deps.storage)
403}
404
405pub fn try_claim_delegator_reward(
407 deps: DepsMut<'_>,
408 info: MessageInfo,
409 mix_id: MixId,
410) -> Result<Response, ContractError> {
411 let account = account_from_address(info.sender.as_str(), deps.storage, deps.api)?;
412
413 account.try_claim_delegator_reward(mix_id, deps.storage)
414}
415
416pub fn try_undelegate_from_mixnode(
418 mix_id: MixId,
419 on_behalf_of: Option<String>,
420 info: MessageInfo,
421 deps: DepsMut<'_>,
422) -> Result<Response, ContractError> {
423 let account = match on_behalf_of {
424 Some(account_owner) => {
425 let account = account_from_address(&account_owner, deps.storage, deps.api)?;
426 ensure_staking_permission(&info.sender, &account)?;
427 account
428 }
429 None => account_from_address(info.sender.as_str(), deps.storage, deps.api)?,
431 };
432
433 account.try_undelegate_from_mixnode(mix_id, deps.storage)
434}
435
436pub fn try_create_periodic_vesting_account(
440 owner_address: &str,
441 staking_address: Option<String>,
442 vesting_spec: Option<VestingSpecification>,
443 cap: Option<PledgeCap>,
444 info: MessageInfo,
445 env: Env,
446 deps: DepsMut<'_>,
447) -> Result<Response, ContractError> {
448 if info.sender != ADMIN.load(deps.storage)? {
449 return Err(ContractError::NotAdmin(info.sender.as_str().to_string()));
450 }
451
452 let mix_denom = MIX_DENOM.load(deps.storage)?;
453
454 let account_exists = account_from_address(owner_address, deps.storage, deps.api).is_ok();
455 if account_exists {
456 return Err(ContractError::AccountAlreadyExists(
457 owner_address.to_string(),
458 ));
459 }
460
461 let vesting_spec = vesting_spec.unwrap_or_default();
462
463 let coin = validate_funds(&info.funds, mix_denom)?;
464
465 let owner_address = deps.api.addr_validate(owner_address)?;
466 let staking_address = if let Some(staking_address) = staking_address {
467 let staking_account_exists =
468 account_from_address(&staking_address, deps.storage, deps.api).is_ok();
469 if staking_account_exists {
470 return Err(ContractError::StakingAccountAlreadyExists(staking_address));
471 }
472 Some(deps.api.addr_validate(&staking_address)?)
473 } else {
474 None
475 };
476 let start_time = vesting_spec
477 .start_time()
478 .unwrap_or_else(|| env.block.time.seconds());
479
480 let periods = populate_vesting_periods(start_time, vesting_spec);
481
482 let start_time = Timestamp::from_seconds(start_time);
483
484 let response = Response::new();
485
486 Account::new(
487 owner_address.clone(),
488 staking_address.clone(),
489 coin.clone(),
490 start_time,
491 periods,
492 cap,
493 deps.storage,
494 )?;
495
496 Ok(response.add_event(new_periodic_vesting_account_event(
497 &owner_address,
498 &coin,
499 &staking_address,
500 start_time,
501 )))
502}