Feature/vesting delegation amount query (#3229)
* moved queries and transactions out of contract.rs * added queries for vesting delegation details * nyxd_client support
This commit is contained in:
committed by
GitHub
parent
a3b4d04d02
commit
4fb63d8892
@@ -229,6 +229,32 @@ pub trait VestingQueryClient {
|
|||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn get_vesting_delegation(
|
||||||
|
&self,
|
||||||
|
address: &str,
|
||||||
|
mix_id: MixId,
|
||||||
|
block_timestamp_secs: u64,
|
||||||
|
) -> Result<VestingDelegation, NyxdError> {
|
||||||
|
self.query_vesting_contract(VestingQueryMsg::GetDelegation {
|
||||||
|
address: address.to_string(),
|
||||||
|
mix_id,
|
||||||
|
block_timestamp_secs,
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_total_delegation_amount(
|
||||||
|
&self,
|
||||||
|
address: &str,
|
||||||
|
mix_id: MixId,
|
||||||
|
) -> Result<Coin, NyxdError> {
|
||||||
|
self.query_vesting_contract(VestingQueryMsg::GetTotalDelegationAmount {
|
||||||
|
address: address.to_string(),
|
||||||
|
mix_id,
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
async fn get_delegation_timestamps(
|
async fn get_delegation_timestamps(
|
||||||
&self,
|
&self,
|
||||||
address: &str,
|
address: &str,
|
||||||
|
|||||||
@@ -260,6 +260,15 @@ pub enum QueryMsg {
|
|||||||
GetCurrentVestingPeriod {
|
GetCurrentVestingPeriod {
|
||||||
address: String,
|
address: String,
|
||||||
},
|
},
|
||||||
|
GetDelegation {
|
||||||
|
address: String,
|
||||||
|
mix_id: MixId,
|
||||||
|
block_timestamp_secs: u64,
|
||||||
|
},
|
||||||
|
GetTotalDelegationAmount {
|
||||||
|
address: String,
|
||||||
|
mix_id: MixId,
|
||||||
|
},
|
||||||
GetDelegationTimes {
|
GetDelegationTimes {
|
||||||
address: String,
|
address: String,
|
||||||
mix_id: MixId,
|
mix_id: MixId,
|
||||||
|
|||||||
@@ -1,38 +1,14 @@
|
|||||||
use crate::errors::ContractError;
|
use crate::errors::ContractError;
|
||||||
use crate::storage::{
|
pub use crate::queries::*;
|
||||||
account_from_address, save_account, BlockTimestampSecs, ACCOUNTS, ADMIN, DELEGATIONS,
|
use crate::storage::{ADMIN, MIXNET_CONTRACT_ADDRESS, MIX_DENOM};
|
||||||
MIXNET_CONTRACT_ADDRESS, MIX_DENOM,
|
pub use crate::transactions::*;
|
||||||
};
|
use crate::vesting::Account;
|
||||||
use crate::traits::{
|
|
||||||
DelegatingAccount, GatewayBondingAccount, MixnodeBondingAccount, NodeFamilies, VestingAccount,
|
|
||||||
};
|
|
||||||
use crate::vesting::{populate_vesting_periods, Account};
|
|
||||||
use contracts_common::signing::MessageSignature;
|
|
||||||
use contracts_common::ContractBuildInformation;
|
|
||||||
use cosmwasm_std::{
|
use cosmwasm_std::{
|
||||||
coin, entry_point, to_binary, Addr, BankMsg, Coin, Deps, DepsMut, Env, MessageInfo, Order,
|
entry_point, to_binary, Addr, Coin, Deps, DepsMut, Env, MessageInfo, QueryResponse, Response,
|
||||||
QueryResponse, Response, StdResult, Timestamp, Uint128,
|
Uint128,
|
||||||
};
|
};
|
||||||
use cw_storage_plus::Bound;
|
|
||||||
use mixnet_contract_common::families::FamilyHead;
|
|
||||||
use mixnet_contract_common::gateway::GatewayConfigUpdate;
|
|
||||||
use mixnet_contract_common::mixnode::{MixNodeConfigUpdate, MixNodeCostParams};
|
|
||||||
use mixnet_contract_common::{Gateway, MixId, MixNode};
|
|
||||||
use semver::Version;
|
use semver::Version;
|
||||||
use vesting_contract_common::events::{
|
use vesting_contract_common::messages::{ExecuteMsg, InitMsg, MigrateMsg, QueryMsg};
|
||||||
new_ownership_transfer_event, new_periodic_vesting_account_event,
|
|
||||||
new_staking_address_update_event, new_track_gateway_unbond_event,
|
|
||||||
new_track_mixnode_unbond_event, new_track_reward_event, new_track_undelegation_event,
|
|
||||||
new_vested_coins_withdraw_event,
|
|
||||||
};
|
|
||||||
use vesting_contract_common::messages::{
|
|
||||||
ExecuteMsg, InitMsg, MigrateMsg, QueryMsg, VestingSpecification,
|
|
||||||
};
|
|
||||||
use vesting_contract_common::{
|
|
||||||
AccountVestingCoins, AccountsResponse, AllDelegationsResponse, BaseVestingAccountInfo,
|
|
||||||
DelegationTimesResponse, OriginalVestingResponse, Period, PledgeCap, PledgeData,
|
|
||||||
VestingCoinsResponse, VestingDelegation,
|
|
||||||
};
|
|
||||||
|
|
||||||
// version info for migration info
|
// version info for migration info
|
||||||
const CONTRACT_NAME: &str = "crate:nym-vesting-contract";
|
const CONTRACT_NAME: &str = "crate:nym-vesting-contract";
|
||||||
@@ -209,453 +185,6 @@ pub fn execute(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn try_create_family(
|
|
||||||
info: MessageInfo,
|
|
||||||
deps: DepsMut,
|
|
||||||
label: String,
|
|
||||||
) -> Result<Response, ContractError> {
|
|
||||||
let account = account_from_address(info.sender.as_ref(), deps.storage, deps.api)?;
|
|
||||||
account.try_create_family(deps.storage, label)
|
|
||||||
}
|
|
||||||
pub fn try_join_family(
|
|
||||||
info: MessageInfo,
|
|
||||||
deps: DepsMut,
|
|
||||||
join_permit: MessageSignature,
|
|
||||||
family_head: FamilyHead,
|
|
||||||
) -> Result<Response, ContractError> {
|
|
||||||
let account = account_from_address(info.sender.as_ref(), deps.storage, deps.api)?;
|
|
||||||
account.try_join_family(deps.storage, join_permit, family_head)
|
|
||||||
}
|
|
||||||
pub fn try_leave_family(
|
|
||||||
info: MessageInfo,
|
|
||||||
deps: DepsMut,
|
|
||||||
family_head: FamilyHead,
|
|
||||||
) -> Result<Response, ContractError> {
|
|
||||||
let account = account_from_address(info.sender.as_ref(), deps.storage, deps.api)?;
|
|
||||||
account.try_leave_family(deps.storage, family_head)
|
|
||||||
}
|
|
||||||
pub fn try_kick_family_member(
|
|
||||||
info: MessageInfo,
|
|
||||||
deps: DepsMut,
|
|
||||||
member: String,
|
|
||||||
) -> Result<Response, ContractError> {
|
|
||||||
let account = account_from_address(info.sender.as_ref(), deps.storage, deps.api)?;
|
|
||||||
account.try_head_kick_member(deps.storage, &member)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Update locked_pledge_cap, the hard cap for staking/bonding with unvested tokens.
|
|
||||||
///
|
|
||||||
/// Callable by ADMIN only, see [instantiate].
|
|
||||||
pub fn try_update_locked_pledge_cap(
|
|
||||||
address: String,
|
|
||||||
cap: PledgeCap,
|
|
||||||
info: MessageInfo,
|
|
||||||
deps: DepsMut,
|
|
||||||
) -> Result<Response, ContractError> {
|
|
||||||
if info.sender != ADMIN.load(deps.storage)? {
|
|
||||||
return Err(ContractError::NotAdmin(info.sender.as_str().to_string()));
|
|
||||||
}
|
|
||||||
let mut account = account_from_address(&address, deps.storage, deps.api)?;
|
|
||||||
|
|
||||||
account.pledge_cap = Some(cap);
|
|
||||||
save_account(&account, deps.storage)?;
|
|
||||||
Ok(Response::default())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Update config for a mixnode bonded with vesting account, sends [mixnet_contract_common::ExecuteMsg::UpdateMixnodeConfig] to [crate::storage::MIXNET_CONTRACT_ADDRESS].
|
|
||||||
pub fn try_update_mixnode_config(
|
|
||||||
new_config: MixNodeConfigUpdate,
|
|
||||||
info: MessageInfo,
|
|
||||||
deps: DepsMut,
|
|
||||||
) -> Result<Response, ContractError> {
|
|
||||||
let account = account_from_address(info.sender.as_str(), deps.storage, deps.api)?;
|
|
||||||
account.try_update_mixnode_config(new_config, deps.storage)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn try_update_gateway_config(
|
|
||||||
new_config: GatewayConfigUpdate,
|
|
||||||
info: MessageInfo,
|
|
||||||
deps: DepsMut,
|
|
||||||
) -> Result<Response, ContractError> {
|
|
||||||
let account = account_from_address(info.sender.as_str(), deps.storage, deps.api)?;
|
|
||||||
account.try_update_gateway_config(new_config, deps.storage)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn try_update_mixnode_cost_params(
|
|
||||||
new_costs: MixNodeCostParams,
|
|
||||||
info: MessageInfo,
|
|
||||||
deps: DepsMut,
|
|
||||||
) -> Result<Response, ContractError> {
|
|
||||||
let account = account_from_address(info.sender.as_str(), deps.storage, deps.api)?;
|
|
||||||
account.try_update_mixnode_cost_params(new_costs, deps.storage)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Updates mixnet contract address, for cases when a new mixnet contract is deployed.
|
|
||||||
///
|
|
||||||
/// Callable by ADMIN only, see [instantiate].
|
|
||||||
pub fn try_update_mixnet_address(
|
|
||||||
address: String,
|
|
||||||
info: MessageInfo,
|
|
||||||
deps: DepsMut<'_>,
|
|
||||||
) -> Result<Response, ContractError> {
|
|
||||||
if info.sender != ADMIN.load(deps.storage)? {
|
|
||||||
return Err(ContractError::NotAdmin(info.sender.as_str().to_string()));
|
|
||||||
}
|
|
||||||
let mixnet_contract_address = deps.api.addr_validate(&address)?;
|
|
||||||
|
|
||||||
MIXNET_CONTRACT_ADDRESS.save(deps.storage, &mixnet_contract_address)?;
|
|
||||||
Ok(Response::default())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Withdraw already vested coins.
|
|
||||||
pub fn try_withdraw_vested_coins(
|
|
||||||
amount: Coin,
|
|
||||||
env: Env,
|
|
||||||
info: MessageInfo,
|
|
||||||
deps: DepsMut<'_>,
|
|
||||||
) -> Result<Response, ContractError> {
|
|
||||||
let mix_denom = MIX_DENOM.load(deps.storage)?;
|
|
||||||
if amount.denom != mix_denom {
|
|
||||||
return Err(ContractError::WrongDenom(amount.denom, mix_denom));
|
|
||||||
}
|
|
||||||
|
|
||||||
let address = info.sender.clone();
|
|
||||||
let account = account_from_address(info.sender.as_str(), deps.storage, deps.api)?;
|
|
||||||
if address != account.owner_address() {
|
|
||||||
return Err(ContractError::NotOwner(account.owner_address().to_string()));
|
|
||||||
}
|
|
||||||
let spendable_coins = account.spendable_coins(None, &env, deps.storage)?;
|
|
||||||
if amount.amount <= spendable_coins.amount {
|
|
||||||
let new_balance = account.withdraw(&amount, deps.storage)?;
|
|
||||||
|
|
||||||
let send_tokens = BankMsg::Send {
|
|
||||||
to_address: account.owner_address().as_str().to_string(),
|
|
||||||
amount: vec![amount.clone()],
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(Response::new()
|
|
||||||
.add_message(send_tokens)
|
|
||||||
.add_event(new_vested_coins_withdraw_event(
|
|
||||||
&address,
|
|
||||||
&amount,
|
|
||||||
&coin(new_balance, &amount.denom),
|
|
||||||
)))
|
|
||||||
} else {
|
|
||||||
Err(ContractError::InsufficientSpendable(
|
|
||||||
account.owner_address().as_str().to_string(),
|
|
||||||
spendable_coins.amount.u128(),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Transfer ownership of the entire vesting account.
|
|
||||||
fn try_transfer_ownership(
|
|
||||||
to_address: String,
|
|
||||||
info: MessageInfo,
|
|
||||||
deps: DepsMut<'_>,
|
|
||||||
) -> Result<Response, ContractError> {
|
|
||||||
let address = info.sender.clone();
|
|
||||||
let to_address = deps.api.addr_validate(&to_address)?;
|
|
||||||
let mut account = account_from_address(info.sender.as_str(), deps.storage, deps.api)?;
|
|
||||||
if address == account.owner_address() {
|
|
||||||
account.transfer_ownership(&to_address, deps.storage)?;
|
|
||||||
Ok(Response::new().add_event(new_ownership_transfer_event(&address, &to_address)))
|
|
||||||
} else {
|
|
||||||
Err(ContractError::NotOwner(account.owner_address().to_string()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set or update staking address for a vesting account.
|
|
||||||
fn try_update_staking_address(
|
|
||||||
to_address: Option<String>,
|
|
||||||
info: MessageInfo,
|
|
||||||
deps: DepsMut<'_>,
|
|
||||||
) -> Result<Response, ContractError> {
|
|
||||||
if let Some(ref to_address) = to_address {
|
|
||||||
if account_from_address(to_address, deps.storage, deps.api).is_ok() {
|
|
||||||
// do not allow setting staking address to an existing account's address
|
|
||||||
return Err(ContractError::StakingAccountExists(to_address.to_string()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let address = info.sender.clone();
|
|
||||||
let to_address = to_address.and_then(|x| deps.api.addr_validate(&x).ok());
|
|
||||||
let mut account = account_from_address(info.sender.as_str(), deps.storage, deps.api)?;
|
|
||||||
if address == account.owner_address() {
|
|
||||||
let old = account.staking_address().cloned();
|
|
||||||
account.update_staking_address(to_address.clone(), deps.storage)?;
|
|
||||||
Ok(Response::new().add_event(new_staking_address_update_event(&old, &to_address)))
|
|
||||||
} else {
|
|
||||||
Err(ContractError::NotOwner(account.owner_address().to_string()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Bond a gateway, sends [mixnet_contract_common::ExecuteMsg::BondGatewayOnBehalf] to [crate::storage::MIXNET_CONTRACT_ADDRESS].
|
|
||||||
pub fn try_bond_gateway(
|
|
||||||
gateway: Gateway,
|
|
||||||
owner_signature: MessageSignature,
|
|
||||||
amount: Coin,
|
|
||||||
info: MessageInfo,
|
|
||||||
env: Env,
|
|
||||||
deps: DepsMut<'_>,
|
|
||||||
) -> Result<Response, ContractError> {
|
|
||||||
let mix_denom = MIX_DENOM.load(deps.storage)?;
|
|
||||||
let pledge = validate_funds(&[amount], mix_denom)?;
|
|
||||||
let account = account_from_address(info.sender.as_str(), deps.storage, deps.api)?;
|
|
||||||
account.try_bond_gateway(gateway, owner_signature, pledge, &env, deps.storage)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Unbond a gateway, sends [mixnet_contract_common::ExecuteMsg::UnbondGatewayOnBehalf] to [crate::storage::MIXNET_CONTRACT_ADDRESS].
|
|
||||||
pub fn try_unbond_gateway(info: MessageInfo, deps: DepsMut<'_>) -> Result<Response, ContractError> {
|
|
||||||
let account = account_from_address(info.sender.as_str(), deps.storage, deps.api)?;
|
|
||||||
account.try_unbond_gateway(deps.storage)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Track gateway unbonding, invoked by the mixnet contract after succesful unbonding, message containes coins returned including any accrued rewards.
|
|
||||||
pub fn try_track_unbond_gateway(
|
|
||||||
owner: &str,
|
|
||||||
amount: Coin,
|
|
||||||
info: MessageInfo,
|
|
||||||
deps: DepsMut<'_>,
|
|
||||||
) -> Result<Response, ContractError> {
|
|
||||||
if info.sender != MIXNET_CONTRACT_ADDRESS.load(deps.storage)? {
|
|
||||||
return Err(ContractError::NotMixnetContract(info.sender));
|
|
||||||
}
|
|
||||||
let account = account_from_address(owner, deps.storage, deps.api)?;
|
|
||||||
account.try_track_unbond_gateway(amount, deps.storage)?;
|
|
||||||
Ok(Response::new().add_event(new_track_gateway_unbond_event()))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Bond a mixnode, sends [mixnet_contract_common::ExecuteMsg::BondMixnodeOnBehalf] to [crate::storage::MIXNET_CONTRACT_ADDRESS].
|
|
||||||
pub fn try_bond_mixnode(
|
|
||||||
mix_node: MixNode,
|
|
||||||
cost_params: MixNodeCostParams,
|
|
||||||
owner_signature: MessageSignature,
|
|
||||||
amount: Coin,
|
|
||||||
info: MessageInfo,
|
|
||||||
env: Env,
|
|
||||||
deps: DepsMut<'_>,
|
|
||||||
) -> Result<Response, ContractError> {
|
|
||||||
let mix_denom = MIX_DENOM.load(deps.storage)?;
|
|
||||||
let pledge = validate_funds(&[amount], mix_denom)?;
|
|
||||||
let account = account_from_address(info.sender.as_str(), deps.storage, deps.api)?;
|
|
||||||
account.try_bond_mixnode(
|
|
||||||
mix_node,
|
|
||||||
cost_params,
|
|
||||||
owner_signature,
|
|
||||||
pledge,
|
|
||||||
&env,
|
|
||||||
deps.storage,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn try_pledge_more(
|
|
||||||
deps: DepsMut<'_>,
|
|
||||||
env: Env,
|
|
||||||
info: MessageInfo,
|
|
||||||
amount: Coin,
|
|
||||||
) -> Result<Response, ContractError> {
|
|
||||||
let mix_denom = MIX_DENOM.load(deps.storage)?;
|
|
||||||
let additional_pledge = validate_funds(&[amount], mix_denom)?;
|
|
||||||
|
|
||||||
let account = account_from_address(info.sender.as_str(), deps.storage, deps.api)?;
|
|
||||||
account.try_pledge_additional_tokens(additional_pledge, &env, deps.storage)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Unbond a mixnode, sends [mixnet_contract_common::ExecuteMsg::UnbondMixnodeOnBehalf] to [crate::storage::MIXNET_CONTRACT_ADDRESS].
|
|
||||||
pub fn try_unbond_mixnode(info: MessageInfo, deps: DepsMut<'_>) -> Result<Response, ContractError> {
|
|
||||||
let account = account_from_address(info.sender.as_str(), deps.storage, deps.api)?;
|
|
||||||
account.try_unbond_mixnode(deps.storage)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Track mixnode unbonding, invoked by the mixnet contract after succesful unbonding, message containes coins returned including any accrued rewards.
|
|
||||||
pub fn try_track_unbond_mixnode(
|
|
||||||
owner: &str,
|
|
||||||
amount: Coin,
|
|
||||||
info: MessageInfo,
|
|
||||||
deps: DepsMut<'_>,
|
|
||||||
) -> Result<Response, ContractError> {
|
|
||||||
if info.sender != MIXNET_CONTRACT_ADDRESS.load(deps.storage)? {
|
|
||||||
return Err(ContractError::NotMixnetContract(info.sender));
|
|
||||||
}
|
|
||||||
let account = account_from_address(owner, deps.storage, deps.api)?;
|
|
||||||
account.try_track_unbond_mixnode(amount, deps.storage)?;
|
|
||||||
Ok(Response::new().add_event(new_track_mixnode_unbond_event()))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Track reward collection, invoked by the mixnert contract after sucessful reward compounding or claiming
|
|
||||||
fn try_track_reward(
|
|
||||||
deps: DepsMut<'_>,
|
|
||||||
info: MessageInfo,
|
|
||||||
amount: Coin,
|
|
||||||
address: &str,
|
|
||||||
) -> Result<Response, ContractError> {
|
|
||||||
if info.sender != MIXNET_CONTRACT_ADDRESS.load(deps.storage)? {
|
|
||||||
return Err(ContractError::NotMixnetContract(info.sender));
|
|
||||||
}
|
|
||||||
let account = account_from_address(address, deps.storage, deps.api)?;
|
|
||||||
account.track_reward(amount, deps.storage)?;
|
|
||||||
Ok(Response::new().add_event(new_track_reward_event()))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Track undelegation, invoked by the mixnet contract after sucessful undelegation, message contains coins returned with any accrued rewards.
|
|
||||||
fn try_track_undelegation(
|
|
||||||
address: &str,
|
|
||||||
mix_id: MixId,
|
|
||||||
amount: Coin,
|
|
||||||
info: MessageInfo,
|
|
||||||
deps: DepsMut<'_>,
|
|
||||||
) -> Result<Response, ContractError> {
|
|
||||||
if info.sender != MIXNET_CONTRACT_ADDRESS.load(deps.storage)? {
|
|
||||||
return Err(ContractError::NotMixnetContract(info.sender));
|
|
||||||
}
|
|
||||||
let account = account_from_address(address, deps.storage, deps.api)?;
|
|
||||||
|
|
||||||
account.track_undelegation(mix_id, amount, deps.storage)?;
|
|
||||||
Ok(Response::new().add_event(new_track_undelegation_event()))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Delegate to mixnode, sends [mixnet_contract_common::ExecuteMsg::DelegateToMixnodeOnBehalf] to [crate::storage::MIXNET_CONTRACT_ADDRESS]..
|
|
||||||
fn try_delegate_to_mixnode(
|
|
||||||
mix_id: MixId,
|
|
||||||
amount: Coin,
|
|
||||||
on_behalf_of: Option<String>,
|
|
||||||
info: MessageInfo,
|
|
||||||
env: Env,
|
|
||||||
deps: DepsMut<'_>,
|
|
||||||
) -> Result<Response, ContractError> {
|
|
||||||
// TODO
|
|
||||||
// as of 01.02.23
|
|
||||||
// thus restricting it to 25, which is more than double of that, doesn't seem too unreasonable.
|
|
||||||
|
|
||||||
// while this might not be the best workaround, if user wishes to delegate more tokens towards the same node
|
|
||||||
// they could remove the existing delegation (thus removing all separate entries from the storage)
|
|
||||||
// and re-delegate it with the reclaimed amount (which will include all rewards).
|
|
||||||
|
|
||||||
let mix_denom = MIX_DENOM.load(deps.storage)?;
|
|
||||||
let amount = validate_funds(&[amount], mix_denom)?;
|
|
||||||
|
|
||||||
let account = match on_behalf_of {
|
|
||||||
Some(account_owner) => {
|
|
||||||
let account = account_from_address(&account_owner, deps.storage, deps.api)?;
|
|
||||||
ensure_staking_permission(&info.sender, &account)?;
|
|
||||||
account
|
|
||||||
}
|
|
||||||
// you're the owner, you can do what you want
|
|
||||||
None => account_from_address(info.sender.as_str(), deps.storage, deps.api)?,
|
|
||||||
};
|
|
||||||
|
|
||||||
account.try_delegate_to_mixnode(mix_id, amount, &env, deps.storage)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Claims operator reward, sends [mixnet_contract_common::ExecuteMsg::ClaimOperatorRewardOnBehalf] to [crate::storage::MIXNET_CONTRACT_ADDRESS].
|
|
||||||
fn try_claim_operator_reward(
|
|
||||||
deps: DepsMut<'_>,
|
|
||||||
info: MessageInfo,
|
|
||||||
) -> Result<Response, ContractError> {
|
|
||||||
let account = account_from_address(info.sender.as_str(), deps.storage, deps.api)?;
|
|
||||||
account.try_claim_operator_reward(deps.storage)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Claims delegator reward, sends [mixnet_contract_common::ExecuteMsg::ClaimDelegatorRewardOnBehalf] to [crate::storage::MIXNET_CONTRACT_ADDRESS].
|
|
||||||
fn try_claim_delegator_reward(
|
|
||||||
deps: DepsMut<'_>,
|
|
||||||
info: MessageInfo,
|
|
||||||
mix_id: MixId,
|
|
||||||
) -> Result<Response, ContractError> {
|
|
||||||
let account = account_from_address(info.sender.as_str(), deps.storage, deps.api)?;
|
|
||||||
|
|
||||||
account.try_claim_delegator_reward(mix_id, deps.storage)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Undelegates from a mixnode, sends [mixnet_contract_common::ExecuteMsg::UndelegateFromMixnodeOnBehalf] to [crate::storage::MIXNET_CONTRACT_ADDRESS].
|
|
||||||
fn try_undelegate_from_mixnode(
|
|
||||||
mix_id: MixId,
|
|
||||||
on_behalf_of: Option<String>,
|
|
||||||
info: MessageInfo,
|
|
||||||
deps: DepsMut<'_>,
|
|
||||||
) -> Result<Response, ContractError> {
|
|
||||||
let account = match on_behalf_of {
|
|
||||||
Some(account_owner) => {
|
|
||||||
let account = account_from_address(&account_owner, deps.storage, deps.api)?;
|
|
||||||
ensure_staking_permission(&info.sender, &account)?;
|
|
||||||
account
|
|
||||||
}
|
|
||||||
// you're the owner, you can do what you want
|
|
||||||
None => account_from_address(info.sender.as_str(), deps.storage, deps.api)?,
|
|
||||||
};
|
|
||||||
|
|
||||||
account.try_undelegate_from_mixnode(mix_id, deps.storage)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Creates a new periodic vesting account, and deposits funds to vest into the contract.
|
|
||||||
///
|
|
||||||
/// Callable by ADMIN only, see [instantiate].
|
|
||||||
pub(crate) fn try_create_periodic_vesting_account(
|
|
||||||
owner_address: &str,
|
|
||||||
staking_address: Option<String>,
|
|
||||||
vesting_spec: Option<VestingSpecification>,
|
|
||||||
cap: Option<PledgeCap>,
|
|
||||||
info: MessageInfo,
|
|
||||||
env: Env,
|
|
||||||
deps: DepsMut<'_>,
|
|
||||||
) -> Result<Response, ContractError> {
|
|
||||||
if info.sender != ADMIN.load(deps.storage)? {
|
|
||||||
return Err(ContractError::NotAdmin(info.sender.as_str().to_string()));
|
|
||||||
}
|
|
||||||
|
|
||||||
let mix_denom = MIX_DENOM.load(deps.storage)?;
|
|
||||||
|
|
||||||
let account_exists = account_from_address(owner_address, deps.storage, deps.api).is_ok();
|
|
||||||
if account_exists {
|
|
||||||
return Err(ContractError::AccountAlreadyExists(
|
|
||||||
owner_address.to_string(),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
let vesting_spec = vesting_spec.unwrap_or_default();
|
|
||||||
|
|
||||||
let coin = validate_funds(&info.funds, mix_denom)?;
|
|
||||||
|
|
||||||
let owner_address = deps.api.addr_validate(owner_address)?;
|
|
||||||
let staking_address = if let Some(staking_address) = staking_address {
|
|
||||||
let staking_account_exists =
|
|
||||||
account_from_address(&staking_address, deps.storage, deps.api).is_ok();
|
|
||||||
if staking_account_exists {
|
|
||||||
return Err(ContractError::StakingAccountAlreadyExists(staking_address));
|
|
||||||
}
|
|
||||||
Some(deps.api.addr_validate(&staking_address)?)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
let start_time = vesting_spec
|
|
||||||
.start_time()
|
|
||||||
.unwrap_or_else(|| env.block.time.seconds());
|
|
||||||
|
|
||||||
let periods = populate_vesting_periods(start_time, vesting_spec);
|
|
||||||
|
|
||||||
let start_time = Timestamp::from_seconds(start_time);
|
|
||||||
|
|
||||||
let response = Response::new();
|
|
||||||
|
|
||||||
Account::new(
|
|
||||||
owner_address.clone(),
|
|
||||||
staking_address.clone(),
|
|
||||||
coin.clone(),
|
|
||||||
start_time,
|
|
||||||
periods,
|
|
||||||
cap,
|
|
||||||
deps.storage,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
Ok(response.add_event(new_periodic_vesting_account_event(
|
|
||||||
&owner_address,
|
|
||||||
&coin,
|
|
||||||
&staking_address,
|
|
||||||
start_time,
|
|
||||||
)))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[entry_point]
|
#[entry_point]
|
||||||
pub fn query(deps: Deps<'_>, env: Env, msg: QueryMsg) -> Result<QueryResponse, ContractError> {
|
pub fn query(deps: Deps<'_>, env: Env, msg: QueryMsg) -> Result<QueryResponse, ContractError> {
|
||||||
let query_res = match msg {
|
let query_res = match msg {
|
||||||
@@ -757,6 +286,19 @@ pub fn query(deps: Deps<'_>, env: Env, msg: QueryMsg) -> Result<QueryResponse, C
|
|||||||
QueryMsg::GetCurrentVestingPeriod { address } => {
|
QueryMsg::GetCurrentVestingPeriod { address } => {
|
||||||
to_binary(&try_get_current_vesting_period(&address, deps, env)?)
|
to_binary(&try_get_current_vesting_period(&address, deps, env)?)
|
||||||
}
|
}
|
||||||
|
QueryMsg::GetDelegation {
|
||||||
|
address,
|
||||||
|
mix_id,
|
||||||
|
block_timestamp_secs,
|
||||||
|
} => to_binary(&try_get_delegation(
|
||||||
|
deps,
|
||||||
|
&address,
|
||||||
|
mix_id,
|
||||||
|
block_timestamp_secs,
|
||||||
|
)?),
|
||||||
|
QueryMsg::GetTotalDelegationAmount { address, mix_id } => {
|
||||||
|
to_binary(&try_get_delegation_amount(deps, &address, mix_id)?)
|
||||||
|
}
|
||||||
QueryMsg::GetDelegationTimes { address, mix_id } => {
|
QueryMsg::GetDelegationTimes { address, mix_id } => {
|
||||||
to_binary(&try_get_delegation_times(deps, &address, mix_id)?)
|
to_binary(&try_get_delegation_times(deps, &address, mix_id)?)
|
||||||
}
|
}
|
||||||
@@ -768,313 +310,7 @@ pub fn query(deps: Deps<'_>, env: Env, msg: QueryMsg) -> Result<QueryResponse, C
|
|||||||
Ok(query_res?)
|
Ok(query_res?)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get current vesting period for a given [crate::vesting::Account].
|
pub(crate) fn validate_funds(funds: &[Coin], mix_denom: String) -> Result<Coin, ContractError> {
|
||||||
pub fn try_get_current_vesting_period(
|
|
||||||
address: &str,
|
|
||||||
deps: Deps<'_>,
|
|
||||||
env: Env,
|
|
||||||
) -> Result<Period, ContractError> {
|
|
||||||
let account = account_from_address(address, deps.storage, deps.api)?;
|
|
||||||
account.get_current_vesting_period(env.block.time)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Loads mixnode bond from vesting contract storage.
|
|
||||||
pub fn try_get_mixnode(address: &str, deps: Deps<'_>) -> Result<Option<PledgeData>, ContractError> {
|
|
||||||
let account = account_from_address(address, deps.storage, deps.api)?;
|
|
||||||
account.load_mixnode_pledge(deps.storage)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Loads gateway bond from vesting contract storage.
|
|
||||||
pub fn try_get_gateway(address: &str, deps: Deps<'_>) -> Result<Option<PledgeData>, ContractError> {
|
|
||||||
let account = account_from_address(address, deps.storage, deps.api)?;
|
|
||||||
account.load_gateway_pledge(deps.storage)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn try_get_account(address: &str, deps: Deps<'_>) -> Result<Account, ContractError> {
|
|
||||||
account_from_address(address, deps.storage, deps.api)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Gets build information of this contract.
|
|
||||||
pub fn get_contract_version() -> ContractBuildInformation {
|
|
||||||
// as per docs
|
|
||||||
// env! macro will expand to the value of the named environment variable at
|
|
||||||
// compile time, yielding an expression of type `&'static str`
|
|
||||||
ContractBuildInformation {
|
|
||||||
build_timestamp: env!("VERGEN_BUILD_TIMESTAMP").to_string(),
|
|
||||||
build_version: env!("VERGEN_BUILD_SEMVER").to_string(),
|
|
||||||
commit_sha: option_env!("VERGEN_GIT_SHA").unwrap_or("NONE").to_string(),
|
|
||||||
commit_timestamp: option_env!("VERGEN_GIT_COMMIT_TIMESTAMP")
|
|
||||||
.unwrap_or("NONE")
|
|
||||||
.to_string(),
|
|
||||||
commit_branch: option_env!("VERGEN_GIT_BRANCH")
|
|
||||||
.unwrap_or("NONE")
|
|
||||||
.to_string(),
|
|
||||||
rustc_version: env!("VERGEN_RUSTC_SEMVER").to_string(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn try_get_all_accounts(
|
|
||||||
deps: Deps<'_>,
|
|
||||||
start_after: Option<String>,
|
|
||||||
limit: Option<u32>,
|
|
||||||
) -> Result<AccountsResponse, ContractError> {
|
|
||||||
let limit = limit.unwrap_or(150).min(250) as usize;
|
|
||||||
|
|
||||||
let start = start_after
|
|
||||||
.map(|raw| deps.api.addr_validate(&raw).map(Bound::exclusive))
|
|
||||||
.transpose()?;
|
|
||||||
|
|
||||||
let accounts = ACCOUNTS
|
|
||||||
.range(deps.storage, start, None, Order::Ascending)
|
|
||||||
.take(limit)
|
|
||||||
.map(|res| {
|
|
||||||
res.map(|(_, account)| BaseVestingAccountInfo {
|
|
||||||
account_id: account.storage_key(),
|
|
||||||
owner: account.owner_address,
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.collect::<StdResult<Vec<_>>>()?;
|
|
||||||
|
|
||||||
let start_next_after = accounts.last().map(|acc| acc.owner.clone());
|
|
||||||
|
|
||||||
Ok(AccountsResponse {
|
|
||||||
accounts,
|
|
||||||
start_next_after,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn try_get_all_accounts_vesting_coins(
|
|
||||||
deps: Deps<'_>,
|
|
||||||
env: Env,
|
|
||||||
start_after: Option<String>,
|
|
||||||
limit: Option<u32>,
|
|
||||||
) -> Result<VestingCoinsResponse, ContractError> {
|
|
||||||
let limit = limit.unwrap_or(150).min(250) as usize;
|
|
||||||
|
|
||||||
let start = start_after
|
|
||||||
.map(|raw| deps.api.addr_validate(&raw).map(Bound::exclusive))
|
|
||||||
.transpose()?;
|
|
||||||
|
|
||||||
let accounts = ACCOUNTS
|
|
||||||
.range(deps.storage, start, None, Order::Ascending)
|
|
||||||
.take(limit)
|
|
||||||
.map(|res| {
|
|
||||||
res.map(|(_, account)| {
|
|
||||||
account
|
|
||||||
.get_vesting_coins(None, &env, deps.storage)
|
|
||||||
.map(|still_vesting| AccountVestingCoins {
|
|
||||||
account_id: account.storage_key(),
|
|
||||||
owner: account.owner_address,
|
|
||||||
still_vesting,
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.collect::<StdResult<Result<Vec<_>, _>>>()??;
|
|
||||||
|
|
||||||
let start_next_after = accounts.last().map(|acc| acc.owner.clone());
|
|
||||||
|
|
||||||
Ok(VestingCoinsResponse {
|
|
||||||
accounts,
|
|
||||||
start_next_after,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Gets currently locked coins, see [crate::traits::VestingAccount::locked_coins]
|
|
||||||
pub fn try_get_locked_coins(
|
|
||||||
vesting_account_address: &str,
|
|
||||||
block_time: Option<Timestamp>,
|
|
||||||
env: Env,
|
|
||||||
deps: Deps<'_>,
|
|
||||||
) -> Result<Coin, ContractError> {
|
|
||||||
let account = account_from_address(vesting_account_address, deps.storage, deps.api)?;
|
|
||||||
account.locked_coins(block_time, &env, deps.storage)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns currently locked coins, see [crate::traits::VestingAccount::spendable_coins]
|
|
||||||
pub fn try_get_spendable_coins(
|
|
||||||
vesting_account_address: &str,
|
|
||||||
block_time: Option<Timestamp>,
|
|
||||||
env: Env,
|
|
||||||
deps: Deps<'_>,
|
|
||||||
) -> Result<Coin, ContractError> {
|
|
||||||
let account = account_from_address(vesting_account_address, deps.storage, deps.api)?;
|
|
||||||
account.spendable_coins(block_time, &env, deps.storage)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns coins that have vested, see [crate::traits::VestingAccount::get_vested_coins]
|
|
||||||
pub fn try_get_vested_coins(
|
|
||||||
vesting_account_address: &str,
|
|
||||||
block_time: Option<Timestamp>,
|
|
||||||
env: Env,
|
|
||||||
deps: Deps<'_>,
|
|
||||||
) -> Result<Coin, ContractError> {
|
|
||||||
let account = account_from_address(vesting_account_address, deps.storage, deps.api)?;
|
|
||||||
account.get_vested_coins(block_time, &env, deps.storage)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns coins that are vesting, see [crate::traits::VestingAccount::get_vesting_coins]
|
|
||||||
pub fn try_get_vesting_coins(
|
|
||||||
vesting_account_address: &str,
|
|
||||||
block_time: Option<Timestamp>,
|
|
||||||
env: Env,
|
|
||||||
deps: Deps<'_>,
|
|
||||||
) -> Result<Coin, ContractError> {
|
|
||||||
let account = account_from_address(vesting_account_address, deps.storage, deps.api)?;
|
|
||||||
account.get_vesting_coins(block_time, &env, deps.storage)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// See [crate::traits::VestingAccount::get_start_time]
|
|
||||||
pub fn try_get_start_time(
|
|
||||||
vesting_account_address: &str,
|
|
||||||
deps: Deps<'_>,
|
|
||||||
) -> Result<Timestamp, ContractError> {
|
|
||||||
let account = account_from_address(vesting_account_address, deps.storage, deps.api)?;
|
|
||||||
Ok(account.get_start_time())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// See [crate::traits::VestingAccount::get_end_time]
|
|
||||||
pub fn try_get_end_time(
|
|
||||||
vesting_account_address: &str,
|
|
||||||
deps: Deps<'_>,
|
|
||||||
) -> Result<Timestamp, ContractError> {
|
|
||||||
let account = account_from_address(vesting_account_address, deps.storage, deps.api)?;
|
|
||||||
Ok(account.get_end_time())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// See [crate::traits::VestingAccount::get_original_vesting]
|
|
||||||
pub fn try_get_original_vesting(
|
|
||||||
vesting_account_address: &str,
|
|
||||||
deps: Deps<'_>,
|
|
||||||
) -> Result<OriginalVestingResponse, ContractError> {
|
|
||||||
let account = account_from_address(vesting_account_address, deps.storage, deps.api)?;
|
|
||||||
account.get_original_vesting()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn try_get_historical_vesting_staking_reward(
|
|
||||||
vesting_account_address: &str,
|
|
||||||
deps: Deps<'_>,
|
|
||||||
) -> Result<Coin, ContractError> {
|
|
||||||
let account = account_from_address(vesting_account_address, deps.storage, deps.api)?;
|
|
||||||
account.get_historical_vested_staking_rewards(deps.storage)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn try_get_spendable_vested_coins(
|
|
||||||
vesting_account_address: &str,
|
|
||||||
deps: Deps<'_>,
|
|
||||||
env: Env,
|
|
||||||
) -> Result<Coin, ContractError> {
|
|
||||||
let account = account_from_address(vesting_account_address, deps.storage, deps.api)?;
|
|
||||||
account.spendable_vested_coins(None, &env, deps.storage)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn try_get_spendable_reward_coins(
|
|
||||||
vesting_account_address: &str,
|
|
||||||
deps: Deps<'_>,
|
|
||||||
env: Env,
|
|
||||||
) -> Result<Coin, ContractError> {
|
|
||||||
let account = account_from_address(vesting_account_address, deps.storage, deps.api)?;
|
|
||||||
account.spendable_reward_coins(None, &env, deps.storage)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn try_get_delegated_coins(
|
|
||||||
vesting_account_address: &str,
|
|
||||||
deps: Deps<'_>,
|
|
||||||
) -> Result<Coin, ContractError> {
|
|
||||||
let account = account_from_address(vesting_account_address, deps.storage, deps.api)?;
|
|
||||||
let denom = MIX_DENOM.load(deps.storage)?;
|
|
||||||
let amount = account.total_delegations(deps.storage)?;
|
|
||||||
Ok(Coin { denom, amount })
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn try_get_pledged_coins(
|
|
||||||
vesting_account_address: &str,
|
|
||||||
deps: Deps<'_>,
|
|
||||||
) -> Result<Coin, ContractError> {
|
|
||||||
let account = account_from_address(vesting_account_address, deps.storage, deps.api)?;
|
|
||||||
let denom = MIX_DENOM.load(deps.storage)?;
|
|
||||||
let amount = account.total_pledged(deps.storage)?;
|
|
||||||
Ok(Coin { denom, amount })
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn try_get_staked_coins(
|
|
||||||
vesting_account_address: &str,
|
|
||||||
deps: Deps<'_>,
|
|
||||||
) -> Result<Coin, ContractError> {
|
|
||||||
let account = account_from_address(vesting_account_address, deps.storage, deps.api)?;
|
|
||||||
let denom = MIX_DENOM.load(deps.storage)?;
|
|
||||||
let amount = account.total_staked(deps.storage)?;
|
|
||||||
Ok(Coin { denom, amount })
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn try_get_withdrawn_coins(
|
|
||||||
vesting_account_address: &str,
|
|
||||||
deps: Deps<'_>,
|
|
||||||
) -> Result<Coin, ContractError> {
|
|
||||||
let account = account_from_address(vesting_account_address, deps.storage, deps.api)?;
|
|
||||||
let denom = MIX_DENOM.load(deps.storage)?;
|
|
||||||
let amount = account.load_withdrawn(deps.storage)?;
|
|
||||||
Ok(Coin { denom, amount })
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns timestamps at which delegations were made
|
|
||||||
pub fn try_get_delegation_times(
|
|
||||||
deps: Deps<'_>,
|
|
||||||
vesting_account_address: &str,
|
|
||||||
mix_id: MixId,
|
|
||||||
) -> Result<DelegationTimesResponse, ContractError> {
|
|
||||||
let owner = deps.api.addr_validate(vesting_account_address)?;
|
|
||||||
let account = account_from_address(vesting_account_address, deps.storage, deps.api)?;
|
|
||||||
|
|
||||||
let delegation_timestamps = DELEGATIONS
|
|
||||||
.prefix((account.storage_key(), mix_id))
|
|
||||||
.keys(deps.storage, None, None, Order::Ascending)
|
|
||||||
.collect::<StdResult<Vec<_>>>()?;
|
|
||||||
|
|
||||||
Ok(DelegationTimesResponse {
|
|
||||||
owner,
|
|
||||||
account_id: account.storage_key(),
|
|
||||||
mix_id,
|
|
||||||
delegation_timestamps,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn try_get_all_delegations(
|
|
||||||
deps: Deps<'_>,
|
|
||||||
start_after: Option<(u32, MixId, BlockTimestampSecs)>,
|
|
||||||
limit: Option<u32>,
|
|
||||||
) -> Result<AllDelegationsResponse, ContractError> {
|
|
||||||
let limit = limit.unwrap_or(100).min(200) as usize;
|
|
||||||
|
|
||||||
let start = start_after.map(Bound::exclusive);
|
|
||||||
let delegations = DELEGATIONS
|
|
||||||
.range(deps.storage, start, None, Order::Ascending)
|
|
||||||
.map(|kv| {
|
|
||||||
kv.map(
|
|
||||||
|((account_id, mix_id, block_timestamp), amount)| VestingDelegation {
|
|
||||||
account_id,
|
|
||||||
mix_id,
|
|
||||||
block_timestamp,
|
|
||||||
amount,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.collect::<StdResult<Vec<_>>>()?;
|
|
||||||
|
|
||||||
let start_next_after = if delegations.len() < limit {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
delegations
|
|
||||||
.last()
|
|
||||||
.map(|delegation| delegation.storage_key())
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(AllDelegationsResponse {
|
|
||||||
delegations,
|
|
||||||
start_next_after,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn validate_funds(funds: &[Coin], mix_denom: String) -> Result<Coin, ContractError> {
|
|
||||||
if funds.is_empty() || funds[0].amount.is_zero() {
|
if funds.is_empty() || funds[0].amount.is_zero() {
|
||||||
return Err(ContractError::EmptyFunds);
|
return Err(ContractError::EmptyFunds);
|
||||||
}
|
}
|
||||||
@@ -1090,7 +326,10 @@ fn validate_funds(funds: &[Coin], mix_denom: String) -> Result<Coin, ContractErr
|
|||||||
Ok(funds[0].clone())
|
Ok(funds[0].clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn ensure_staking_permission(addr: &Addr, account: &Account) -> Result<(), ContractError> {
|
pub(crate) fn ensure_staking_permission(
|
||||||
|
addr: &Addr,
|
||||||
|
account: &Account,
|
||||||
|
) -> Result<(), ContractError> {
|
||||||
if let Some(staking_address) = account.staking_address() {
|
if let Some(staking_address) = account.staking_address() {
|
||||||
if staking_address == addr {
|
if staking_address == addr {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
|
|||||||
@@ -6,8 +6,10 @@
|
|||||||
|
|
||||||
pub mod contract;
|
pub mod contract;
|
||||||
mod errors;
|
mod errors;
|
||||||
|
mod queries;
|
||||||
mod queued_migrations;
|
mod queued_migrations;
|
||||||
mod storage;
|
mod storage;
|
||||||
mod support;
|
mod support;
|
||||||
mod traits;
|
mod traits;
|
||||||
|
mod transactions;
|
||||||
pub mod vesting;
|
pub mod vesting;
|
||||||
|
|||||||
@@ -0,0 +1,357 @@
|
|||||||
|
// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
use crate::errors::ContractError;
|
||||||
|
use crate::storage;
|
||||||
|
use crate::storage::{account_from_address, BlockTimestampSecs, ACCOUNTS, DELEGATIONS, MIX_DENOM};
|
||||||
|
use crate::traits::VestingAccount;
|
||||||
|
use crate::vesting::Account;
|
||||||
|
use contracts_common::ContractBuildInformation;
|
||||||
|
use cosmwasm_std::{Coin, Deps, Env, Order, StdResult, Timestamp, Uint128};
|
||||||
|
use cw_storage_plus::Bound;
|
||||||
|
use mixnet_contract_common::MixId;
|
||||||
|
use vesting_contract_common::{
|
||||||
|
AccountVestingCoins, AccountsResponse, AllDelegationsResponse, BaseVestingAccountInfo,
|
||||||
|
DelegationTimesResponse, OriginalVestingResponse, Period, PledgeData, VestingCoinsResponse,
|
||||||
|
VestingDelegation,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Get current vesting period for a given [crate::vesting::Account].
|
||||||
|
pub fn try_get_current_vesting_period(
|
||||||
|
address: &str,
|
||||||
|
deps: Deps<'_>,
|
||||||
|
env: Env,
|
||||||
|
) -> Result<Period, ContractError> {
|
||||||
|
let account = account_from_address(address, deps.storage, deps.api)?;
|
||||||
|
account.get_current_vesting_period(env.block.time)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Loads mixnode bond from vesting contract storage.
|
||||||
|
pub fn try_get_mixnode(address: &str, deps: Deps<'_>) -> Result<Option<PledgeData>, ContractError> {
|
||||||
|
let account = account_from_address(address, deps.storage, deps.api)?;
|
||||||
|
account.load_mixnode_pledge(deps.storage)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Loads gateway bond from vesting contract storage.
|
||||||
|
pub fn try_get_gateway(address: &str, deps: Deps<'_>) -> Result<Option<PledgeData>, ContractError> {
|
||||||
|
let account = account_from_address(address, deps.storage, deps.api)?;
|
||||||
|
account.load_gateway_pledge(deps.storage)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn try_get_account(address: &str, deps: Deps<'_>) -> Result<Account, ContractError> {
|
||||||
|
account_from_address(address, deps.storage, deps.api)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets build information of this contract.
|
||||||
|
pub fn get_contract_version() -> ContractBuildInformation {
|
||||||
|
// as per docs
|
||||||
|
// env! macro will expand to the value of the named environment variable at
|
||||||
|
// compile time, yielding an expression of type `&'static str`
|
||||||
|
ContractBuildInformation {
|
||||||
|
build_timestamp: env!("VERGEN_BUILD_TIMESTAMP").to_string(),
|
||||||
|
build_version: env!("VERGEN_BUILD_SEMVER").to_string(),
|
||||||
|
commit_sha: option_env!("VERGEN_GIT_SHA").unwrap_or("NONE").to_string(),
|
||||||
|
commit_timestamp: option_env!("VERGEN_GIT_COMMIT_TIMESTAMP")
|
||||||
|
.unwrap_or("NONE")
|
||||||
|
.to_string(),
|
||||||
|
commit_branch: option_env!("VERGEN_GIT_BRANCH")
|
||||||
|
.unwrap_or("NONE")
|
||||||
|
.to_string(),
|
||||||
|
rustc_version: env!("VERGEN_RUSTC_SEMVER").to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn try_get_all_accounts(
|
||||||
|
deps: Deps<'_>,
|
||||||
|
start_after: Option<String>,
|
||||||
|
limit: Option<u32>,
|
||||||
|
) -> Result<AccountsResponse, ContractError> {
|
||||||
|
let limit = limit.unwrap_or(150).min(250) as usize;
|
||||||
|
|
||||||
|
let start = start_after
|
||||||
|
.map(|raw| deps.api.addr_validate(&raw).map(Bound::exclusive))
|
||||||
|
.transpose()?;
|
||||||
|
|
||||||
|
let accounts = ACCOUNTS
|
||||||
|
.range(deps.storage, start, None, Order::Ascending)
|
||||||
|
.take(limit)
|
||||||
|
.map(|res| {
|
||||||
|
res.map(|(_, account)| BaseVestingAccountInfo {
|
||||||
|
account_id: account.storage_key(),
|
||||||
|
owner: account.owner_address,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect::<StdResult<Vec<_>>>()?;
|
||||||
|
|
||||||
|
let start_next_after = accounts.last().map(|acc| acc.owner.clone());
|
||||||
|
|
||||||
|
Ok(AccountsResponse {
|
||||||
|
accounts,
|
||||||
|
start_next_after,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn try_get_all_accounts_vesting_coins(
|
||||||
|
deps: Deps<'_>,
|
||||||
|
env: Env,
|
||||||
|
start_after: Option<String>,
|
||||||
|
limit: Option<u32>,
|
||||||
|
) -> Result<VestingCoinsResponse, ContractError> {
|
||||||
|
let limit = limit.unwrap_or(150).min(250) as usize;
|
||||||
|
|
||||||
|
let start = start_after
|
||||||
|
.map(|raw| deps.api.addr_validate(&raw).map(Bound::exclusive))
|
||||||
|
.transpose()?;
|
||||||
|
|
||||||
|
let accounts = ACCOUNTS
|
||||||
|
.range(deps.storage, start, None, Order::Ascending)
|
||||||
|
.take(limit)
|
||||||
|
.map(|res| {
|
||||||
|
res.map(|(_, account)| {
|
||||||
|
account
|
||||||
|
.get_vesting_coins(None, &env, deps.storage)
|
||||||
|
.map(|still_vesting| AccountVestingCoins {
|
||||||
|
account_id: account.storage_key(),
|
||||||
|
owner: account.owner_address,
|
||||||
|
still_vesting,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect::<StdResult<Result<Vec<_>, _>>>()??;
|
||||||
|
|
||||||
|
let start_next_after = accounts.last().map(|acc| acc.owner.clone());
|
||||||
|
|
||||||
|
Ok(VestingCoinsResponse {
|
||||||
|
accounts,
|
||||||
|
start_next_after,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets currently locked coins, see [crate::traits::VestingAccount::locked_coins]
|
||||||
|
pub fn try_get_locked_coins(
|
||||||
|
vesting_account_address: &str,
|
||||||
|
block_time: Option<Timestamp>,
|
||||||
|
env: Env,
|
||||||
|
deps: Deps<'_>,
|
||||||
|
) -> Result<Coin, ContractError> {
|
||||||
|
let account = account_from_address(vesting_account_address, deps.storage, deps.api)?;
|
||||||
|
account.locked_coins(block_time, &env, deps.storage)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns currently locked coins, see [crate::traits::VestingAccount::spendable_coins]
|
||||||
|
pub fn try_get_spendable_coins(
|
||||||
|
vesting_account_address: &str,
|
||||||
|
block_time: Option<Timestamp>,
|
||||||
|
env: Env,
|
||||||
|
deps: Deps<'_>,
|
||||||
|
) -> Result<Coin, ContractError> {
|
||||||
|
let account = account_from_address(vesting_account_address, deps.storage, deps.api)?;
|
||||||
|
account.spendable_coins(block_time, &env, deps.storage)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns coins that have vested, see [crate::traits::VestingAccount::get_vested_coins]
|
||||||
|
pub fn try_get_vested_coins(
|
||||||
|
vesting_account_address: &str,
|
||||||
|
block_time: Option<Timestamp>,
|
||||||
|
env: Env,
|
||||||
|
deps: Deps<'_>,
|
||||||
|
) -> Result<Coin, ContractError> {
|
||||||
|
let account = account_from_address(vesting_account_address, deps.storage, deps.api)?;
|
||||||
|
account.get_vested_coins(block_time, &env, deps.storage)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns coins that are vesting, see [crate::traits::VestingAccount::get_vesting_coins]
|
||||||
|
pub fn try_get_vesting_coins(
|
||||||
|
vesting_account_address: &str,
|
||||||
|
block_time: Option<Timestamp>,
|
||||||
|
env: Env,
|
||||||
|
deps: Deps<'_>,
|
||||||
|
) -> Result<Coin, ContractError> {
|
||||||
|
let account = account_from_address(vesting_account_address, deps.storage, deps.api)?;
|
||||||
|
account.get_vesting_coins(block_time, &env, deps.storage)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// See [crate::traits::VestingAccount::get_start_time]
|
||||||
|
pub fn try_get_start_time(
|
||||||
|
vesting_account_address: &str,
|
||||||
|
deps: Deps<'_>,
|
||||||
|
) -> Result<Timestamp, ContractError> {
|
||||||
|
let account = account_from_address(vesting_account_address, deps.storage, deps.api)?;
|
||||||
|
Ok(account.get_start_time())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// See [crate::traits::VestingAccount::get_end_time]
|
||||||
|
pub fn try_get_end_time(
|
||||||
|
vesting_account_address: &str,
|
||||||
|
deps: Deps<'_>,
|
||||||
|
) -> Result<Timestamp, ContractError> {
|
||||||
|
let account = account_from_address(vesting_account_address, deps.storage, deps.api)?;
|
||||||
|
Ok(account.get_end_time())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// See [crate::traits::VestingAccount::get_original_vesting]
|
||||||
|
pub fn try_get_original_vesting(
|
||||||
|
vesting_account_address: &str,
|
||||||
|
deps: Deps<'_>,
|
||||||
|
) -> Result<OriginalVestingResponse, ContractError> {
|
||||||
|
let account = account_from_address(vesting_account_address, deps.storage, deps.api)?;
|
||||||
|
account.get_original_vesting()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn try_get_historical_vesting_staking_reward(
|
||||||
|
vesting_account_address: &str,
|
||||||
|
deps: Deps<'_>,
|
||||||
|
) -> Result<Coin, ContractError> {
|
||||||
|
let account = account_from_address(vesting_account_address, deps.storage, deps.api)?;
|
||||||
|
account.get_historical_vested_staking_rewards(deps.storage)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn try_get_spendable_vested_coins(
|
||||||
|
vesting_account_address: &str,
|
||||||
|
deps: Deps<'_>,
|
||||||
|
env: Env,
|
||||||
|
) -> Result<Coin, ContractError> {
|
||||||
|
let account = account_from_address(vesting_account_address, deps.storage, deps.api)?;
|
||||||
|
account.spendable_vested_coins(None, &env, deps.storage)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn try_get_spendable_reward_coins(
|
||||||
|
vesting_account_address: &str,
|
||||||
|
deps: Deps<'_>,
|
||||||
|
env: Env,
|
||||||
|
) -> Result<Coin, ContractError> {
|
||||||
|
let account = account_from_address(vesting_account_address, deps.storage, deps.api)?;
|
||||||
|
account.spendable_reward_coins(None, &env, deps.storage)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn try_get_delegated_coins(
|
||||||
|
vesting_account_address: &str,
|
||||||
|
deps: Deps<'_>,
|
||||||
|
) -> Result<Coin, ContractError> {
|
||||||
|
let account = account_from_address(vesting_account_address, deps.storage, deps.api)?;
|
||||||
|
let denom = MIX_DENOM.load(deps.storage)?;
|
||||||
|
let amount = account.total_delegations(deps.storage)?;
|
||||||
|
Ok(Coin { denom, amount })
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn try_get_pledged_coins(
|
||||||
|
vesting_account_address: &str,
|
||||||
|
deps: Deps<'_>,
|
||||||
|
) -> Result<Coin, ContractError> {
|
||||||
|
let account = account_from_address(vesting_account_address, deps.storage, deps.api)?;
|
||||||
|
let denom = MIX_DENOM.load(deps.storage)?;
|
||||||
|
let amount = account.total_pledged(deps.storage)?;
|
||||||
|
Ok(Coin { denom, amount })
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn try_get_staked_coins(
|
||||||
|
vesting_account_address: &str,
|
||||||
|
deps: Deps<'_>,
|
||||||
|
) -> Result<Coin, ContractError> {
|
||||||
|
let account = account_from_address(vesting_account_address, deps.storage, deps.api)?;
|
||||||
|
let denom = MIX_DENOM.load(deps.storage)?;
|
||||||
|
let amount = account.total_staked(deps.storage)?;
|
||||||
|
Ok(Coin { denom, amount })
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn try_get_withdrawn_coins(
|
||||||
|
vesting_account_address: &str,
|
||||||
|
deps: Deps<'_>,
|
||||||
|
) -> Result<Coin, ContractError> {
|
||||||
|
let account = account_from_address(vesting_account_address, deps.storage, deps.api)?;
|
||||||
|
let denom = MIX_DENOM.load(deps.storage)?;
|
||||||
|
let amount = account.load_withdrawn(deps.storage)?;
|
||||||
|
Ok(Coin { denom, amount })
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns timestamps at which delegations were made
|
||||||
|
pub fn try_get_delegation_times(
|
||||||
|
deps: Deps<'_>,
|
||||||
|
vesting_account_address: &str,
|
||||||
|
mix_id: MixId,
|
||||||
|
) -> Result<DelegationTimesResponse, ContractError> {
|
||||||
|
let owner = deps.api.addr_validate(vesting_account_address)?;
|
||||||
|
let account = account_from_address(vesting_account_address, deps.storage, deps.api)?;
|
||||||
|
|
||||||
|
let delegation_timestamps =
|
||||||
|
storage::load_delegation_timestamps((account.storage_key(), mix_id), deps.storage)?;
|
||||||
|
|
||||||
|
Ok(DelegationTimesResponse {
|
||||||
|
owner,
|
||||||
|
account_id: account.storage_key(),
|
||||||
|
mix_id,
|
||||||
|
delegation_timestamps,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn try_get_all_delegations(
|
||||||
|
deps: Deps<'_>,
|
||||||
|
start_after: Option<(u32, MixId, BlockTimestampSecs)>,
|
||||||
|
limit: Option<u32>,
|
||||||
|
) -> Result<AllDelegationsResponse, ContractError> {
|
||||||
|
let limit = limit.unwrap_or(100).min(200) as usize;
|
||||||
|
|
||||||
|
let start = start_after.map(Bound::exclusive);
|
||||||
|
let delegations = DELEGATIONS
|
||||||
|
.range(deps.storage, start, None, Order::Ascending)
|
||||||
|
.map(|kv| {
|
||||||
|
kv.map(
|
||||||
|
|((account_id, mix_id, block_timestamp), amount)| VestingDelegation {
|
||||||
|
account_id,
|
||||||
|
mix_id,
|
||||||
|
block_timestamp,
|
||||||
|
amount,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect::<StdResult<Vec<_>>>()?;
|
||||||
|
|
||||||
|
let start_next_after = if delegations.len() < limit {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
delegations
|
||||||
|
.last()
|
||||||
|
.map(|delegation| delegation.storage_key())
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(AllDelegationsResponse {
|
||||||
|
delegations,
|
||||||
|
start_next_after,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn try_get_delegation(
|
||||||
|
deps: Deps<'_>,
|
||||||
|
vesting_account_address: &str,
|
||||||
|
mix_id: MixId,
|
||||||
|
block_timestamp_secs: BlockTimestampSecs,
|
||||||
|
) -> Result<VestingDelegation, ContractError> {
|
||||||
|
let account = account_from_address(vesting_account_address, deps.storage, deps.api)?;
|
||||||
|
|
||||||
|
let storage_key = (account.storage_key(), mix_id, block_timestamp_secs);
|
||||||
|
let delegation_amount = DELEGATIONS.load(deps.storage, storage_key)?;
|
||||||
|
|
||||||
|
Ok(VestingDelegation {
|
||||||
|
account_id: account.storage_key(),
|
||||||
|
mix_id,
|
||||||
|
block_timestamp: block_timestamp_secs,
|
||||||
|
amount: delegation_amount,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn try_get_delegation_amount(
|
||||||
|
deps: Deps<'_>,
|
||||||
|
vesting_account_address: &str,
|
||||||
|
mix_id: MixId,
|
||||||
|
) -> Result<Coin, ContractError> {
|
||||||
|
let account = account_from_address(vesting_account_address, deps.storage, deps.api)?;
|
||||||
|
|
||||||
|
let amount = DELEGATIONS
|
||||||
|
.prefix((account.storage_key(), mix_id))
|
||||||
|
.range(deps.storage, None, None, Order::Ascending)
|
||||||
|
.map(|kv_res| kv_res.map(|kv| kv.1))
|
||||||
|
.try_fold(Uint128::zero(), |acc, x_res| x_res.map(|x| x + acc))?;
|
||||||
|
let denom = MIX_DENOM.load(deps.storage)?;
|
||||||
|
|
||||||
|
Ok(Coin { denom, amount })
|
||||||
|
}
|
||||||
@@ -0,0 +1,473 @@
|
|||||||
|
// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
use crate::contract::{ensure_staking_permission, validate_funds};
|
||||||
|
use crate::errors::ContractError;
|
||||||
|
use crate::storage::{
|
||||||
|
account_from_address, save_account, ADMIN, MIXNET_CONTRACT_ADDRESS, MIX_DENOM,
|
||||||
|
};
|
||||||
|
use crate::traits::{
|
||||||
|
DelegatingAccount, GatewayBondingAccount, MixnodeBondingAccount, NodeFamilies, VestingAccount,
|
||||||
|
};
|
||||||
|
use crate::vesting::{populate_vesting_periods, Account};
|
||||||
|
use contracts_common::signing::MessageSignature;
|
||||||
|
use cosmwasm_std::{coin, BankMsg, Coin, DepsMut, Env, MessageInfo, Response, Timestamp};
|
||||||
|
use mixnet_contract_common::families::FamilyHead;
|
||||||
|
use mixnet_contract_common::{
|
||||||
|
Gateway, GatewayConfigUpdate, MixId, MixNode, MixNodeConfigUpdate, MixNodeCostParams,
|
||||||
|
};
|
||||||
|
use vesting_contract_common::events::{
|
||||||
|
new_ownership_transfer_event, new_periodic_vesting_account_event,
|
||||||
|
new_staking_address_update_event, new_track_gateway_unbond_event,
|
||||||
|
new_track_mixnode_unbond_event, new_track_reward_event, new_track_undelegation_event,
|
||||||
|
new_vested_coins_withdraw_event,
|
||||||
|
};
|
||||||
|
use vesting_contract_common::messages::VestingSpecification;
|
||||||
|
use vesting_contract_common::PledgeCap;
|
||||||
|
|
||||||
|
pub fn try_create_family(
|
||||||
|
info: MessageInfo,
|
||||||
|
deps: DepsMut,
|
||||||
|
label: String,
|
||||||
|
) -> Result<Response, ContractError> {
|
||||||
|
let account = account_from_address(info.sender.as_ref(), deps.storage, deps.api)?;
|
||||||
|
account.try_create_family(deps.storage, label)
|
||||||
|
}
|
||||||
|
pub fn try_join_family(
|
||||||
|
info: MessageInfo,
|
||||||
|
deps: DepsMut,
|
||||||
|
join_permit: MessageSignature,
|
||||||
|
family_head: FamilyHead,
|
||||||
|
) -> Result<Response, ContractError> {
|
||||||
|
let account = account_from_address(info.sender.as_ref(), deps.storage, deps.api)?;
|
||||||
|
account.try_join_family(deps.storage, join_permit, family_head)
|
||||||
|
}
|
||||||
|
pub fn try_leave_family(
|
||||||
|
info: MessageInfo,
|
||||||
|
deps: DepsMut,
|
||||||
|
family_head: FamilyHead,
|
||||||
|
) -> Result<Response, ContractError> {
|
||||||
|
let account = account_from_address(info.sender.as_ref(), deps.storage, deps.api)?;
|
||||||
|
account.try_leave_family(deps.storage, family_head)
|
||||||
|
}
|
||||||
|
pub fn try_kick_family_member(
|
||||||
|
info: MessageInfo,
|
||||||
|
deps: DepsMut,
|
||||||
|
member: String,
|
||||||
|
) -> Result<Response, ContractError> {
|
||||||
|
let account = account_from_address(info.sender.as_ref(), deps.storage, deps.api)?;
|
||||||
|
account.try_head_kick_member(deps.storage, &member)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Update locked_pledge_cap, the hard cap for staking/bonding with unvested tokens.
|
||||||
|
///
|
||||||
|
/// Callable by ADMIN only, see [instantiate].
|
||||||
|
pub fn try_update_locked_pledge_cap(
|
||||||
|
address: String,
|
||||||
|
cap: PledgeCap,
|
||||||
|
info: MessageInfo,
|
||||||
|
deps: DepsMut,
|
||||||
|
) -> Result<Response, ContractError> {
|
||||||
|
if info.sender != ADMIN.load(deps.storage)? {
|
||||||
|
return Err(ContractError::NotAdmin(info.sender.as_str().to_string()));
|
||||||
|
}
|
||||||
|
let mut account = account_from_address(&address, deps.storage, deps.api)?;
|
||||||
|
|
||||||
|
account.pledge_cap = Some(cap);
|
||||||
|
save_account(&account, deps.storage)?;
|
||||||
|
Ok(Response::default())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Update config for a mixnode bonded with vesting account, sends [mixnet_contract_common::ExecuteMsg::UpdateMixnodeConfig] to [crate::storage::MIXNET_CONTRACT_ADDRESS].
|
||||||
|
pub fn try_update_mixnode_config(
|
||||||
|
new_config: MixNodeConfigUpdate,
|
||||||
|
info: MessageInfo,
|
||||||
|
deps: DepsMut,
|
||||||
|
) -> Result<Response, ContractError> {
|
||||||
|
let account = account_from_address(info.sender.as_str(), deps.storage, deps.api)?;
|
||||||
|
account.try_update_mixnode_config(new_config, deps.storage)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn try_update_gateway_config(
|
||||||
|
new_config: GatewayConfigUpdate,
|
||||||
|
info: MessageInfo,
|
||||||
|
deps: DepsMut,
|
||||||
|
) -> Result<Response, ContractError> {
|
||||||
|
let account = account_from_address(info.sender.as_str(), deps.storage, deps.api)?;
|
||||||
|
account.try_update_gateway_config(new_config, deps.storage)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn try_update_mixnode_cost_params(
|
||||||
|
new_costs: MixNodeCostParams,
|
||||||
|
info: MessageInfo,
|
||||||
|
deps: DepsMut,
|
||||||
|
) -> Result<Response, ContractError> {
|
||||||
|
let account = account_from_address(info.sender.as_str(), deps.storage, deps.api)?;
|
||||||
|
account.try_update_mixnode_cost_params(new_costs, deps.storage)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Updates mixnet contract address, for cases when a new mixnet contract is deployed.
|
||||||
|
///
|
||||||
|
/// Callable by ADMIN only, see [instantiate].
|
||||||
|
pub fn try_update_mixnet_address(
|
||||||
|
address: String,
|
||||||
|
info: MessageInfo,
|
||||||
|
deps: DepsMut<'_>,
|
||||||
|
) -> Result<Response, ContractError> {
|
||||||
|
if info.sender != ADMIN.load(deps.storage)? {
|
||||||
|
return Err(ContractError::NotAdmin(info.sender.as_str().to_string()));
|
||||||
|
}
|
||||||
|
let mixnet_contract_address = deps.api.addr_validate(&address)?;
|
||||||
|
|
||||||
|
MIXNET_CONTRACT_ADDRESS.save(deps.storage, &mixnet_contract_address)?;
|
||||||
|
Ok(Response::default())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Withdraw already vested coins.
|
||||||
|
pub fn try_withdraw_vested_coins(
|
||||||
|
amount: Coin,
|
||||||
|
env: Env,
|
||||||
|
info: MessageInfo,
|
||||||
|
deps: DepsMut<'_>,
|
||||||
|
) -> Result<Response, ContractError> {
|
||||||
|
let mix_denom = MIX_DENOM.load(deps.storage)?;
|
||||||
|
if amount.denom != mix_denom {
|
||||||
|
return Err(ContractError::WrongDenom(amount.denom, mix_denom));
|
||||||
|
}
|
||||||
|
|
||||||
|
let address = info.sender.clone();
|
||||||
|
let account = account_from_address(info.sender.as_str(), deps.storage, deps.api)?;
|
||||||
|
if address != account.owner_address() {
|
||||||
|
return Err(ContractError::NotOwner(account.owner_address().to_string()));
|
||||||
|
}
|
||||||
|
let spendable_coins = account.spendable_coins(None, &env, deps.storage)?;
|
||||||
|
if amount.amount <= spendable_coins.amount {
|
||||||
|
let new_balance = account.withdraw(&amount, deps.storage)?;
|
||||||
|
|
||||||
|
let send_tokens = BankMsg::Send {
|
||||||
|
to_address: account.owner_address().as_str().to_string(),
|
||||||
|
amount: vec![amount.clone()],
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Response::new()
|
||||||
|
.add_message(send_tokens)
|
||||||
|
.add_event(new_vested_coins_withdraw_event(
|
||||||
|
&address,
|
||||||
|
&amount,
|
||||||
|
&coin(new_balance, &amount.denom),
|
||||||
|
)))
|
||||||
|
} else {
|
||||||
|
Err(ContractError::InsufficientSpendable(
|
||||||
|
account.owner_address().as_str().to_string(),
|
||||||
|
spendable_coins.amount.u128(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Transfer ownership of the entire vesting account.
|
||||||
|
pub fn try_transfer_ownership(
|
||||||
|
to_address: String,
|
||||||
|
info: MessageInfo,
|
||||||
|
deps: DepsMut<'_>,
|
||||||
|
) -> Result<Response, ContractError> {
|
||||||
|
let address = info.sender.clone();
|
||||||
|
let to_address = deps.api.addr_validate(&to_address)?;
|
||||||
|
let mut account = account_from_address(info.sender.as_str(), deps.storage, deps.api)?;
|
||||||
|
if address == account.owner_address() {
|
||||||
|
account.transfer_ownership(&to_address, deps.storage)?;
|
||||||
|
Ok(Response::new().add_event(new_ownership_transfer_event(&address, &to_address)))
|
||||||
|
} else {
|
||||||
|
Err(ContractError::NotOwner(account.owner_address().to_string()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set or update staking address for a vesting account.
|
||||||
|
pub fn try_update_staking_address(
|
||||||
|
to_address: Option<String>,
|
||||||
|
info: MessageInfo,
|
||||||
|
deps: DepsMut<'_>,
|
||||||
|
) -> Result<Response, ContractError> {
|
||||||
|
if let Some(ref to_address) = to_address {
|
||||||
|
if account_from_address(to_address, deps.storage, deps.api).is_ok() {
|
||||||
|
// do not allow setting staking address to an existing account's address
|
||||||
|
return Err(ContractError::StakingAccountExists(to_address.to_string()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let address = info.sender.clone();
|
||||||
|
let to_address = to_address.and_then(|x| deps.api.addr_validate(&x).ok());
|
||||||
|
let mut account = account_from_address(info.sender.as_str(), deps.storage, deps.api)?;
|
||||||
|
if address == account.owner_address() {
|
||||||
|
let old = account.staking_address().cloned();
|
||||||
|
account.update_staking_address(to_address.clone(), deps.storage)?;
|
||||||
|
Ok(Response::new().add_event(new_staking_address_update_event(&old, &to_address)))
|
||||||
|
} else {
|
||||||
|
Err(ContractError::NotOwner(account.owner_address().to_string()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Bond a gateway, sends [mixnet_contract_common::ExecuteMsg::BondGatewayOnBehalf] to [crate::storage::MIXNET_CONTRACT_ADDRESS].
|
||||||
|
pub fn try_bond_gateway(
|
||||||
|
gateway: Gateway,
|
||||||
|
owner_signature: MessageSignature,
|
||||||
|
amount: Coin,
|
||||||
|
info: MessageInfo,
|
||||||
|
env: Env,
|
||||||
|
deps: DepsMut<'_>,
|
||||||
|
) -> Result<Response, ContractError> {
|
||||||
|
let mix_denom = MIX_DENOM.load(deps.storage)?;
|
||||||
|
let pledge = validate_funds(&[amount], mix_denom)?;
|
||||||
|
let account = account_from_address(info.sender.as_str(), deps.storage, deps.api)?;
|
||||||
|
account.try_bond_gateway(gateway, owner_signature, pledge, &env, deps.storage)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Unbond a gateway, sends [mixnet_contract_common::ExecuteMsg::UnbondGatewayOnBehalf] to [crate::storage::MIXNET_CONTRACT_ADDRESS].
|
||||||
|
pub fn try_unbond_gateway(info: MessageInfo, deps: DepsMut<'_>) -> Result<Response, ContractError> {
|
||||||
|
let account = account_from_address(info.sender.as_str(), deps.storage, deps.api)?;
|
||||||
|
account.try_unbond_gateway(deps.storage)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Track gateway unbonding, invoked by the mixnet contract after succesful unbonding, message containes coins returned including any accrued rewards.
|
||||||
|
pub fn try_track_unbond_gateway(
|
||||||
|
owner: &str,
|
||||||
|
amount: Coin,
|
||||||
|
info: MessageInfo,
|
||||||
|
deps: DepsMut<'_>,
|
||||||
|
) -> Result<Response, ContractError> {
|
||||||
|
if info.sender != MIXNET_CONTRACT_ADDRESS.load(deps.storage)? {
|
||||||
|
return Err(ContractError::NotMixnetContract(info.sender));
|
||||||
|
}
|
||||||
|
let account = account_from_address(owner, deps.storage, deps.api)?;
|
||||||
|
account.try_track_unbond_gateway(amount, deps.storage)?;
|
||||||
|
Ok(Response::new().add_event(new_track_gateway_unbond_event()))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Bond a mixnode, sends [mixnet_contract_common::ExecuteMsg::BondMixnodeOnBehalf] to [crate::storage::MIXNET_CONTRACT_ADDRESS].
|
||||||
|
pub fn try_bond_mixnode(
|
||||||
|
mix_node: MixNode,
|
||||||
|
cost_params: MixNodeCostParams,
|
||||||
|
owner_signature: MessageSignature,
|
||||||
|
amount: Coin,
|
||||||
|
info: MessageInfo,
|
||||||
|
env: Env,
|
||||||
|
deps: DepsMut<'_>,
|
||||||
|
) -> Result<Response, ContractError> {
|
||||||
|
let mix_denom = MIX_DENOM.load(deps.storage)?;
|
||||||
|
let pledge = validate_funds(&[amount], mix_denom)?;
|
||||||
|
let account = account_from_address(info.sender.as_str(), deps.storage, deps.api)?;
|
||||||
|
account.try_bond_mixnode(
|
||||||
|
mix_node,
|
||||||
|
cost_params,
|
||||||
|
owner_signature,
|
||||||
|
pledge,
|
||||||
|
&env,
|
||||||
|
deps.storage,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn try_pledge_more(
|
||||||
|
deps: DepsMut<'_>,
|
||||||
|
env: Env,
|
||||||
|
info: MessageInfo,
|
||||||
|
amount: Coin,
|
||||||
|
) -> Result<Response, ContractError> {
|
||||||
|
let mix_denom = MIX_DENOM.load(deps.storage)?;
|
||||||
|
let additional_pledge = validate_funds(&[amount], mix_denom)?;
|
||||||
|
|
||||||
|
let account = account_from_address(info.sender.as_str(), deps.storage, deps.api)?;
|
||||||
|
account.try_pledge_additional_tokens(additional_pledge, &env, deps.storage)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Unbond a mixnode, sends [mixnet_contract_common::ExecuteMsg::UnbondMixnodeOnBehalf] to [crate::storage::MIXNET_CONTRACT_ADDRESS].
|
||||||
|
pub fn try_unbond_mixnode(info: MessageInfo, deps: DepsMut<'_>) -> Result<Response, ContractError> {
|
||||||
|
let account = account_from_address(info.sender.as_str(), deps.storage, deps.api)?;
|
||||||
|
account.try_unbond_mixnode(deps.storage)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Track mixnode unbonding, invoked by the mixnet contract after succesful unbonding, message containes coins returned including any accrued rewards.
|
||||||
|
pub fn try_track_unbond_mixnode(
|
||||||
|
owner: &str,
|
||||||
|
amount: Coin,
|
||||||
|
info: MessageInfo,
|
||||||
|
deps: DepsMut<'_>,
|
||||||
|
) -> Result<Response, ContractError> {
|
||||||
|
if info.sender != MIXNET_CONTRACT_ADDRESS.load(deps.storage)? {
|
||||||
|
return Err(ContractError::NotMixnetContract(info.sender));
|
||||||
|
}
|
||||||
|
let account = account_from_address(owner, deps.storage, deps.api)?;
|
||||||
|
account.try_track_unbond_mixnode(amount, deps.storage)?;
|
||||||
|
Ok(Response::new().add_event(new_track_mixnode_unbond_event()))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Track reward collection, invoked by the mixnert contract after sucessful reward compounding or claiming
|
||||||
|
pub fn try_track_reward(
|
||||||
|
deps: DepsMut<'_>,
|
||||||
|
info: MessageInfo,
|
||||||
|
amount: Coin,
|
||||||
|
address: &str,
|
||||||
|
) -> Result<Response, ContractError> {
|
||||||
|
if info.sender != MIXNET_CONTRACT_ADDRESS.load(deps.storage)? {
|
||||||
|
return Err(ContractError::NotMixnetContract(info.sender));
|
||||||
|
}
|
||||||
|
let account = account_from_address(address, deps.storage, deps.api)?;
|
||||||
|
account.track_reward(amount, deps.storage)?;
|
||||||
|
Ok(Response::new().add_event(new_track_reward_event()))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Track undelegation, invoked by the mixnet contract after sucessful undelegation, message contains coins returned with any accrued rewards.
|
||||||
|
pub fn try_track_undelegation(
|
||||||
|
address: &str,
|
||||||
|
mix_id: MixId,
|
||||||
|
amount: Coin,
|
||||||
|
info: MessageInfo,
|
||||||
|
deps: DepsMut<'_>,
|
||||||
|
) -> Result<Response, ContractError> {
|
||||||
|
if info.sender != MIXNET_CONTRACT_ADDRESS.load(deps.storage)? {
|
||||||
|
return Err(ContractError::NotMixnetContract(info.sender));
|
||||||
|
}
|
||||||
|
let account = account_from_address(address, deps.storage, deps.api)?;
|
||||||
|
|
||||||
|
account.track_undelegation(mix_id, amount, deps.storage)?;
|
||||||
|
Ok(Response::new().add_event(new_track_undelegation_event()))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Delegate to mixnode, sends [mixnet_contract_common::ExecuteMsg::DelegateToMixnodeOnBehalf] to [crate::storage::MIXNET_CONTRACT_ADDRESS]..
|
||||||
|
pub fn try_delegate_to_mixnode(
|
||||||
|
mix_id: MixId,
|
||||||
|
amount: Coin,
|
||||||
|
on_behalf_of: Option<String>,
|
||||||
|
info: MessageInfo,
|
||||||
|
env: Env,
|
||||||
|
deps: DepsMut<'_>,
|
||||||
|
) -> Result<Response, ContractError> {
|
||||||
|
// TODO
|
||||||
|
// as of 01.02.23
|
||||||
|
// thus restricting it to 25, which is more than double of that, doesn't seem too unreasonable.
|
||||||
|
|
||||||
|
// while this might not be the best workaround, if user wishes to delegate more tokens towards the same node
|
||||||
|
// they could remove the existing delegation (thus removing all separate entries from the storage)
|
||||||
|
// and re-delegate it with the reclaimed amount (which will include all rewards).
|
||||||
|
|
||||||
|
let mix_denom = MIX_DENOM.load(deps.storage)?;
|
||||||
|
let amount = validate_funds(&[amount], mix_denom)?;
|
||||||
|
|
||||||
|
let account = match on_behalf_of {
|
||||||
|
Some(account_owner) => {
|
||||||
|
let account = account_from_address(&account_owner, deps.storage, deps.api)?;
|
||||||
|
ensure_staking_permission(&info.sender, &account)?;
|
||||||
|
account
|
||||||
|
}
|
||||||
|
// you're the owner, you can do what you want
|
||||||
|
None => account_from_address(info.sender.as_str(), deps.storage, deps.api)?,
|
||||||
|
};
|
||||||
|
|
||||||
|
account.try_delegate_to_mixnode(mix_id, amount, &env, deps.storage)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Claims operator reward, sends [mixnet_contract_common::ExecuteMsg::ClaimOperatorRewardOnBehalf] to [crate::storage::MIXNET_CONTRACT_ADDRESS].
|
||||||
|
pub fn try_claim_operator_reward(
|
||||||
|
deps: DepsMut<'_>,
|
||||||
|
info: MessageInfo,
|
||||||
|
) -> Result<Response, ContractError> {
|
||||||
|
let account = account_from_address(info.sender.as_str(), deps.storage, deps.api)?;
|
||||||
|
account.try_claim_operator_reward(deps.storage)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Claims delegator reward, sends [mixnet_contract_common::ExecuteMsg::ClaimDelegatorRewardOnBehalf] to [crate::storage::MIXNET_CONTRACT_ADDRESS].
|
||||||
|
pub fn try_claim_delegator_reward(
|
||||||
|
deps: DepsMut<'_>,
|
||||||
|
info: MessageInfo,
|
||||||
|
mix_id: MixId,
|
||||||
|
) -> Result<Response, ContractError> {
|
||||||
|
let account = account_from_address(info.sender.as_str(), deps.storage, deps.api)?;
|
||||||
|
|
||||||
|
account.try_claim_delegator_reward(mix_id, deps.storage)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Undelegates from a mixnode, sends [mixnet_contract_common::ExecuteMsg::UndelegateFromMixnodeOnBehalf] to [crate::storage::MIXNET_CONTRACT_ADDRESS].
|
||||||
|
pub fn try_undelegate_from_mixnode(
|
||||||
|
mix_id: MixId,
|
||||||
|
on_behalf_of: Option<String>,
|
||||||
|
info: MessageInfo,
|
||||||
|
deps: DepsMut<'_>,
|
||||||
|
) -> Result<Response, ContractError> {
|
||||||
|
let account = match on_behalf_of {
|
||||||
|
Some(account_owner) => {
|
||||||
|
let account = account_from_address(&account_owner, deps.storage, deps.api)?;
|
||||||
|
ensure_staking_permission(&info.sender, &account)?;
|
||||||
|
account
|
||||||
|
}
|
||||||
|
// you're the owner, you can do what you want
|
||||||
|
None => account_from_address(info.sender.as_str(), deps.storage, deps.api)?,
|
||||||
|
};
|
||||||
|
|
||||||
|
account.try_undelegate_from_mixnode(mix_id, deps.storage)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a new periodic vesting account, and deposits funds to vest into the contract.
|
||||||
|
///
|
||||||
|
/// Callable by ADMIN only, see [instantiate].
|
||||||
|
pub fn try_create_periodic_vesting_account(
|
||||||
|
owner_address: &str,
|
||||||
|
staking_address: Option<String>,
|
||||||
|
vesting_spec: Option<VestingSpecification>,
|
||||||
|
cap: Option<PledgeCap>,
|
||||||
|
info: MessageInfo,
|
||||||
|
env: Env,
|
||||||
|
deps: DepsMut<'_>,
|
||||||
|
) -> Result<Response, ContractError> {
|
||||||
|
if info.sender != ADMIN.load(deps.storage)? {
|
||||||
|
return Err(ContractError::NotAdmin(info.sender.as_str().to_string()));
|
||||||
|
}
|
||||||
|
|
||||||
|
let mix_denom = MIX_DENOM.load(deps.storage)?;
|
||||||
|
|
||||||
|
let account_exists = account_from_address(owner_address, deps.storage, deps.api).is_ok();
|
||||||
|
if account_exists {
|
||||||
|
return Err(ContractError::AccountAlreadyExists(
|
||||||
|
owner_address.to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let vesting_spec = vesting_spec.unwrap_or_default();
|
||||||
|
|
||||||
|
let coin = validate_funds(&info.funds, mix_denom)?;
|
||||||
|
|
||||||
|
let owner_address = deps.api.addr_validate(owner_address)?;
|
||||||
|
let staking_address = if let Some(staking_address) = staking_address {
|
||||||
|
let staking_account_exists =
|
||||||
|
account_from_address(&staking_address, deps.storage, deps.api).is_ok();
|
||||||
|
if staking_account_exists {
|
||||||
|
return Err(ContractError::StakingAccountAlreadyExists(staking_address));
|
||||||
|
}
|
||||||
|
Some(deps.api.addr_validate(&staking_address)?)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
let start_time = vesting_spec
|
||||||
|
.start_time()
|
||||||
|
.unwrap_or_else(|| env.block.time.seconds());
|
||||||
|
|
||||||
|
let periods = populate_vesting_periods(start_time, vesting_spec);
|
||||||
|
|
||||||
|
let start_time = Timestamp::from_seconds(start_time);
|
||||||
|
|
||||||
|
let response = Response::new();
|
||||||
|
|
||||||
|
Account::new(
|
||||||
|
owner_address.clone(),
|
||||||
|
staking_address.clone(),
|
||||||
|
coin.clone(),
|
||||||
|
start_time,
|
||||||
|
periods,
|
||||||
|
cap,
|
||||||
|
deps.storage,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Ok(response.add_event(new_periodic_vesting_account_event(
|
||||||
|
&owner_address,
|
||||||
|
&coin,
|
||||||
|
&staking_address,
|
||||||
|
start_time,
|
||||||
|
)))
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user