Finalize bonding/unbonding

This commit is contained in:
Drazen Urch
2021-11-21 21:17:01 +01:00
parent 9e8455f49e
commit bf4e18be15
6 changed files with 247 additions and 66 deletions
-1
View File
@@ -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(),
};
+59 -34
View File
@@ -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())
}
+8
View File
@@ -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,
}
+5 -10
View File
@@ -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)]
+27 -1
View File
@@ -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)
+148 -20
View File
@@ -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
}