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:
Jędrzej Stuczyński
2023-04-05 09:22:43 +01:00
committed by GitHub
parent a3b4d04d02
commit 4fb63d8892
6 changed files with 892 additions and 786 deletions
@@ -229,6 +229,32 @@ pub trait VestingQueryClient {
.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(
&self,
address: &str,
@@ -260,6 +260,15 @@ pub enum QueryMsg {
GetCurrentVestingPeriod {
address: String,
},
GetDelegation {
address: String,
mix_id: MixId,
block_timestamp_secs: u64,
},
GetTotalDelegationAmount {
address: String,
mix_id: MixId,
},
GetDelegationTimes {
address: String,
mix_id: MixId,
+25 -786
View File
@@ -1,38 +1,14 @@
use crate::errors::ContractError;
use crate::storage::{
account_from_address, save_account, BlockTimestampSecs, ACCOUNTS, ADMIN, DELEGATIONS,
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 contracts_common::ContractBuildInformation;
pub use crate::queries::*;
use crate::storage::{ADMIN, MIXNET_CONTRACT_ADDRESS, MIX_DENOM};
pub use crate::transactions::*;
use crate::vesting::Account;
use cosmwasm_std::{
coin, entry_point, to_binary, Addr, BankMsg, Coin, Deps, DepsMut, Env, MessageInfo, Order,
QueryResponse, Response, StdResult, Timestamp, Uint128,
entry_point, to_binary, Addr, Coin, Deps, DepsMut, Env, MessageInfo, QueryResponse, Response,
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 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::{
ExecuteMsg, InitMsg, MigrateMsg, QueryMsg, VestingSpecification,
};
use vesting_contract_common::{
AccountVestingCoins, AccountsResponse, AllDelegationsResponse, BaseVestingAccountInfo,
DelegationTimesResponse, OriginalVestingResponse, Period, PledgeCap, PledgeData,
VestingCoinsResponse, VestingDelegation,
};
use vesting_contract_common::messages::{ExecuteMsg, InitMsg, MigrateMsg, QueryMsg};
// version info for migration info
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]
pub fn query(deps: Deps<'_>, env: Env, msg: QueryMsg) -> Result<QueryResponse, ContractError> {
let query_res = match msg {
@@ -757,6 +286,19 @@ pub fn query(deps: Deps<'_>, env: Env, msg: QueryMsg) -> Result<QueryResponse, C
QueryMsg::GetCurrentVestingPeriod { address } => {
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 } => {
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?)
}
/// 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 = 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> {
pub(crate) fn validate_funds(funds: &[Coin], mix_denom: String) -> Result<Coin, ContractError> {
if funds.is_empty() || funds[0].amount.is_zero() {
return Err(ContractError::EmptyFunds);
}
@@ -1090,7 +326,10 @@ fn validate_funds(funds: &[Coin], mix_denom: String) -> Result<Coin, ContractErr
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 staking_address == addr {
return Ok(());
+2
View File
@@ -6,8 +6,10 @@
pub mod contract;
mod errors;
mod queries;
mod queued_migrations;
mod storage;
mod support;
mod traits;
mod transactions;
pub mod vesting;
+357
View File
@@ -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 })
}
+473
View File
@@ -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,
)))
}