Feature/bonding adjustments (#561)

* Made mixnode bonding process similar to that of gateways' + lowered bonding fee

* Adjusted bonding fee to 100

* Adjusted MixNode Display implementation for nicer formating

* Renamed bonding stake to just bond

* Renamed other cases where stake was referring to bond
This commit is contained in:
Jędrzej Stuczyński
2021-04-08 11:15:19 +01:00
committed by GitHub
parent f536449f0c
commit 3136346a01
4 changed files with 192 additions and 81 deletions
+9 -1
View File
@@ -81,7 +81,15 @@ impl Display for MixNodeBond {
// stream: `f`. Returns `fmt::Result` which indicates whether the
// operation succeeded or failed. Note that `write!` uses syntax which
// is very similar to `println!`.
write!(f, "amount: {:?}, owner: {}", self.amount, self.owner)
if self.amount.len() != 1 {
write!(f, "amount: {:?}, owner: {}", self.amount, self.owner)
} else {
write!(
f,
"amount: {} {}, owner: {}",
self.amount[0].amount, self.amount[0].denom, self.owner
)
}
}
}
+172 -71
View File
@@ -5,16 +5,19 @@ use crate::queries::{
use crate::state::{config, gateways, gateways_read, State};
use crate::{error::ContractError, state::mixnodes, state::mixnodes_read};
use cosmwasm_std::{
attr, coins, to_binary, BankMsg, Binary, Coin, Deps, DepsMut, Env, HandleResponse,
InitResponse, MessageInfo, MigrateResponse, StdResult, Uint128,
attr, to_binary, BankMsg, Binary, Coin, Deps, DepsMut, Env, HandleResponse, InitResponse,
MessageInfo, MigrateResponse, StdResult, Uint128,
};
use mixnet_contract::{Gateway, GatewayBond, MixNode, MixNodeBond};
/// Constant specifying minimum of coin required to bond a gateway
const GATEWAY_BONDING_STAKE: Uint128 = Uint128(1000_000000); // 1000 nym
const GATEWAY_BOND: Uint128 = Uint128(100_000000);
/// Constant specifying minimum of coin required to bond a mixnode
const MIXNODE_BOND: Uint128 = Uint128(100_000000);
/// Constant specifying denomination of the coin used for bonding
pub const STAKE_DENOM: &str = "uhal";
pub const DENOM: &str = "uhal";
/// Instantiate the contract.
///
@@ -47,26 +50,56 @@ pub fn handle(
}
}
fn validate_mixnode_bond(bond: &[Coin]) -> Result<(), ContractError> {
// check if anything was put as bond
if bond.is_empty() {
return Err(ContractError::NoBondFound);
}
if bond.len() > 1 {
// TODO: ask DH what would be an appropriate action here
}
// check that the denomination is correct
if bond[0].denom != DENOM {
return Err(ContractError::WrongDenom {});
}
// check that we have at least MIXNODE_BOND coins in our bond
if bond[0].amount < MIXNODE_BOND {
return Err(ContractError::InsufficientMixNodeBond {
received: bond[0].amount.into(),
minimum: GATEWAY_BOND.into(),
});
}
Ok(())
}
pub fn try_add_mixnode(
deps: DepsMut,
info: MessageInfo,
mix_node: MixNode,
) -> Result<HandleResponse, ContractError> {
let stake = &info.sent_funds[0];
// check that the denomination is correct
if stake.denom != STAKE_DENOM {
return Err(ContractError::WrongDenom {});
}
// check that we have at least 1000 nym in our bond
if stake.amount < coins(1000_000000, STAKE_DENOM)[0].amount {
return Err(ContractError::InsufficientMixNodeBond {});
}
validate_mixnode_bond(&info.sent_funds)?;
let bond = MixNodeBond::new(info.sent_funds, info.sender.clone(), mix_node);
mixnodes(deps.storage).save(info.sender.as_bytes(), &bond)?;
Ok(HandleResponse::default())
let sender_bytes = info.sender.as_bytes();
let was_present = mixnodes_read(deps.storage)
.may_load(sender_bytes)?
.is_some();
// TODO: do attributes also go back to the client or does this need to be put into `data`?
let attributes = vec![attr("overwritten", was_present)];
mixnodes(deps.storage).save(sender_bytes, &bond)?;
Ok(HandleResponse {
messages: vec![],
attributes,
data: None,
})
}
fn try_remove_mixnode(
@@ -101,26 +134,26 @@ fn try_remove_mixnode(
})
}
fn validate_gateway_stake(stake: &[Coin]) -> Result<(), ContractError> {
// check if anything was put as stake
if stake.is_empty() {
return Err(ContractError::NoStakeFound);
fn validate_gateway_bond(bond: &[Coin]) -> Result<(), ContractError> {
// check if anything was put as bond
if bond.is_empty() {
return Err(ContractError::NoBondFound);
}
if stake.len() > 1 {
if bond.len() > 1 {
// TODO: ask DH what would be an appropriate action here
}
// check that the denomination is correct
if stake[0].denom != STAKE_DENOM {
if bond[0].denom != DENOM {
return Err(ContractError::WrongDenom {});
}
// check that we have at least 1000 nym in our bond
if stake[0].amount < GATEWAY_BONDING_STAKE {
// check that we have at least 100 coins in our bond
if bond[0].amount < GATEWAY_BOND {
return Err(ContractError::InsufficientGatewayBond {
received: stake[0].amount.into(),
minimum: GATEWAY_BONDING_STAKE.into(),
received: bond[0].amount.into(),
minimum: GATEWAY_BOND.into(),
});
}
@@ -132,7 +165,7 @@ pub(crate) fn try_add_gateway(
info: MessageInfo,
gateway: Gateway,
) -> Result<HandleResponse, ContractError> {
validate_gateway_stake(&info.sent_funds)?;
validate_gateway_bond(&info.sent_funds)?;
let bond = GatewayBond::new(info.sent_funds, info.sender.clone(), gateway);
@@ -250,24 +283,74 @@ pub mod tests {
// Contract balance should match what we initialized it as
assert_eq!(
coins(0, STAKE_DENOM),
coins(0, DENOM),
query_contract_balance(env.contract.address, deps)
);
}
fn good_mixnode_bond() -> Vec<Coin> {
vec![Coin {
denom: DENOM.to_string(),
amount: MIXNODE_BOND,
}]
}
#[test]
fn validating_mixnode_bond() {
// you must send SOME funds
let result = validate_mixnode_bond(&[]);
assert_eq!(result, Err(ContractError::NoBondFound));
// you must send at least 100 coins...
let mut bond = good_mixnode_bond();
bond[0].amount = (MIXNODE_BOND - Uint128(1)).unwrap();
let result = validate_mixnode_bond(&bond);
assert_eq!(
result,
Err(ContractError::InsufficientMixNodeBond {
received: Into::<u128>::into(MIXNODE_BOND) - 1,
minimum: MIXNODE_BOND.into(),
})
);
// more than that is still fine
let mut bond = good_mixnode_bond();
bond[0].amount = MIXNODE_BOND + Uint128(1);
let result = validate_mixnode_bond(&bond);
assert!(result.is_ok());
// it must be sent in the defined denom!
let mut bond = good_mixnode_bond();
bond[0].denom = "baddenom".to_string();
let result = validate_mixnode_bond(&bond);
assert_eq!(result, Err(ContractError::WrongDenom {}));
let mut bond = good_mixnode_bond();
bond[0].denom = "foomp".to_string();
let result = validate_mixnode_bond(&bond);
assert_eq!(result, Err(ContractError::WrongDenom {}));
}
#[test]
fn mixnode_add() {
let mut deps = helpers::init_contract();
// if we don't send enough funds
let info = mock_info("anyone", &coins(999_999999, STAKE_DENOM));
let insufficient_bond = Into::<u128>::into(MIXNODE_BOND) - 1;
let info = mock_info("anyone", &coins(insufficient_bond, DENOM));
let msg = HandleMsg::RegisterMixnode {
mix_node: helpers::mix_node_fixture(),
};
// we are informed that we didn't send enough funds
let result = handle(deps.as_mut(), mock_env(), info, msg);
assert_eq!(result, Err(ContractError::InsufficientMixNodeBond {}));
assert_eq!(
result,
Err(ContractError::InsufficientMixNodeBond {
received: insufficient_bond,
minimum: GATEWAY_BOND.into(),
})
);
// no mixnode was inserted into the topology
let res = query(
@@ -283,14 +366,14 @@ pub mod tests {
assert_eq!(0, page.nodes.len());
// if we send enough funds
let info = mock_info("anyone", &coins(1000_000000, STAKE_DENOM));
let info = mock_info("anyone", &coins(1000_000000, DENOM));
let msg = HandleMsg::RegisterMixnode {
mix_node: helpers::mix_node_fixture(),
};
// we get back a message telling us everything was OK
let handle_response = handle(deps.as_mut(), mock_env(), info, msg).unwrap();
assert_eq!(HandleResponse::default(), handle_response);
let handle_response = handle(deps.as_mut(), mock_env(), info, msg);
assert!(handle_response.is_ok());
// we can query topology and the new node is there
let query_response = query(
@@ -304,7 +387,25 @@ pub mod tests {
.unwrap();
let page: PagedResponse = from_binary(&query_response).unwrap();
assert_eq!(1, page.nodes.len());
assert_eq!(&helpers::mix_node_fixture(), page.nodes[0].mix_node())
assert_eq!(&helpers::mix_node_fixture(), page.nodes[0].mix_node());
// if there was already a mixnode bonded by particular user
let info = mock_info("foomper", &good_mixnode_bond());
let msg = HandleMsg::BondGateway {
gateway: helpers::gateway_fixture(),
};
let handle_response = handle(deps.as_mut(), mock_env(), info, msg).unwrap();
assert_eq!(handle_response.attributes[0], attr("overwritten", false));
let info = mock_info("foomper", &good_mixnode_bond());
let msg = HandleMsg::BondGateway {
gateway: helpers::gateway_fixture(),
};
// we get a log message about it (TODO: does it get back to the user?)
let handle_response = handle(deps.as_mut(), mock_env(), info, msg).unwrap();
assert_eq!(handle_response.attributes[0], attr("overwritten", true));
// adding another node from another account, but with the same IP, should fail (or we would have a weird state). Is that right? Think about this, not sure yet.
// if we attempt to register a second node from the same address, should we get an error? It would probably be polite.
@@ -319,7 +420,7 @@ pub mod tests {
init(deps.as_mut(), env.clone(), info, msg).unwrap();
// try un-registering when no nodes exist yet
let info = mock_info("anyone", &coins(999_9999, STAKE_DENOM));
let info = mock_info("anyone", &coins(999_9999, DENOM));
let msg = HandleMsg::UnRegisterMixnode {};
let result = handle(deps.as_mut(), mock_env(), info, msg);
@@ -327,10 +428,10 @@ pub mod tests {
assert_eq!(result, Err(ContractError::MixNodeBondNotFound {}));
// let's add a node owned by bob
helpers::add_mixnode("bob", coins(1000_000000, STAKE_DENOM), &mut deps);
helpers::add_mixnode("bob", coins(1000_000000, DENOM), &mut deps);
// attempt to un-register fred's node, which doesn't exist
let info = mock_info("fred", &coins(999_9999, STAKE_DENOM));
let info = mock_info("fred", &coins(999_9999, DENOM));
let msg = HandleMsg::UnRegisterMixnode {};
let result = handle(deps.as_mut(), mock_env(), info, msg);
assert_eq!(result, Err(ContractError::MixNodeBondNotFound {}));
@@ -351,14 +452,14 @@ pub mod tests {
assert_eq!("bob", first_node.owner());
// add a node owned by fred
let fred_bond = coins(1666_000000, STAKE_DENOM);
let fred_bond = good_mixnode_bond();
helpers::add_mixnode("fred", fred_bond.clone(), &mut deps);
// let's make sure we now have 2 nodes:
assert_eq!(2, helpers::get_mix_nodes(&mut deps).len());
// un-register fred's node
let info = mock_info("fred", &coins(999_9999, STAKE_DENOM));
let info = mock_info("fred", &coins(999_9999, DENOM));
let msg = HandleMsg::UnRegisterMixnode {};
let remove_fred = handle(deps.as_mut(), mock_env(), info.clone(), msg).unwrap();
@@ -367,7 +468,7 @@ pub mod tests {
attr("action", "unbond"),
attr(
"mixnode_bond",
"amount: [Coin { denom: \"uhal\", amount: Uint128(1666000000) }], owner: fred",
format!("amount: {} {}, owner: fred", MIXNODE_BOND, DENOM),
),
];
@@ -393,46 +494,46 @@ pub mod tests {
assert_eq!("bob", mix_node_bonds[0].owner());
}
fn good_gateway_stake() -> Vec<Coin> {
fn good_gateway_bond() -> Vec<Coin> {
vec![Coin {
denom: STAKE_DENOM.to_string(),
amount: GATEWAY_BONDING_STAKE,
denom: DENOM.to_string(),
amount: GATEWAY_BOND,
}]
}
#[test]
fn validating_gateway_stake() {
fn validating_gateway_bond() {
// you must send SOME funds
let result = validate_gateway_stake(&[]);
assert_eq!(result, Err(ContractError::NoStakeFound));
let result = validate_gateway_bond(&[]);
assert_eq!(result, Err(ContractError::NoBondFound));
// you must send at least 1000 nym...
let mut stake = good_gateway_stake();
stake[0].amount = (GATEWAY_BONDING_STAKE - Uint128(1)).unwrap();
let result = validate_gateway_stake(&stake);
// you must send at least 100 coins...
let mut bond = good_gateway_bond();
bond[0].amount = (GATEWAY_BOND - Uint128(1)).unwrap();
let result = validate_gateway_bond(&bond);
assert_eq!(
result,
Err(ContractError::InsufficientGatewayBond {
received: Into::<u128>::into(GATEWAY_BONDING_STAKE) - 1,
minimum: GATEWAY_BONDING_STAKE.into(),
received: Into::<u128>::into(GATEWAY_BOND) - 1,
minimum: GATEWAY_BOND.into(),
})
);
// more than that is still fine
let mut stake = good_gateway_stake();
stake[0].amount = GATEWAY_BONDING_STAKE + Uint128(1);
let result = validate_gateway_stake(&stake);
let mut bond = good_gateway_bond();
bond[0].amount = GATEWAY_BOND + Uint128(1);
let result = validate_gateway_bond(&bond);
assert!(result.is_ok());
// it must be sent as unym!
let mut stake = good_gateway_stake();
stake[0].denom = "nym".to_string();
let result = validate_gateway_stake(&stake);
// it must be sent in the defined denom!
let mut bond = good_gateway_bond();
bond[0].denom = "baddenom".to_string();
let result = validate_gateway_bond(&bond);
assert_eq!(result, Err(ContractError::WrongDenom {}));
let mut stake = good_gateway_stake();
stake[0].denom = "foomp".to_string();
let result = validate_gateway_stake(&stake);
let mut bond = good_gateway_bond();
bond[0].denom = "foomp".to_string();
let result = validate_gateway_bond(&bond);
assert_eq!(result, Err(ContractError::WrongDenom {}));
}
@@ -441,8 +542,8 @@ pub mod tests {
let mut deps = helpers::init_contract();
// if we fail validation (by say not sending enough funds
let insufficient_bond = Into::<u128>::into(GATEWAY_BONDING_STAKE) - 1;
let info = mock_info("anyone", &coins(insufficient_bond, STAKE_DENOM));
let insufficient_bond = Into::<u128>::into(GATEWAY_BOND) - 1;
let info = mock_info("anyone", &coins(insufficient_bond, DENOM));
let msg = HandleMsg::BondGateway {
gateway: helpers::gateway_fixture(),
};
@@ -453,7 +554,7 @@ pub mod tests {
result,
Err(ContractError::InsufficientGatewayBond {
received: insufficient_bond,
minimum: GATEWAY_BONDING_STAKE.into(),
minimum: GATEWAY_BOND.into(),
})
);
@@ -471,7 +572,7 @@ pub mod tests {
assert_eq!(0, page.nodes.len());
// if we send enough funds
let info = mock_info("anyone", &good_gateway_stake());
let info = mock_info("anyone", &good_gateway_bond());
let msg = HandleMsg::BondGateway {
gateway: helpers::gateway_fixture(),
};
@@ -495,7 +596,7 @@ pub mod tests {
assert_eq!(&helpers::gateway_fixture(), page.nodes[0].gateway());
// if there was already a gateway bonded by particular user
let info = mock_info("foomper", &good_gateway_stake());
let info = mock_info("foomper", &good_gateway_bond());
let msg = HandleMsg::BondGateway {
gateway: helpers::gateway_fixture(),
};
@@ -503,7 +604,7 @@ pub mod tests {
let handle_response = handle(deps.as_mut(), mock_env(), info, msg).unwrap();
assert_eq!(handle_response.attributes[0], attr("overwritten", false));
let info = mock_info("foomper", &good_gateway_stake());
let info = mock_info("foomper", &good_gateway_bond());
let msg = HandleMsg::BondGateway {
gateway: helpers::gateway_fixture(),
};
@@ -538,7 +639,7 @@ pub mod tests {
);
// let's add a node owned by bob
helpers::add_gateway("bob", good_gateway_stake(), &mut deps);
helpers::add_gateway("bob", good_gateway_bond(), &mut deps);
// attempt to unbond fred's node, which doesn't exist
let info = mock_info("fred", &[]);
@@ -559,7 +660,7 @@ pub mod tests {
assert_eq!("bob", first_node.owner());
// add a node owned by fred
let fred_bond = good_gateway_stake();
let fred_bond = good_gateway_bond();
helpers::add_gateway("fred", fred_bond.clone(), &mut deps);
// let's make sure we now have 2 nodes:
@@ -576,7 +677,7 @@ pub mod tests {
attr("address", "fred"),
attr(
"gateway_bond",
format!("amount: 1000000000 {}, owner: fred", STAKE_DENOM),
format!("amount: {} {}, owner: fred", GATEWAY_BOND, DENOM),
),
];
+7 -5
View File
@@ -1,4 +1,4 @@
use crate::contract::STAKE_DENOM;
use crate::contract::DENOM;
use cosmwasm_std::{HumanAddr, StdError};
use thiserror::Error;
@@ -11,8 +11,10 @@ pub enum ContractError {
#[error("{0}")]
Std(#[from] StdError),
#[error("Not enough funds sent for mixnode bond")]
InsufficientMixNodeBond {},
#[error(
"Not enough funds sent for mixnode bond. (received {received:?}, minimum {minimum:?})"
)]
InsufficientMixNodeBond { received: u128, minimum: u128 },
#[error("Account does not own any mixnode bonds")]
MixNodeBondNotFound {},
@@ -31,6 +33,6 @@ pub enum ContractError {
#[error("Wrong coin denomination, you must send {}", STAKE_DENOM)]
WrongDenom {},
#[error("No coin was sent for the staking, you must send {}", STAKE_DENOM)]
NoStakeFound,
#[error("No coin was sent for the bonding, you must send {}", STAKE_DENOM)]
NoBondFound,
}
+4 -4
View File
@@ -22,7 +22,7 @@ pub mod helpers {
Gateway, GatewayBond, MixNode, MixNodeBond, PagedGatewayResponse, PagedResponse,
};
use crate::contract::STAKE_DENOM;
use crate::contract::DENOM;
pub fn add_mixnode(
pubkey: &str,
@@ -105,7 +105,7 @@ pub mod helpers {
"aaaa".to_string(),
"0.10.0".to_string(),
);
MixNodeBond::new(coins(50, STAKE_DENOM), HumanAddr::from("foo"), mix_node)
MixNodeBond::new(coins(50, DENOM), HumanAddr::from("foo"), mix_node)
}
pub fn gateway_fixture() -> Gateway {
@@ -128,7 +128,7 @@ pub mod helpers {
"identity".to_string(),
"0.10.0".to_string(),
);
GatewayBond::new(coins(50, STAKE_DENOM), HumanAddr::from("foo"), gateway)
GatewayBond::new(coins(50, DENOM), HumanAddr::from("foo"), gateway)
}
pub fn query_contract_balance(
@@ -136,6 +136,6 @@ pub mod helpers {
deps: OwnedDeps<MockStorage, MockApi, MockQuerier>,
) -> Vec<Coin> {
let querier = deps.as_ref().querier;
vec![querier.query_balance(address, STAKE_DENOM).unwrap()]
vec![querier.query_balance(address, DENOM).unwrap()]
}
}