diff --git a/common/mixnet-contract/src/gateway.rs b/common/mixnet-contract/src/gateway.rs index 600842274e..301a011278 100644 --- a/common/mixnet-contract/src/gateway.rs +++ b/common/mixnet-contract/src/gateway.rs @@ -109,3 +109,9 @@ impl PagedGatewayResponse { } } } + +#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)] +pub struct GatewayOwnershipResponse { + pub address: HumanAddr, + pub has_gateway: bool, +} diff --git a/common/mixnet-contract/src/lib.rs b/common/mixnet-contract/src/lib.rs index 18a831b6ce..0ffefd312d 100644 --- a/common/mixnet-contract/src/lib.rs +++ b/common/mixnet-contract/src/lib.rs @@ -2,5 +2,5 @@ mod gateway; mod mixnode; pub use cosmwasm_std::{Coin, HumanAddr}; -pub use gateway::{Gateway, GatewayBond, PagedGatewayResponse}; -pub use mixnode::{MixNode, MixNodeBond, PagedResponse}; +pub use gateway::{Gateway, GatewayBond, GatewayOwnershipResponse, PagedGatewayResponse}; +pub use mixnode::{MixNode, MixNodeBond, MixOwnershipResponse, PagedResponse}; diff --git a/common/mixnet-contract/src/mixnode.rs b/common/mixnet-contract/src/mixnode.rs index 8e9de8eb4e..b28b2f8f3e 100644 --- a/common/mixnet-contract/src/mixnode.rs +++ b/common/mixnet-contract/src/mixnode.rs @@ -105,3 +105,9 @@ impl PagedResponse { } } } + +#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)] +pub struct MixOwnershipResponse { + pub address: HumanAddr, + pub has_node: bool, +} diff --git a/contracts/mixnet/src/contract.rs b/contracts/mixnet/src/contract.rs index a4691c3c84..2c30682d4f 100644 --- a/contracts/mixnet/src/contract.rs +++ b/contracts/mixnet/src/contract.rs @@ -1,5 +1,7 @@ use crate::msg::{HandleMsg, InitMsg, QueryMsg}; -use crate::queries::{query_gateways_paged, query_mixnodes_paged}; +use crate::queries::{ + query_gateways_paged, query_mixnodes_paged, query_owns_gateway, query_owns_mixnode, +}; use crate::state::{config, gateways, gateways_read, State}; use crate::{error::ContractError, state::mixnodes, state::mixnodes_read}; use cosmwasm_std::{ @@ -200,6 +202,8 @@ pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { QueryMsg::GetGateways { limit, start_after } => { to_binary(&query_gateways_paged(deps, start_after, limit)?) } + QueryMsg::OwnsMixnode { address } => to_binary(&query_owns_mixnode(deps, address)?), + QueryMsg::OwnsGateway { address } => to_binary(&query_owns_gateway(deps, address)?), } } diff --git a/contracts/mixnet/src/msg.rs b/contracts/mixnet/src/msg.rs index d8c20aeaad..341592162a 100644 --- a/contracts/mixnet/src/msg.rs +++ b/contracts/mixnet/src/msg.rs @@ -26,4 +26,10 @@ pub enum QueryMsg { start_after: Option, limit: Option, }, + OwnsMixnode { + address: HumanAddr, + }, + OwnsGateway { + address: HumanAddr, + }, } diff --git a/contracts/mixnet/src/queries.rs b/contracts/mixnet/src/queries.rs index efdc9ef2a4..e7937e84da 100644 --- a/contracts/mixnet/src/queries.rs +++ b/contracts/mixnet/src/queries.rs @@ -1,11 +1,14 @@ // settings for pagination -use crate::state::{gateways_read, PREFIX_MIXNODES}; +use crate::state::{gateways_read, mixnodes_read, PREFIX_MIXNODES}; use cosmwasm_std::Deps; use cosmwasm_std::HumanAddr; use cosmwasm_std::Order; use cosmwasm_std::StdResult; use cosmwasm_storage::bucket_read; -use mixnet_contract::{GatewayBond, MixNodeBond, PagedGatewayResponse, PagedResponse}; +use mixnet_contract::{ + GatewayBond, GatewayOwnershipResponse, MixNodeBond, MixOwnershipResponse, PagedGatewayResponse, + PagedResponse, +}; const MAX_LIMIT: u32 = 30; const DEFAULT_LIMIT: u32 = 10; @@ -52,6 +55,29 @@ pub(crate) fn query_gateways_paged( Ok(PagedGatewayResponse::new(nodes, limit, start_next_after)) } +pub(crate) fn query_owns_mixnode( + deps: Deps, + address: HumanAddr, +) -> StdResult { + let has_node = mixnodes_read(deps.storage) + .may_load(address.as_ref())? + .is_some(); + Ok(MixOwnershipResponse { address, has_node }) +} + +pub(crate) fn query_owns_gateway( + deps: Deps, + address: HumanAddr, +) -> StdResult { + let has_gateway = gateways_read(deps.storage) + .may_load(address.as_ref())? + .is_some(); + Ok(GatewayOwnershipResponse { + address, + has_gateway, + }) +} + /// Adds a 0 byte to terminate the `start_after` value given. This allows CosmWasm /// to get the succeeding key as the start of the next page. fn calculate_start_value( @@ -306,4 +332,68 @@ mod tests { // now we have 2 pages, with 2 results on the second page assert_eq!(2, page2.nodes.len()); } + + #[test] + fn query_for_mixnode_owner_works() { + let mut deps = helpers::init_contract(); + + // "fred" does not own a mixnode if there are no mixnodes + let res = query_owns_mixnode(deps.as_ref(), "fred".into()).unwrap(); + assert!(!res.has_node); + + // mixnode was added to "bob", "fred" still does not own one + let node = helpers::mixnode_bond_fixture(); + mixnodes(&mut deps.storage) + .save("bob".as_bytes(), &node) + .unwrap(); + + let res = query_owns_mixnode(deps.as_ref(), "fred".into()).unwrap(); + assert!(!res.has_node); + + // "fred" now owns a mixnode! + let node2 = helpers::mixnode_bond_fixture(); + mixnodes(&mut deps.storage) + .save("fred".as_bytes(), &node2) + .unwrap(); + + let res = query_owns_mixnode(deps.as_ref(), "fred".into()).unwrap(); + assert!(res.has_node); + + // but after unbonding it, he doesn't own one anymore + mixnodes(&mut deps.storage).remove("fred".as_bytes()); + let res = query_owns_mixnode(deps.as_ref(), "fred".into()).unwrap(); + assert!(!res.has_node); + } + + #[test] + fn query_for_gateway_owner_works() { + let mut deps = helpers::init_contract(); + + // "fred" does not own a mixnode if there are no mixnodes + let res = query_owns_gateway(deps.as_ref(), "fred".into()).unwrap(); + assert!(!res.has_gateway); + + // mixnode was added to "bob", "fred" still does not own one + let node = helpers::gateway_bond_fixture(); + gateways(&mut deps.storage) + .save("bob".as_bytes(), &node) + .unwrap(); + + let res = query_owns_gateway(deps.as_ref(), "fred".into()).unwrap(); + assert!(!res.has_gateway); + + // "fred" now owns a mixnode! + let node2 = helpers::gateway_bond_fixture(); + gateways(&mut deps.storage) + .save("fred".as_bytes(), &node2) + .unwrap(); + + let res = query_owns_gateway(deps.as_ref(), "fred".into()).unwrap(); + assert!(res.has_gateway); + + // but after unbonding it, he doesn't own one anymore + gateways(&mut deps.storage).remove("fred".as_bytes()); + let res = query_owns_gateway(deps.as_ref(), "fred".into()).unwrap(); + assert!(!res.has_gateway); + } }