Allow proxy gateway bonding (#936)
* Allow proxy gateway bonding * Update msg text Co-authored-by: Drazen Urch <durch@users.noreply.guthub.com>
This commit is contained in:
@@ -27,15 +27,23 @@ pub struct GatewayBond {
|
||||
pub owner: Addr,
|
||||
pub block_height: u64,
|
||||
pub gateway: Gateway,
|
||||
pub proxy: Option<Addr>,
|
||||
}
|
||||
|
||||
impl GatewayBond {
|
||||
pub fn new(bond_amount: Coin, owner: Addr, block_height: u64, gateway: Gateway) -> Self {
|
||||
pub fn new(
|
||||
bond_amount: Coin,
|
||||
owner: Addr,
|
||||
block_height: u64,
|
||||
gateway: Gateway,
|
||||
proxy: Option<Addr>,
|
||||
) -> Self {
|
||||
GatewayBond {
|
||||
bond_amount,
|
||||
owner,
|
||||
block_height,
|
||||
gateway,
|
||||
proxy,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -154,6 +162,7 @@ mod tests {
|
||||
owner: Addr::unchecked("foo1"),
|
||||
block_height: 100,
|
||||
gateway: gateway_fixture(),
|
||||
proxy: None,
|
||||
};
|
||||
|
||||
let gate2 = GatewayBond {
|
||||
@@ -161,6 +170,7 @@ mod tests {
|
||||
owner: Addr::unchecked("foo2"),
|
||||
block_height: 120,
|
||||
gateway: gateway_fixture(),
|
||||
proxy: None,
|
||||
};
|
||||
|
||||
let gate3 = GatewayBond {
|
||||
@@ -168,6 +178,7 @@ mod tests {
|
||||
owner: Addr::unchecked("foo3"),
|
||||
block_height: 120,
|
||||
gateway: gateway_fixture(),
|
||||
proxy: None,
|
||||
};
|
||||
|
||||
let gate4 = GatewayBond {
|
||||
@@ -175,6 +186,7 @@ mod tests {
|
||||
owner: Addr::unchecked("foo4"),
|
||||
block_height: 120,
|
||||
gateway: gateway_fixture(),
|
||||
proxy: None,
|
||||
};
|
||||
|
||||
let gate5 = GatewayBond {
|
||||
@@ -182,6 +194,7 @@ mod tests {
|
||||
owner: Addr::unchecked("foo5"),
|
||||
block_height: 120,
|
||||
gateway: gateway_fixture(),
|
||||
proxy: None,
|
||||
};
|
||||
|
||||
// summary:
|
||||
|
||||
@@ -71,6 +71,13 @@ pub enum ExecuteMsg {
|
||||
UnbondMixnodeOnBehalf {
|
||||
owner: String,
|
||||
},
|
||||
BondGatewayOnBehalf {
|
||||
gateway: Gateway,
|
||||
owner: String,
|
||||
},
|
||||
UnbondGatewayOnBehalf {
|
||||
owner: String,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
|
||||
|
||||
@@ -184,6 +184,14 @@ pub fn execute(
|
||||
ExecuteMsg::UnbondMixnodeOnBehalf { owner } => {
|
||||
crate::mixnodes::transactions::try_remove_mixnode_on_behalf(deps, info, owner)
|
||||
}
|
||||
ExecuteMsg::BondGatewayOnBehalf { gateway, owner } => {
|
||||
crate::gateways::transactions::try_add_gateway_on_behalf(
|
||||
deps, env, info, gateway, owner,
|
||||
)
|
||||
}
|
||||
ExecuteMsg::UnbondGatewayOnBehalf { owner } => {
|
||||
crate::gateways::transactions::try_remove_gateway_on_behalf(deps, info, owner)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -93,6 +93,7 @@ mod tests {
|
||||
identity_key: node_identity.clone(),
|
||||
..test_helpers::gateway_fixture()
|
||||
},
|
||||
proxy: None,
|
||||
};
|
||||
|
||||
storage::gateways()
|
||||
|
||||
@@ -6,36 +6,71 @@ use crate::error::ContractError;
|
||||
use crate::mixnet_contract_settings::storage as mixnet_params_storage;
|
||||
use crate::support::helpers::ensure_no_existing_bond;
|
||||
use config::defaults::DENOM;
|
||||
use cosmwasm_std::{BankMsg, Coin, DepsMut, Env, MessageInfo, Response, Uint128};
|
||||
use cosmwasm_std::{
|
||||
coins, wasm_execute, Addr, BankMsg, Coin, DepsMut, Env, MessageInfo, Response, Uint128,
|
||||
};
|
||||
use mixnet_contract::{Gateway, GatewayBond, Layer};
|
||||
use vesting_contract::messages::ExecuteMsg as VestingContractExecuteMsg;
|
||||
|
||||
pub(crate) fn try_add_gateway(
|
||||
pub fn try_add_gateway(
|
||||
deps: DepsMut,
|
||||
env: Env,
|
||||
info: MessageInfo,
|
||||
gateway: Gateway,
|
||||
) -> Result<Response, ContractError> {
|
||||
// check if the bond contains any funds of the appropriate denomination
|
||||
let minimum_bond = mixnet_params_storage::CONTRACT_STATE
|
||||
.load(deps.storage)?
|
||||
.params
|
||||
.minimum_mixnode_bond;
|
||||
let bond = validate_gateway_bond(info.funds, minimum_bond)?;
|
||||
|
||||
_try_add_gateway(deps, env, gateway, bond, info.sender.as_str(), None)
|
||||
}
|
||||
|
||||
pub fn try_add_gateway_on_behalf(
|
||||
deps: DepsMut,
|
||||
env: Env,
|
||||
info: MessageInfo,
|
||||
gateway: Gateway,
|
||||
owner: String,
|
||||
) -> Result<Response, ContractError> {
|
||||
// check if the bond contains any funds of the appropriate denomination
|
||||
let minimum_bond = mixnet_params_storage::CONTRACT_STATE
|
||||
.load(deps.storage)?
|
||||
.params
|
||||
.minimum_mixnode_bond;
|
||||
let bond = validate_gateway_bond(info.funds, minimum_bond)?;
|
||||
|
||||
let proxy = info.sender;
|
||||
_try_add_gateway(deps, env, gateway, bond, &owner, Some(proxy))
|
||||
}
|
||||
|
||||
pub(crate) fn _try_add_gateway(
|
||||
deps: DepsMut,
|
||||
env: Env,
|
||||
gateway: Gateway,
|
||||
bond: Coin,
|
||||
owner: &str,
|
||||
proxy: Option<Addr>,
|
||||
) -> Result<Response, ContractError> {
|
||||
let owner = deps.api.addr_validate(owner)?;
|
||||
|
||||
// if the client has an active bonded mixnode or gateway, don't allow bonding
|
||||
ensure_no_existing_bond(deps.storage, &info.sender)?;
|
||||
ensure_no_existing_bond(deps.storage, &owner)?;
|
||||
|
||||
// check if somebody else has already bonded a gateway with this identity
|
||||
if let Some(existing_bond) =
|
||||
storage::gateways().may_load(deps.storage, &gateway.identity_key)?
|
||||
{
|
||||
if existing_bond.owner != info.sender {
|
||||
if existing_bond.owner != owner {
|
||||
return Err(ContractError::DuplicateGateway {
|
||||
owner: existing_bond.owner,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
let minimum_bond = mixnet_params_storage::CONTRACT_STATE
|
||||
.load(deps.storage)?
|
||||
.params
|
||||
.minimum_gateway_bond;
|
||||
let bond_amount = validate_gateway_bond(info.funds, minimum_bond)?;
|
||||
|
||||
let bond = GatewayBond::new(bond_amount, info.sender, env.block.height, gateway);
|
||||
let bond = GatewayBond::new(bond, owner, env.block.height, gateway, proxy);
|
||||
|
||||
storage::gateways().save(deps.storage, bond.identity(), &bond)?;
|
||||
mixnet_params_storage::increment_layer_count(deps.storage, Layer::Gateway)?;
|
||||
@@ -43,23 +78,47 @@ pub(crate) fn try_add_gateway(
|
||||
Ok(Response::new())
|
||||
}
|
||||
|
||||
pub(crate) fn try_remove_gateway(
|
||||
pub fn try_remove_gateway_on_behalf(
|
||||
deps: DepsMut,
|
||||
info: MessageInfo,
|
||||
owner: String,
|
||||
) -> Result<Response, ContractError> {
|
||||
let proxy = info.sender;
|
||||
_try_remove_gateway(deps, &owner, Some(proxy))
|
||||
}
|
||||
|
||||
pub fn try_remove_gateway(deps: DepsMut, info: MessageInfo) -> Result<Response, ContractError> {
|
||||
_try_remove_gateway(deps, info.sender.as_ref(), None)
|
||||
}
|
||||
|
||||
pub(crate) fn _try_remove_gateway(
|
||||
deps: DepsMut,
|
||||
owner: &str,
|
||||
proxy: Option<Addr>,
|
||||
) -> Result<Response, ContractError> {
|
||||
let owner = deps.api.addr_validate(owner)?;
|
||||
// try to find the node of the sender
|
||||
let gateway_bond = match storage::gateways()
|
||||
.idx
|
||||
.owner
|
||||
.item(deps.storage, info.sender.clone())?
|
||||
.item(deps.storage, owner.clone())?
|
||||
{
|
||||
Some(record) => record.1,
|
||||
None => return Err(ContractError::NoAssociatedGatewayBond { owner: info.sender }),
|
||||
None => return Err(ContractError::NoAssociatedGatewayBond { owner }),
|
||||
};
|
||||
|
||||
if proxy != gateway_bond.proxy {
|
||||
return Err(ContractError::ProxyMismatch {
|
||||
existing: gateway_bond
|
||||
.proxy
|
||||
.map_or_else(|| "None".to_string(), |a| a.as_str().to_string()),
|
||||
incoming: proxy.map_or_else(|| "None".to_string(), |a| a.as_str().to_string()),
|
||||
});
|
||||
}
|
||||
|
||||
// send bonded funds back to the bond owner
|
||||
let return_tokens = BankMsg::Send {
|
||||
to_address: info.sender.as_str().to_owned(),
|
||||
to_address: proxy.as_ref().unwrap_or(&owner).to_string(),
|
||||
amount: vec![gateway_bond.bond_amount()],
|
||||
};
|
||||
|
||||
@@ -69,11 +128,23 @@ pub(crate) fn try_remove_gateway(
|
||||
// decrement layer count
|
||||
mixnet_params_storage::decrement_layer_count(deps.storage, Layer::Gateway)?;
|
||||
|
||||
Ok(Response::new()
|
||||
let mut response = Response::new()
|
||||
.add_message(return_tokens)
|
||||
.add_attribute("action", "unbond")
|
||||
.add_attribute("address", info.sender)
|
||||
.add_attribute("gateway_bond", gateway_bond.to_string()))
|
||||
.add_attribute("address", owner.clone())
|
||||
.add_attribute("gateway_bond", gateway_bond.to_string());
|
||||
|
||||
if let Some(proxy) = &proxy {
|
||||
let msg = VestingContractExecuteMsg::TrackUnbondGateway {
|
||||
owner: owner.as_str().to_string(),
|
||||
amount: gateway_bond.bond_amount,
|
||||
};
|
||||
|
||||
let track_unbond_message = wasm_execute(proxy, &msg, coins(0, DENOM))?;
|
||||
response = response.add_message(track_unbond_message);
|
||||
}
|
||||
|
||||
Ok(response)
|
||||
}
|
||||
|
||||
fn validate_gateway_bond(
|
||||
|
||||
@@ -150,7 +150,7 @@ pub(crate) fn _try_remove_mixnode(
|
||||
.add_attribute("mixnode_bond", mixnode_bond.to_string());
|
||||
|
||||
if let Some(proxy) = &proxy {
|
||||
let msg = VestingContractExecuteMsg::TrackUnbond {
|
||||
let msg = VestingContractExecuteMsg::TrackUnbondMixnode {
|
||||
owner: owner.as_str().to_string(),
|
||||
amount: mixnode_bond.bond_amount,
|
||||
};
|
||||
|
||||
@@ -154,7 +154,13 @@ pub mod test_helpers {
|
||||
identity_key: format!("id-{}", owner),
|
||||
..gateway_fixture()
|
||||
};
|
||||
GatewayBond::new(coin(50, DENOM), Addr::unchecked(owner), 12_345, gateway)
|
||||
GatewayBond::new(
|
||||
coin(50, DENOM),
|
||||
Addr::unchecked(owner),
|
||||
12_345,
|
||||
gateway,
|
||||
None,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn query_contract_balance(
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
use crate::errors::ContractError;
|
||||
use crate::messages::{ExecuteMsg, InitMsg, QueryMsg};
|
||||
use crate::storage::account_from_address;
|
||||
use crate::traits::{BondingAccount, DelegatingAccount, VestingAccount};
|
||||
use crate::traits::{
|
||||
DelegatingAccount, GatewayBondingAccount, MixnodeBondingAccount, VestingAccount,
|
||||
};
|
||||
use crate::vesting::{populate_vesting_periods, Account};
|
||||
use config::defaults::{DEFAULT_MIXNET_CONTRACT_ADDRESS, DENOM};
|
||||
use cosmwasm_std::{
|
||||
entry_point, to_binary, BankMsg, Coin, Deps, DepsMut, Env, MessageInfo, QueryResponse,
|
||||
Response, Timestamp, Uint128,
|
||||
};
|
||||
use mixnet_contract::{IdentityKey, MixNode};
|
||||
use mixnet_contract::{Gateway, IdentityKey, MixNode};
|
||||
|
||||
// We're using a 24 month vesting period with 3 months sub-periods.
|
||||
// There are 8 three month periods in two years
|
||||
@@ -56,10 +58,47 @@ pub fn execute(
|
||||
} => try_track_undelegation(&owner, mix_identity, amount, info, 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, info, deps),
|
||||
ExecuteMsg::TrackUnbondMixnode { owner, amount } => {
|
||||
try_track_unbond_mixnode(&owner, amount, info, deps)
|
||||
}
|
||||
ExecuteMsg::BondGateway { gateway } => try_bond_gateway(gateway, info, env, deps),
|
||||
ExecuteMsg::UnbondGateway {} => try_unbond_gateway(info, deps),
|
||||
ExecuteMsg::TrackUnbondGateway { owner, amount } => {
|
||||
try_track_unbond_gateway(&owner, amount, info, deps)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn try_bond_gateway(
|
||||
gateway: Gateway,
|
||||
info: MessageInfo,
|
||||
env: Env,
|
||||
deps: DepsMut,
|
||||
) -> Result<Response, ContractError> {
|
||||
let bond = validate_funds(&info.funds)?;
|
||||
let account = account_from_address(info.sender.as_str(), deps.storage, deps.api)?;
|
||||
account.try_bond_gateway(gateway, bond, &env, deps.storage)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
pub fn try_track_unbond_gateway(
|
||||
owner: &str,
|
||||
amount: Coin,
|
||||
info: MessageInfo,
|
||||
deps: DepsMut,
|
||||
) -> Result<Response, ContractError> {
|
||||
if info.sender != DEFAULT_MIXNET_CONTRACT_ADDRESS {
|
||||
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::default())
|
||||
}
|
||||
|
||||
pub fn try_bond_mixnode(
|
||||
mix_node: MixNode,
|
||||
info: MessageInfo,
|
||||
@@ -76,7 +115,7 @@ pub fn try_unbond_mixnode(info: MessageInfo, deps: DepsMut) -> Result<Response,
|
||||
account.try_unbond_mixnode(deps.storage)
|
||||
}
|
||||
|
||||
pub fn try_track_unbond(
|
||||
pub fn try_track_unbond_mixnode(
|
||||
owner: &str,
|
||||
amount: Coin,
|
||||
info: MessageInfo,
|
||||
@@ -86,7 +125,7 @@ pub fn try_track_unbond(
|
||||
return Err(ContractError::NotMixnetContract(info.sender));
|
||||
}
|
||||
let account = account_from_address(owner, deps.storage, deps.api)?;
|
||||
account.track_unbond(amount, deps.storage)?;
|
||||
account.try_track_unbond_mixnode(amount, deps.storage)?;
|
||||
Ok(Response::default())
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use cosmwasm_std::{Coin, Timestamp};
|
||||
use mixnet_contract::IdentityKey;
|
||||
use mixnet_contract::MixNode;
|
||||
use mixnet_contract::{Gateway, MixNode};
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
@@ -33,7 +33,15 @@ pub enum ExecuteMsg {
|
||||
mix_node: MixNode,
|
||||
},
|
||||
UnbondMixnode {},
|
||||
TrackUnbond {
|
||||
TrackUnbondMixnode {
|
||||
owner: String,
|
||||
amount: Coin,
|
||||
},
|
||||
BondGateway {
|
||||
gateway: Gateway,
|
||||
},
|
||||
UnbondGateway {},
|
||||
TrackUnbondGateway {
|
||||
owner: String,
|
||||
amount: Coin,
|
||||
},
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
use crate::errors::ContractError;
|
||||
use cosmwasm_std::{Coin, Env, Response, Storage};
|
||||
use mixnet_contract::{Gateway, MixNode};
|
||||
|
||||
pub trait MixnodeBondingAccount {
|
||||
fn try_bond_mixnode(
|
||||
&self,
|
||||
mix_node: MixNode,
|
||||
amount: Coin,
|
||||
env: &Env,
|
||||
storage: &mut dyn Storage,
|
||||
) -> Result<Response, ContractError>;
|
||||
|
||||
fn try_unbond_mixnode(&self, storage: &dyn Storage) -> Result<Response, ContractError>;
|
||||
|
||||
fn try_track_unbond_mixnode(
|
||||
&self,
|
||||
amount: Coin,
|
||||
storage: &mut dyn Storage,
|
||||
) -> Result<(), ContractError>;
|
||||
}
|
||||
|
||||
pub trait GatewayBondingAccount {
|
||||
fn try_bond_gateway(
|
||||
&self,
|
||||
gateway: Gateway,
|
||||
amount: Coin,
|
||||
env: &Env,
|
||||
storage: &mut dyn Storage,
|
||||
) -> Result<Response, ContractError>;
|
||||
|
||||
fn try_unbond_gateway(&self, storage: &dyn Storage) -> Result<Response, ContractError>;
|
||||
|
||||
fn try_track_unbond_gateway(
|
||||
&self,
|
||||
amount: Coin,
|
||||
storage: &mut dyn Storage,
|
||||
) -> Result<(), ContractError>;
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
use crate::errors::ContractError;
|
||||
use cosmwasm_std::{Coin, Env, Response, Storage};
|
||||
use mixnet_contract::MixNode;
|
||||
|
||||
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, storage: &dyn Storage) -> Result<Response, ContractError>;
|
||||
|
||||
fn track_unbond(&self, amount: Coin, storage: &mut dyn Storage) -> Result<(), ContractError>;
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
pub mod bonding_account;
|
||||
pub mod bonding;
|
||||
pub mod delegating_account;
|
||||
pub mod vesting_account;
|
||||
|
||||
pub use self::bonding_account::BondingAccount;
|
||||
pub use self::bonding::{GatewayBondingAccount, MixnodeBondingAccount};
|
||||
pub use self::delegating_account::DelegatingAccount;
|
||||
pub use self::vesting_account::VestingAccount;
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
use super::BondData;
|
||||
use crate::errors::ContractError;
|
||||
use crate::traits::GatewayBondingAccount;
|
||||
use config::defaults::DEFAULT_MIXNET_CONTRACT_ADDRESS;
|
||||
use cosmwasm_std::{wasm_execute, Coin, Env, Response, Storage, Uint128};
|
||||
use mixnet_contract::{ExecuteMsg as MixnetExecuteMsg, Gateway};
|
||||
|
||||
use super::Account;
|
||||
|
||||
impl GatewayBondingAccount for Account {
|
||||
fn try_bond_gateway(
|
||||
&self,
|
||||
gateway: Gateway,
|
||||
bond: Coin,
|
||||
env: &Env,
|
||||
storage: &mut dyn Storage,
|
||||
) -> Result<Response, ContractError> {
|
||||
let current_balance = self.load_balance(storage)?;
|
||||
|
||||
if current_balance < bond.amount {
|
||||
return Err(ContractError::InsufficientBalance(
|
||||
self.address.as_str().to_string(),
|
||||
current_balance.u128(),
|
||||
));
|
||||
}
|
||||
|
||||
let bond_data = if let Some(_bond) = self.load_gateway_bond(storage)? {
|
||||
return Err(ContractError::AlreadyBonded(
|
||||
self.address.as_str().to_string(),
|
||||
));
|
||||
} else {
|
||||
BondData {
|
||||
block_time: env.block.time,
|
||||
amount: bond.amount,
|
||||
}
|
||||
};
|
||||
|
||||
let msg = MixnetExecuteMsg::BondGatewayOnBehalf {
|
||||
gateway,
|
||||
owner: self.address().into_string(),
|
||||
};
|
||||
|
||||
let new_balance = Uint128::new(current_balance.u128() - bond.amount.u128());
|
||||
|
||||
let bond_mixnode_mag = wasm_execute(DEFAULT_MIXNET_CONTRACT_ADDRESS, &msg, vec![bond])?;
|
||||
|
||||
self.save_balance(new_balance, storage)?;
|
||||
self.save_gateway_bond(bond_data, storage)?;
|
||||
|
||||
Ok(Response::new()
|
||||
.add_attribute("action", "bond gateway on behalf")
|
||||
.add_message(bond_mixnode_mag))
|
||||
}
|
||||
|
||||
fn try_unbond_gateway(&self, storage: &dyn Storage) -> Result<Response, ContractError> {
|
||||
let msg = MixnetExecuteMsg::UnbondGatewayOnBehalf {
|
||||
owner: self.address().into_string(),
|
||||
};
|
||||
|
||||
if let Some(_bond) = self.load_gateway_bond(storage)? {
|
||||
let unbond_msg = wasm_execute(DEFAULT_MIXNET_CONTRACT_ADDRESS, &msg, vec![])?;
|
||||
|
||||
Ok(Response::new()
|
||||
.add_attribute("action", "unbond gateway on behalf")
|
||||
.add_message(unbond_msg))
|
||||
} else {
|
||||
Err(ContractError::NoBondFound(
|
||||
self.address.as_str().to_string(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
fn try_track_unbond_gateway(
|
||||
&self,
|
||||
amount: Coin,
|
||||
storage: &mut dyn Storage,
|
||||
) -> Result<(), ContractError> {
|
||||
let new_balance = Uint128::new(self.load_balance(storage)?.u128() + amount.amount.u128());
|
||||
self.save_balance(new_balance, storage)?;
|
||||
|
||||
self.remove_gateway_bond(storage)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
+11
-7
@@ -1,13 +1,13 @@
|
||||
use super::BondData;
|
||||
use crate::errors::ContractError;
|
||||
use crate::traits::BondingAccount;
|
||||
use crate::traits::MixnodeBondingAccount;
|
||||
use config::defaults::DEFAULT_MIXNET_CONTRACT_ADDRESS;
|
||||
use cosmwasm_std::{wasm_execute, Coin, Env, Response, Storage, Uint128};
|
||||
use mixnet_contract::{ExecuteMsg as MixnetExecuteMsg, MixNode};
|
||||
|
||||
use super::Account;
|
||||
|
||||
impl BondingAccount for Account {
|
||||
impl MixnodeBondingAccount for Account {
|
||||
fn try_bond_mixnode(
|
||||
&self,
|
||||
mix_node: MixNode,
|
||||
@@ -24,7 +24,7 @@ impl BondingAccount for Account {
|
||||
));
|
||||
}
|
||||
|
||||
let bond_data = if let Some(_bond) = self.load_bond(storage)? {
|
||||
let bond_data = if let Some(_bond) = self.load_mixnode_bond(storage)? {
|
||||
return Err(ContractError::AlreadyBonded(
|
||||
self.address.as_str().to_string(),
|
||||
));
|
||||
@@ -45,7 +45,7 @@ impl BondingAccount for Account {
|
||||
let bond_mixnode_mag = wasm_execute(DEFAULT_MIXNET_CONTRACT_ADDRESS, &msg, vec![bond])?;
|
||||
|
||||
self.save_balance(new_balance, storage)?;
|
||||
self.save_bond(bond_data, storage)?;
|
||||
self.save_mixnode_bond(bond_data, storage)?;
|
||||
|
||||
Ok(Response::new()
|
||||
.add_attribute("action", "bond mixnode on behalf")
|
||||
@@ -57,7 +57,7 @@ impl BondingAccount for Account {
|
||||
owner: self.address().into_string(),
|
||||
};
|
||||
|
||||
if let Some(_bond) = self.load_bond(storage)? {
|
||||
if let Some(_bond) = self.load_mixnode_bond(storage)? {
|
||||
let unbond_msg = wasm_execute(DEFAULT_MIXNET_CONTRACT_ADDRESS, &msg, vec![])?;
|
||||
|
||||
Ok(Response::new()
|
||||
@@ -70,11 +70,15 @@ impl BondingAccount for Account {
|
||||
}
|
||||
}
|
||||
|
||||
fn track_unbond(&self, amount: Coin, storage: &mut dyn Storage) -> Result<(), ContractError> {
|
||||
fn try_track_unbond_mixnode(
|
||||
&self,
|
||||
amount: Coin,
|
||||
storage: &mut dyn Storage,
|
||||
) -> Result<(), ContractError> {
|
||||
let new_balance = Uint128::new(self.load_balance(storage)?.u128() + amount.amount.u128());
|
||||
self.save_balance(new_balance, storage)?;
|
||||
|
||||
self.remove_bond(storage)?;
|
||||
self.remove_mixnode_bond(storage)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -8,13 +8,15 @@ use mixnet_contract::IdentityKey;
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
mod bonding_account;
|
||||
mod delegating_account;
|
||||
mod gateway_bonding_account;
|
||||
mod mixnode_bonding_account;
|
||||
mod vesting_account;
|
||||
|
||||
const DELEGATIONS_SUFFIX: &str = "de";
|
||||
const BALANCE_SUFFIX: &str = "ba";
|
||||
const BOND_SUFFIX: &str = "bo";
|
||||
const GATEWAY_SUFFIX: &str = "ga";
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
|
||||
pub struct Account {
|
||||
@@ -24,7 +26,8 @@ pub struct Account {
|
||||
coin: Coin,
|
||||
delegations_key: String,
|
||||
balance_key: String,
|
||||
bond_key: String,
|
||||
mixnode_bond_key: String,
|
||||
gateway_bond_key: String,
|
||||
}
|
||||
|
||||
impl Account {
|
||||
@@ -43,7 +46,8 @@ impl Account {
|
||||
coin,
|
||||
delegations_key: format!("{}_{}", address, DELEGATIONS_SUFFIX),
|
||||
balance_key: format!("{}_{}", address, BALANCE_SUFFIX),
|
||||
bond_key: format!("{}_{}", address, BOND_SUFFIX),
|
||||
mixnode_bond_key: format!("{}_{}", address, BOND_SUFFIX),
|
||||
gateway_bond_key: format!("{}_{}", address, GATEWAY_SUFFIX),
|
||||
};
|
||||
save_account(&account, storage)?;
|
||||
account.save_balance(amount, storage)?;
|
||||
@@ -114,25 +118,52 @@ impl Account {
|
||||
Item::new(self.balance_key.as_ref())
|
||||
}
|
||||
|
||||
pub fn load_bond(&self, storage: &dyn Storage) -> Result<Option<BondData>, ContractError> {
|
||||
Ok(self.bond().may_load(storage)?)
|
||||
pub fn load_mixnode_bond(
|
||||
&self,
|
||||
storage: &dyn Storage,
|
||||
) -> Result<Option<BondData>, ContractError> {
|
||||
Ok(self.mixnode_bond().may_load(storage)?)
|
||||
}
|
||||
|
||||
pub fn save_bond(
|
||||
pub fn save_mixnode_bond(
|
||||
&self,
|
||||
bond: BondData,
|
||||
storage: &mut dyn Storage,
|
||||
) -> Result<(), ContractError> {
|
||||
Ok(self.bond().save(storage, &bond)?)
|
||||
Ok(self.mixnode_bond().save(storage, &bond)?)
|
||||
}
|
||||
|
||||
pub fn remove_bond(&self, storage: &mut dyn Storage) -> Result<(), ContractError> {
|
||||
self.bond().remove(storage);
|
||||
pub fn remove_mixnode_bond(&self, storage: &mut dyn Storage) -> Result<(), ContractError> {
|
||||
self.mixnode_bond().remove(storage);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn bond(&self) -> Item<BondData> {
|
||||
Item::new(self.bond_key.as_ref())
|
||||
fn mixnode_bond(&self) -> Item<BondData> {
|
||||
Item::new(self.mixnode_bond_key.as_ref())
|
||||
}
|
||||
|
||||
pub fn load_gateway_bond(
|
||||
&self,
|
||||
storage: &dyn Storage,
|
||||
) -> Result<Option<BondData>, ContractError> {
|
||||
Ok(self.gateway_bond().may_load(storage)?)
|
||||
}
|
||||
|
||||
pub fn save_gateway_bond(
|
||||
&self,
|
||||
bond: BondData,
|
||||
storage: &mut dyn Storage,
|
||||
) -> Result<(), ContractError> {
|
||||
Ok(self.gateway_bond().save(storage, &bond)?)
|
||||
}
|
||||
|
||||
pub fn remove_gateway_bond(&self, storage: &mut dyn Storage) -> Result<(), ContractError> {
|
||||
self.gateway_bond().remove(storage);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn gateway_bond(&self) -> Item<BondData> {
|
||||
Item::new(self.gateway_bond_key.as_ref())
|
||||
}
|
||||
|
||||
fn delegations(&self) -> Map<(&[u8], u64), Uint128> {
|
||||
|
||||
@@ -163,7 +163,10 @@ impl VestingAccount for Account {
|
||||
let max_vested = self.tokens_per_period()? * period as u128;
|
||||
let start_time = self.periods[period].start_time;
|
||||
|
||||
let amount = if let Some(bond) = self.load_bond(storage)? {
|
||||
let amount = if let Some(bond) = self
|
||||
.load_mixnode_bond(storage)?
|
||||
.or(self.load_gateway_bond(storage)?)
|
||||
{
|
||||
if bond.block_time.seconds() < start_time {
|
||||
bond.amount
|
||||
} else {
|
||||
@@ -190,7 +193,10 @@ impl VestingAccount for Account {
|
||||
let block_time = block_time.unwrap_or(env.block.time);
|
||||
let bonded_free = self.get_bonded_free(Some(block_time), env, storage)?;
|
||||
|
||||
if let Some(bond) = self.load_bond(storage)? {
|
||||
if let Some(bond) = self
|
||||
.load_mixnode_bond(storage)?
|
||||
.or(self.load_gateway_bond(storage)?)
|
||||
{
|
||||
let amount = bond.amount - bonded_free.amount;
|
||||
Ok(Coin {
|
||||
amount,
|
||||
|
||||
@@ -39,13 +39,13 @@ mod tests {
|
||||
use crate::contract::{NUM_VESTING_PERIODS, VESTING_PERIOD};
|
||||
use crate::storage::load_account;
|
||||
use crate::support::tests::helpers::{init_contract, vesting_account_fixture};
|
||||
use crate::traits::BondingAccount;
|
||||
use crate::traits::DelegatingAccount;
|
||||
use crate::traits::VestingAccount;
|
||||
use crate::traits::{GatewayBondingAccount, MixnodeBondingAccount};
|
||||
use config::defaults::DENOM;
|
||||
use cosmwasm_std::testing::mock_env;
|
||||
use cosmwasm_std::{Addr, Coin, Timestamp, Uint128};
|
||||
use mixnet_contract::MixNode;
|
||||
use mixnet_contract::{Gateway, MixNode};
|
||||
|
||||
#[test]
|
||||
fn test_account_creation() {
|
||||
@@ -234,7 +234,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_bonds() {
|
||||
fn test_mixnode_bonds() {
|
||||
let mut deps = init_contract();
|
||||
let env = mock_env();
|
||||
|
||||
@@ -287,7 +287,117 @@ mod tests {
|
||||
);
|
||||
assert!(err.is_err());
|
||||
|
||||
let bond = account.load_bond(&deps.storage).unwrap().unwrap();
|
||||
let bond = account.load_mixnode_bond(&deps.storage).unwrap().unwrap();
|
||||
assert_eq!(Uint128::new(500_000_000_000), bond.amount);
|
||||
|
||||
// Current period -> block_time: None
|
||||
let bonded_free = account.get_bonded_free(None, &env, &deps.storage).unwrap();
|
||||
assert_eq!(Uint128::new(0), bonded_free.amount);
|
||||
|
||||
let bonded_vesting = account
|
||||
.get_bonded_vesting(None, &env, &deps.storage)
|
||||
.unwrap();
|
||||
assert_eq!(bond.amount - bonded_free.amount, bonded_vesting.amount);
|
||||
|
||||
// All periods
|
||||
for (i, period) in account.periods().iter().enumerate() {
|
||||
let bonded_free = account
|
||||
.get_bonded_free(
|
||||
Some(Timestamp::from_seconds(period.start_time + 1)),
|
||||
&env,
|
||||
&deps.storage,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
(account.tokens_per_period().unwrap() * i as u128).min(bond.amount.u128()),
|
||||
bonded_free.amount.u128()
|
||||
);
|
||||
|
||||
let bonded_vesting = account
|
||||
.get_bonded_vesting(
|
||||
Some(Timestamp::from_seconds(period.start_time + 1)),
|
||||
&env,
|
||||
&deps.storage,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(bond.amount - bonded_free.amount, bonded_vesting.amount);
|
||||
}
|
||||
|
||||
let bonded_free = account
|
||||
.get_bonded_free(
|
||||
Some(Timestamp::from_seconds(1764416964)),
|
||||
&env,
|
||||
&deps.storage,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(bond.amount, bonded_free.amount);
|
||||
|
||||
let bonded_vesting = account
|
||||
.get_bonded_vesting(
|
||||
Some(Timestamp::from_seconds(1764416964)),
|
||||
&env,
|
||||
&deps.storage,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(Uint128::zero(), bonded_vesting.amount);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_gateway_bonds() {
|
||||
let mut deps = init_contract();
|
||||
let env = mock_env();
|
||||
|
||||
let account = vesting_account_fixture(&mut deps.storage, &env);
|
||||
|
||||
let gateway = Gateway {
|
||||
host: "1.1.1.1".to_string(),
|
||||
mix_port: 1789,
|
||||
clients_port: 9000,
|
||||
location: "Sweden".to_string(),
|
||||
sphinx_key: "sphinx".to_string(),
|
||||
identity_key: "identity".to_string(),
|
||||
version: "0.10.0".to_string(),
|
||||
};
|
||||
|
||||
// Try delegating too much
|
||||
let err = account.try_bond_gateway(
|
||||
gateway.clone(),
|
||||
Coin {
|
||||
amount: Uint128::new(1_000_000_000_001),
|
||||
denom: DENOM.to_string(),
|
||||
},
|
||||
&env,
|
||||
&mut deps.storage,
|
||||
);
|
||||
assert!(err.is_err());
|
||||
|
||||
let ok = account.try_bond_gateway(
|
||||
gateway.clone(),
|
||||
Coin {
|
||||
amount: Uint128::new(500_000_000_000),
|
||||
denom: DENOM.to_string(),
|
||||
},
|
||||
&env,
|
||||
&mut deps.storage,
|
||||
);
|
||||
assert!(ok.is_ok());
|
||||
|
||||
let balance = account.load_balance(&deps.storage).unwrap();
|
||||
assert_eq!(balance, Uint128::new(500_000_000_000));
|
||||
|
||||
// Try delegating too much again
|
||||
let err = account.try_bond_gateway(
|
||||
gateway,
|
||||
Coin {
|
||||
amount: Uint128::new(500_000_000_001),
|
||||
denom: DENOM.to_string(),
|
||||
},
|
||||
&env,
|
||||
&mut deps.storage,
|
||||
);
|
||||
assert!(err.is_err());
|
||||
|
||||
let bond = account.load_gateway_bond(&deps.storage).unwrap().unwrap();
|
||||
assert_eq!(Uint128::new(500_000_000_000), bond.amount);
|
||||
|
||||
// Current period -> block_time: None
|
||||
|
||||
Reference in New Issue
Block a user