Finalize bonding/unbonding
This commit is contained in:
@@ -204,7 +204,6 @@ pub(crate) fn _try_remove_mixnode(
|
||||
if let Some(proxy) = &proxy {
|
||||
let msg = VestingContractExecuteMsg::TrackUnbond {
|
||||
owner: owner.to_owned(),
|
||||
mix_identity: mix_identity.clone(),
|
||||
amount: coins(mixnode_bond.bond_amount.amount.u128(), DENOM)[0].clone(),
|
||||
};
|
||||
|
||||
|
||||
@@ -2,13 +2,15 @@ use crate::errors::ContractError;
|
||||
use crate::messages::{ExecuteMsg, InitMsg, QueryMsg};
|
||||
use crate::storage::{get_account, get_account_balance, set_account_balance};
|
||||
use crate::vesting::{
|
||||
populate_vesting_periods, DelegationAccount, PeriodicVestingAccount, VestingAccount,
|
||||
populate_vesting_periods, BondingAccount, DelegationAccount, PeriodicVestingAccount,
|
||||
VestingAccount,
|
||||
};
|
||||
use config::defaults::DENOM;
|
||||
use cosmwasm_std::{
|
||||
attr, entry_point, to_binary, Addr, BankMsg, Coin, Deps, DepsMut, Env, MessageInfo,
|
||||
QueryResponse, Response, Timestamp, Uint128,
|
||||
};
|
||||
use mixnet_contract::IdentityKey;
|
||||
use mixnet_contract::{IdentityKey, MixNode};
|
||||
|
||||
pub const NUM_VESTING_PERIODS: usize = 8;
|
||||
pub const VESTING_PERIOD: u64 = 3 * 30 * 86400;
|
||||
@@ -38,10 +40,9 @@ pub fn execute(
|
||||
msg: ExecuteMsg,
|
||||
) -> Result<Response, ContractError> {
|
||||
match msg {
|
||||
ExecuteMsg::DelegateToMixnode {
|
||||
mix_identity,
|
||||
amount,
|
||||
} => try_delegate_to_mixnode(mix_identity, amount, info, env, deps),
|
||||
ExecuteMsg::DelegateToMixnode { mix_identity } => {
|
||||
try_delegate_to_mixnode(mix_identity, info, env, deps)
|
||||
}
|
||||
ExecuteMsg::UndelegateFromMixnode { mix_identity } => {
|
||||
try_undelegate_from_mixnode(mix_identity, info, deps)
|
||||
}
|
||||
@@ -57,49 +58,54 @@ pub fn execute(
|
||||
mix_identity,
|
||||
amount,
|
||||
} => try_track_undelegation(owner, mix_identity, amount, deps),
|
||||
ExecuteMsg::BondMixnode {
|
||||
mix_identity,
|
||||
amount,
|
||||
} => try_bond_mixnode(mix_identity, amount, info, env, deps),
|
||||
ExecuteMsg::UnbondMixnode {
|
||||
mix_identity,
|
||||
amount,
|
||||
} => try_unbond_mixnode(mix_identity, amount, info, env, deps),
|
||||
ExecuteMsg::TrackUnbond {
|
||||
owner,
|
||||
mix_identity,
|
||||
amount,
|
||||
} => try_track_unbond(owner, mix_identity, amount, deps),
|
||||
ExecuteMsg::BondMixnode { mix_node } => try_bond_mixnode(mix_node, info, env, deps),
|
||||
ExecuteMsg::UnbondMixnode {} => try_unbond_mixnode(info, deps),
|
||||
ExecuteMsg::TrackUnbond { owner, amount } => try_track_unbond(owner, amount, deps),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn try_bond_mixnode(
|
||||
mix_identity: IdentityKey,
|
||||
amount: Coin,
|
||||
mix_node: MixNode,
|
||||
info: MessageInfo,
|
||||
env: Env,
|
||||
deps: DepsMut,
|
||||
) -> Result<Response, ContractError> {
|
||||
unimplemented!()
|
||||
let owner = deps.api.addr_validate(info.sender.as_str())?;
|
||||
let bond = validate_funds(&info.funds)?;
|
||||
if let Some(account) = get_account(deps.storage, &owner) {
|
||||
account.try_bond_mixnode(mix_node, bond, &env, deps.storage)
|
||||
} else {
|
||||
Err(ContractError::NoAccountForAddress(
|
||||
owner.as_str().to_string(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn try_unbond_mixnode(
|
||||
mix_identity: IdentityKey,
|
||||
amount: Coin,
|
||||
info: MessageInfo,
|
||||
env: Env,
|
||||
deps: DepsMut,
|
||||
) -> Result<Response, ContractError> {
|
||||
unimplemented!()
|
||||
pub fn try_unbond_mixnode(info: MessageInfo, deps: DepsMut) -> Result<Response, ContractError> {
|
||||
let owner = deps.api.addr_validate(info.sender.as_str())?;
|
||||
if let Some(account) = get_account(deps.storage, &owner) {
|
||||
account.try_unbond_mixnode()
|
||||
} else {
|
||||
Err(ContractError::NoAccountForAddress(
|
||||
owner.as_str().to_string(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn try_track_unbond(
|
||||
owner: Addr,
|
||||
mix_identity: IdentityKey,
|
||||
amount: Coin,
|
||||
deps: DepsMut,
|
||||
) -> Result<Response, ContractError> {
|
||||
unimplemented!()
|
||||
let owner = deps.api.addr_validate(owner.as_str())?;
|
||||
if let Some(account) = get_account(deps.storage, &owner) {
|
||||
account.track_unbond(amount, deps.storage)?;
|
||||
Ok(Response::default())
|
||||
} else {
|
||||
Err(ContractError::NoAccountForAddress(
|
||||
owner.as_str().to_string(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn try_withdraw_vested_coins(
|
||||
@@ -167,12 +173,12 @@ fn try_track_undelegation(
|
||||
|
||||
fn try_delegate_to_mixnode(
|
||||
mix_identity: IdentityKey,
|
||||
amount: Coin,
|
||||
info: MessageInfo,
|
||||
env: Env,
|
||||
deps: DepsMut,
|
||||
) -> Result<Response, ContractError> {
|
||||
let delegate_addr = info.sender;
|
||||
let amount = validate_funds(&info.funds)?;
|
||||
let address = deps.api.addr_validate(delegate_addr.as_str())?;
|
||||
if let Some(account) = get_account(deps.storage, &address) {
|
||||
account.try_delegate_to_mixnode(mix_identity, amount, &env, deps.storage)
|
||||
@@ -209,7 +215,7 @@ fn try_create_periodic_vesting_account(
|
||||
if info.sender != ADMIN_ADDRESS {
|
||||
return Err(ContractError::NotAdmin(info.sender.as_str().to_string()));
|
||||
}
|
||||
let coin = info.funds[0].clone();
|
||||
let coin = validate_funds(&info.funds)?;
|
||||
let address = deps.api.addr_validate(&address)?;
|
||||
let start_time = start_time.unwrap_or_else(|| env.block.time.seconds());
|
||||
let periods = populate_vesting_periods(start_time, NUM_VESTING_PERIODS);
|
||||
@@ -413,3 +419,22 @@ pub fn try_get_delegated_vesting(
|
||||
Err(ContractError::NoAccountForAddress(vesting_account_address))
|
||||
}
|
||||
}
|
||||
|
||||
fn validate_funds(funds: &[Coin]) -> Result<Coin, ContractError> {
|
||||
if funds.is_empty() || funds[0].amount.is_zero() {
|
||||
return Err(ContractError::EmptyFunds);
|
||||
}
|
||||
|
||||
if funds.len() > 1 {
|
||||
return Err(ContractError::MultipleDenoms);
|
||||
}
|
||||
|
||||
if funds[0].denom != DENOM {
|
||||
return Err(ContractError::WrongDenom(
|
||||
funds[0].denom.clone(),
|
||||
DENOM.to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
Ok(funds[0].clone())
|
||||
}
|
||||
|
||||
@@ -21,4 +21,12 @@ pub enum ContractError {
|
||||
NotDelegate(String),
|
||||
#[error("Total vesting amount is inprobably low -> {0}, this is likely an error")]
|
||||
ImprobableVestingAmount(u128),
|
||||
#[error("Address {0} has already bonded a node")]
|
||||
AlreadyBonded(String),
|
||||
#[error("Recieved empty funds vector")]
|
||||
EmptyFunds,
|
||||
#[error("Recieved wrong denom: {0}, expected {1}")]
|
||||
WrongDenom(String, String),
|
||||
#[error("Recieved multiple denoms, expected 1")]
|
||||
MultipleDenoms,
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use cosmwasm_std::{Addr, Coin, Timestamp};
|
||||
use mixnet_contract::IdentityKey;
|
||||
use mixnet_contract::MixNode;
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
@@ -11,7 +12,6 @@ pub struct InitMsg {}
|
||||
pub enum ExecuteMsg {
|
||||
DelegateToMixnode {
|
||||
mix_identity: IdentityKey,
|
||||
amount: Coin,
|
||||
},
|
||||
UndelegateFromMixnode {
|
||||
mix_identity: IdentityKey,
|
||||
@@ -29,18 +29,13 @@ pub enum ExecuteMsg {
|
||||
amount: Coin,
|
||||
},
|
||||
BondMixnode {
|
||||
mix_identity: IdentityKey,
|
||||
amount: Coin,
|
||||
},
|
||||
UnbondMixnode {
|
||||
mix_identity: IdentityKey,
|
||||
amount: Coin,
|
||||
mix_node: MixNode,
|
||||
},
|
||||
UnbondMixnode {},
|
||||
TrackUnbond {
|
||||
mix_identity: IdentityKey,
|
||||
owner: Addr,
|
||||
amount: Coin
|
||||
}
|
||||
amount: Coin,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
|
||||
|
||||
@@ -5,7 +5,7 @@ use cosmwasm_storage::{bucket, bucket_read, Bucket, ReadonlyBucket};
|
||||
|
||||
use crate::{
|
||||
errors::ContractError,
|
||||
vesting::{DelegationData, PeriodicVestingAccount},
|
||||
vesting::{BondData, DelegationData, PeriodicVestingAccount},
|
||||
};
|
||||
// storage prefixes
|
||||
// all of them must be unique and presumably not be a prefix of a different one
|
||||
@@ -16,6 +16,7 @@ use crate::{
|
||||
const PREFIX_ACCOUNTS: &[u8] = b"ac";
|
||||
const PREFIX_ACCOUNT_DELEGATIONS: &[u8] = b"ad";
|
||||
const PREFIX_ACCOUNT_BALANCE: &[u8] = b"ab";
|
||||
const PREFIX_ACCOUNT_MIXBOND: &[u8] = b"am";
|
||||
// Contract-level stuff
|
||||
|
||||
fn accounts_mut(storage: &mut dyn Storage) -> Bucket<PeriodicVestingAccount> {
|
||||
@@ -34,6 +35,14 @@ fn account_delegations(storage: &dyn Storage) -> ReadonlyBucket<Vec<DelegationDa
|
||||
bucket_read(storage, PREFIX_ACCOUNT_DELEGATIONS)
|
||||
}
|
||||
|
||||
fn account_bond_mut(storage: &mut dyn Storage) -> Bucket<BondData> {
|
||||
bucket(storage, PREFIX_ACCOUNT_DELEGATIONS)
|
||||
}
|
||||
|
||||
fn account_bond(storage: &dyn Storage) -> ReadonlyBucket<BondData> {
|
||||
bucket_read(storage, PREFIX_ACCOUNT_DELEGATIONS)
|
||||
}
|
||||
|
||||
fn account_balance(storage: &dyn Storage) -> ReadonlyBucket<Uint128> {
|
||||
bucket_read(storage, PREFIX_ACCOUNT_BALANCE)
|
||||
}
|
||||
@@ -72,6 +81,23 @@ pub fn set_account_delegations(
|
||||
account_delegations_mut(storage).save(address.as_bytes(), &delegations)
|
||||
}
|
||||
|
||||
pub fn get_account_bond(storage: &dyn Storage, address: &Addr) -> Option<BondData> {
|
||||
// Due to using may_load this should be safe to unwrap
|
||||
account_bond(storage).may_load(address.as_bytes()).unwrap()
|
||||
}
|
||||
|
||||
pub fn drop_account_bond(storage: &mut dyn Storage, address: &Addr) -> StdResult<()> {
|
||||
Ok(account_bond_mut(storage).remove(address.as_bytes()))
|
||||
}
|
||||
|
||||
pub fn set_account_bond(
|
||||
storage: &mut dyn Storage,
|
||||
address: &Addr,
|
||||
bond: BondData,
|
||||
) -> StdResult<()> {
|
||||
account_bond_mut(storage).save(address.as_bytes(), &bond)
|
||||
}
|
||||
|
||||
pub fn get_account_balance(storage: &dyn Storage, address: &Addr) -> Option<Uint128> {
|
||||
// Due to using may_load this should be safe to unwrap
|
||||
account_balance(storage)
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
use crate::contract::{NUM_VESTING_PERIODS, VESTING_PERIOD};
|
||||
use crate::errors::ContractError;
|
||||
use crate::storage::{
|
||||
get_account_balance, get_account_delegations, set_account, set_account_balance,
|
||||
set_account_delegations,
|
||||
drop_account_bond, get_account_balance, get_account_bond, get_account_delegations, set_account,
|
||||
set_account_balance, set_account_bond, set_account_delegations,
|
||||
};
|
||||
use config::defaults::{DEFAULT_MIXNET_CONTRACT_ADDRESS, DENOM};
|
||||
use cosmwasm_std::{attr, wasm_execute, Addr, Coin, Env, Response, Storage, Timestamp, Uint128};
|
||||
use mixnet_contract::ExecuteMsg as MixnetExecuteMsg;
|
||||
use mixnet_contract::IdentityKey;
|
||||
use mixnet_contract::{ExecuteMsg as MixnetExecuteMsg, MixNode};
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
@@ -84,7 +84,7 @@ pub trait DelegationAccount {
|
||||
&self,
|
||||
block_time: Timestamp,
|
||||
mix_identity: IdentityKey,
|
||||
delegation_amount: Coin,
|
||||
delegation: Coin,
|
||||
storage: &mut dyn Storage,
|
||||
) -> Result<(), ContractError>;
|
||||
// track_undelegation performs internal vesting accounting necessary when a
|
||||
@@ -97,6 +97,27 @@ pub trait DelegationAccount {
|
||||
) -> Result<(), ContractError>;
|
||||
}
|
||||
|
||||
pub trait BondingAccount {
|
||||
fn try_bond_mixnode(
|
||||
&self,
|
||||
mix_node: MixNode,
|
||||
amount: Coin,
|
||||
env: &Env,
|
||||
storage: &mut dyn Storage,
|
||||
) -> Result<Response, ContractError>;
|
||||
|
||||
fn try_unbond_mixnode(&self) -> Result<Response, ContractError>;
|
||||
|
||||
fn track_bond(
|
||||
&self,
|
||||
block_time: Timestamp,
|
||||
bond: Coin,
|
||||
storage: &mut dyn Storage,
|
||||
) -> Result<(), ContractError>;
|
||||
|
||||
fn track_unbond(&self, amount: Coin, storage: &mut dyn Storage) -> Result<(), ContractError>;
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
|
||||
pub struct VestingPeriod {
|
||||
pub start_time: u64,
|
||||
@@ -116,13 +137,6 @@ pub struct PeriodicVestingAccount {
|
||||
coin: Coin,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
|
||||
pub struct DelegationData {
|
||||
mix_identity: IdentityKey,
|
||||
amount: Uint128,
|
||||
block_time: Timestamp,
|
||||
}
|
||||
|
||||
impl PeriodicVestingAccount {
|
||||
pub fn new(
|
||||
address: Addr,
|
||||
@@ -254,10 +268,10 @@ impl DelegationAccount for PeriodicVestingAccount {
|
||||
data: None,
|
||||
})
|
||||
} else {
|
||||
return Err(ContractError::InsufficientBalance(
|
||||
Err(ContractError::InsufficientBalance(
|
||||
self.address.as_str().to_string(),
|
||||
self.get_balance(storage).u128(),
|
||||
));
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -293,7 +307,7 @@ impl DelegationAccount for PeriodicVestingAccount {
|
||||
&self,
|
||||
block_time: Timestamp,
|
||||
mix_identity: IdentityKey,
|
||||
delegation_amount: Coin,
|
||||
delegation: Coin,
|
||||
storage: &mut dyn Storage,
|
||||
) -> Result<(), ContractError> {
|
||||
let mut delegations =
|
||||
@@ -304,13 +318,13 @@ impl DelegationAccount for PeriodicVestingAccount {
|
||||
};
|
||||
delegations.push(DelegationData {
|
||||
mix_identity,
|
||||
amount: delegation_amount.amount,
|
||||
amount: delegation.amount,
|
||||
block_time,
|
||||
});
|
||||
|
||||
let new_balance = if let Some(balance) = get_account_balance(storage, &self.address) {
|
||||
// We've checked that delegation_amount < balance in the caller function
|
||||
Uint128(balance.u128() - delegation_amount.amount.u128())
|
||||
// We've checked that delegation < balance in the caller function
|
||||
Uint128(balance.u128() - delegation.amount.u128())
|
||||
} else {
|
||||
return Err(ContractError::NoBalanceForAddress(
|
||||
self.address.as_str().to_string(),
|
||||
@@ -335,7 +349,7 @@ impl DelegationAccount for PeriodicVestingAccount {
|
||||
.into_iter()
|
||||
.filter(|d| d.mix_identity != mix_identity)
|
||||
.collect();
|
||||
|
||||
|
||||
let new_balance = if let Some(balance) = get_account_balance(storage, &self.address) {
|
||||
Uint128(balance.u128() + amount.amount.u128())
|
||||
} else {
|
||||
@@ -346,8 +360,6 @@ impl DelegationAccount for PeriodicVestingAccount {
|
||||
|
||||
set_account_balance(storage, &self.address, new_balance)?;
|
||||
|
||||
// Since we're always removing the entire delegation we can just drop the key
|
||||
// TODO: track balance here as well, maybe
|
||||
Ok(set_account_delegations(
|
||||
storage,
|
||||
&self.address,
|
||||
@@ -356,6 +368,109 @@ impl DelegationAccount for PeriodicVestingAccount {
|
||||
}
|
||||
}
|
||||
|
||||
impl BondingAccount for PeriodicVestingAccount {
|
||||
fn try_bond_mixnode(
|
||||
&self,
|
||||
mix_node: MixNode,
|
||||
bond: Coin,
|
||||
env: &Env,
|
||||
storage: &mut dyn Storage,
|
||||
) -> Result<Response, ContractError> {
|
||||
if bond.amount < self.get_balance(storage) {
|
||||
let msg = MixnetExecuteMsg::BondMixnodeOnBehalf {
|
||||
mix_node,
|
||||
owner: self.address.clone(),
|
||||
};
|
||||
let messages =
|
||||
vec![
|
||||
wasm_execute(DEFAULT_MIXNET_CONTRACT_ADDRESS, &msg, vec![bond.clone()])?.into(),
|
||||
];
|
||||
let attributes = vec![attr("action", "bond mixnode on behalf")];
|
||||
self.track_bond(env.block.time, bond, storage)?;
|
||||
|
||||
Ok(Response {
|
||||
submessages: Vec::new(),
|
||||
messages,
|
||||
attributes,
|
||||
data: None,
|
||||
})
|
||||
} else {
|
||||
Err(ContractError::InsufficientBalance(
|
||||
self.address.as_str().to_string(),
|
||||
self.get_balance(storage).u128(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
fn track_bond(
|
||||
&self,
|
||||
block_time: Timestamp,
|
||||
bond: Coin,
|
||||
storage: &mut dyn Storage,
|
||||
) -> Result<(), ContractError> {
|
||||
let bond = if let Some(_bond) = get_account_bond(storage, &self.address) {
|
||||
return Err(ContractError::AlreadyBonded(
|
||||
self.address.as_str().to_string(),
|
||||
));
|
||||
} else {
|
||||
BondData {
|
||||
block_time,
|
||||
amount: bond.amount,
|
||||
}
|
||||
};
|
||||
|
||||
let new_balance = if let Some(balance) = get_account_balance(storage, &self.address) {
|
||||
// We've checked that bond.amount < balance in the caller function
|
||||
Uint128(balance.u128() - bond.amount.u128())
|
||||
} else {
|
||||
return Err(ContractError::NoBalanceForAddress(
|
||||
self.address.as_str().to_string(),
|
||||
));
|
||||
};
|
||||
|
||||
set_account_balance(storage, &self.address, new_balance)?;
|
||||
Ok(set_account_bond(storage, &self.address, bond)?)
|
||||
}
|
||||
|
||||
fn try_unbond_mixnode(&self) -> Result<Response, ContractError> {
|
||||
let msg = MixnetExecuteMsg::UnbondMixnodeOnBehalf {
|
||||
owner: self.address.clone(),
|
||||
};
|
||||
let messages = vec![wasm_execute(
|
||||
DEFAULT_MIXNET_CONTRACT_ADDRESS,
|
||||
&msg,
|
||||
vec![Coin {
|
||||
amount: Uint128(0),
|
||||
denom: DENOM.to_string(),
|
||||
}],
|
||||
)?
|
||||
.into()];
|
||||
|
||||
let attributes = vec![attr("action", "unbond mixnode on behalf")];
|
||||
|
||||
Ok(Response {
|
||||
submessages: Vec::new(),
|
||||
messages,
|
||||
attributes,
|
||||
data: None,
|
||||
})
|
||||
}
|
||||
|
||||
fn track_unbond(&self, amount: Coin, storage: &mut dyn Storage) -> Result<(), ContractError> {
|
||||
let new_balance = if let Some(balance) = get_account_balance(storage, &self.address) {
|
||||
Uint128(balance.u128() + amount.amount.u128())
|
||||
} else {
|
||||
return Err(ContractError::NoBalanceForAddress(
|
||||
self.address.as_str().to_string(),
|
||||
));
|
||||
};
|
||||
|
||||
set_account_balance(storage, &self.address, new_balance)?;
|
||||
drop_account_bond(storage, &self.address)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl VestingAccount for PeriodicVestingAccount {
|
||||
fn locked_coins(
|
||||
&self,
|
||||
@@ -467,6 +582,19 @@ impl VestingAccount for PeriodicVestingAccount {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
|
||||
pub struct DelegationData {
|
||||
mix_identity: IdentityKey,
|
||||
amount: Uint128,
|
||||
block_time: Timestamp,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
|
||||
pub struct BondData {
|
||||
amount: Uint128,
|
||||
block_time: Timestamp,
|
||||
}
|
||||
|
||||
fn lt(x: u64, y: u64) -> bool {
|
||||
x < y
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user