From 1f42ce57e37fa69ecd7bacd1139dfd80b72c8c4e Mon Sep 17 00:00:00 2001 From: Drazen Urch Date: Thu, 2 Dec 2021 18:16:26 +0100 Subject: [PATCH] Allow proxy gateway bonding (#936) * Allow proxy gateway bonding * Update msg text Co-authored-by: Drazen Urch --- common/mixnet-contract/src/gateway.rs | 15 ++- common/mixnet-contract/src/msg.rs | 7 ++ contracts/mixnet/src/contract.rs | 8 ++ contracts/mixnet/src/gateways/storage.rs | 1 + contracts/mixnet/src/gateways/transactions.rs | 107 +++++++++++++--- contracts/mixnet/src/mixnodes/transactions.rs | 2 +- contracts/mixnet/src/support/tests.rs | 8 +- contracts/vesting/src/contract.rs | 49 +++++++- contracts/vesting/src/messages.rs | 12 +- contracts/vesting/src/traits/bonding.rs | 39 ++++++ .../vesting/src/traits/bonding_account.rs | 17 --- contracts/vesting/src/traits/mod.rs | 4 +- .../account/gateway_bonding_account.rs | 84 +++++++++++++ ..._account.rs => mixnode_bonding_account.rs} | 18 +-- contracts/vesting/src/vesting/account/mod.rs | 53 ++++++-- .../src/vesting/account/vesting_account.rs | 10 +- contracts/vesting/src/vesting/mod.rs | 118 +++++++++++++++++- 17 files changed, 481 insertions(+), 71 deletions(-) create mode 100644 contracts/vesting/src/traits/bonding.rs delete mode 100644 contracts/vesting/src/traits/bonding_account.rs create mode 100644 contracts/vesting/src/vesting/account/gateway_bonding_account.rs rename contracts/vesting/src/vesting/account/{bonding_account.rs => mixnode_bonding_account.rs} (83%) diff --git a/common/mixnet-contract/src/gateway.rs b/common/mixnet-contract/src/gateway.rs index 6e2cde6826..93a6ab170d 100644 --- a/common/mixnet-contract/src/gateway.rs +++ b/common/mixnet-contract/src/gateway.rs @@ -27,15 +27,23 @@ pub struct GatewayBond { pub owner: Addr, pub block_height: u64, pub gateway: Gateway, + pub proxy: Option, } 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, + ) -> 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: diff --git a/common/mixnet-contract/src/msg.rs b/common/mixnet-contract/src/msg.rs index 690e13542c..22f9726a25 100644 --- a/common/mixnet-contract/src/msg.rs +++ b/common/mixnet-contract/src/msg.rs @@ -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)] diff --git a/contracts/mixnet/src/contract.rs b/contracts/mixnet/src/contract.rs index 6743847e2f..b516e3e4b5 100644 --- a/contracts/mixnet/src/contract.rs +++ b/contracts/mixnet/src/contract.rs @@ -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) + } } } diff --git a/contracts/mixnet/src/gateways/storage.rs b/contracts/mixnet/src/gateways/storage.rs index 67ee20dc19..da80c9e6db 100644 --- a/contracts/mixnet/src/gateways/storage.rs +++ b/contracts/mixnet/src/gateways/storage.rs @@ -93,6 +93,7 @@ mod tests { identity_key: node_identity.clone(), ..test_helpers::gateway_fixture() }, + proxy: None, }; storage::gateways() diff --git a/contracts/mixnet/src/gateways/transactions.rs b/contracts/mixnet/src/gateways/transactions.rs index c9a4a55180..7aa16c5e60 100644 --- a/contracts/mixnet/src/gateways/transactions.rs +++ b/contracts/mixnet/src/gateways/transactions.rs @@ -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 { + // 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 { + // 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, +) -> Result { + 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 { + let proxy = info.sender; + _try_remove_gateway(deps, &owner, Some(proxy)) +} + +pub fn try_remove_gateway(deps: DepsMut, info: MessageInfo) -> Result { + _try_remove_gateway(deps, info.sender.as_ref(), None) +} + +pub(crate) fn _try_remove_gateway( + deps: DepsMut, + owner: &str, + proxy: Option, +) -> Result { + 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( diff --git a/contracts/mixnet/src/mixnodes/transactions.rs b/contracts/mixnet/src/mixnodes/transactions.rs index b8c6e2d6e1..c83d4518fe 100644 --- a/contracts/mixnet/src/mixnodes/transactions.rs +++ b/contracts/mixnet/src/mixnodes/transactions.rs @@ -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, }; diff --git a/contracts/mixnet/src/support/tests.rs b/contracts/mixnet/src/support/tests.rs index 9ecab2b943..a6ad0ac881 100644 --- a/contracts/mixnet/src/support/tests.rs +++ b/contracts/mixnet/src/support/tests.rs @@ -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( diff --git a/contracts/vesting/src/contract.rs b/contracts/vesting/src/contract.rs index 8b3fdc9ac8..8a26888121 100644 --- a/contracts/vesting/src/contract.rs +++ b/contracts/vesting/src/contract.rs @@ -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 { + 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 { + 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 { + 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 Result; + + fn try_unbond_mixnode(&self, storage: &dyn Storage) -> Result; + + 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; + + fn try_unbond_gateway(&self, storage: &dyn Storage) -> Result; + + fn try_track_unbond_gateway( + &self, + amount: Coin, + storage: &mut dyn Storage, + ) -> Result<(), ContractError>; +} diff --git a/contracts/vesting/src/traits/bonding_account.rs b/contracts/vesting/src/traits/bonding_account.rs deleted file mode 100644 index b526534589..0000000000 --- a/contracts/vesting/src/traits/bonding_account.rs +++ /dev/null @@ -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; - - fn try_unbond_mixnode(&self, storage: &dyn Storage) -> Result; - - fn track_unbond(&self, amount: Coin, storage: &mut dyn Storage) -> Result<(), ContractError>; -} diff --git a/contracts/vesting/src/traits/mod.rs b/contracts/vesting/src/traits/mod.rs index 87587c7dde..961198a443 100644 --- a/contracts/vesting/src/traits/mod.rs +++ b/contracts/vesting/src/traits/mod.rs @@ -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; diff --git a/contracts/vesting/src/vesting/account/gateway_bonding_account.rs b/contracts/vesting/src/vesting/account/gateway_bonding_account.rs new file mode 100644 index 0000000000..f3f613620b --- /dev/null +++ b/contracts/vesting/src/vesting/account/gateway_bonding_account.rs @@ -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 { + 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 { + 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(()) + } +} diff --git a/contracts/vesting/src/vesting/account/bonding_account.rs b/contracts/vesting/src/vesting/account/mixnode_bonding_account.rs similarity index 83% rename from contracts/vesting/src/vesting/account/bonding_account.rs rename to contracts/vesting/src/vesting/account/mixnode_bonding_account.rs index 625ef3fd49..84cfc2e070 100644 --- a/contracts/vesting/src/vesting/account/bonding_account.rs +++ b/contracts/vesting/src/vesting/account/mixnode_bonding_account.rs @@ -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(()) } } diff --git a/contracts/vesting/src/vesting/account/mod.rs b/contracts/vesting/src/vesting/account/mod.rs index 998f2e4fe3..e4511b03a6 100644 --- a/contracts/vesting/src/vesting/account/mod.rs +++ b/contracts/vesting/src/vesting/account/mod.rs @@ -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, ContractError> { - Ok(self.bond().may_load(storage)?) + pub fn load_mixnode_bond( + &self, + storage: &dyn Storage, + ) -> Result, 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 { - Item::new(self.bond_key.as_ref()) + fn mixnode_bond(&self) -> Item { + Item::new(self.mixnode_bond_key.as_ref()) + } + + pub fn load_gateway_bond( + &self, + storage: &dyn Storage, + ) -> Result, 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 { + Item::new(self.gateway_bond_key.as_ref()) } fn delegations(&self) -> Map<(&[u8], u64), Uint128> { diff --git a/contracts/vesting/src/vesting/account/vesting_account.rs b/contracts/vesting/src/vesting/account/vesting_account.rs index 29c254c56a..2873440144 100644 --- a/contracts/vesting/src/vesting/account/vesting_account.rs +++ b/contracts/vesting/src/vesting/account/vesting_account.rs @@ -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, diff --git a/contracts/vesting/src/vesting/mod.rs b/contracts/vesting/src/vesting/mod.rs index e12d6fa59f..acb34610ec 100644 --- a/contracts/vesting/src/vesting/mod.rs +++ b/contracts/vesting/src/vesting/mod.rs @@ -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