Sign when announcing service providers to the directory contract (#3459)

* create_payload and call from nym-cli

* Remove some commented out code

* wip

* Service announce now compiles

* Fix other compilation issues

* Move ServiceDetails into Service

* Move service_id inside Service type

* wip: start sorting out tests

* wip: sorting out testing

* wip: first announce test now works

* wip: more work on announce test

* Move nonce

* Add check for nonce

* Extract out some helpers to separate files

* reenable state::services tests

* wip: start going through the integration tests

* All integration tests reenabled

* Remove some unused stuff

* Iterate on integration tests

* More iteration on test setup

* Rename to test_setup.rs

* Add more tests specific to signing

* Tweak

* Another nonce test and reorg

* Rename to announce.rs and delete.rs

* Tidy

* Make some inner modules private

* Use IdentityKey alias

* Update nym-api contract cache

* Fix that nym-cli was asking for signing nonce from wrong contract

* Add sign comment to network-requester

* Uploaded updated service provider contract to qwerty

* Allow large enum variant

* lock files

* Remove dbg

* Move error.rs to service-provider common

* Update code for moving errors.rs to common crate

* Rename to SpContractError

* constants module not pub

* lock file

* rustfmt

* Move IdentityKey type to contract-common

* clippy
This commit is contained in:
Jon Häggblad
2023-06-05 10:32:58 +02:00
committed by GitHub
parent 019b3299f2
commit 87cb8a6b20
64 changed files with 1799 additions and 859 deletions
Generated
+22
View File
@@ -1201,6 +1201,20 @@ dependencies = [
"zeroize",
]
[[package]]
name = "cw-controllers"
version = "0.13.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4f0bc6019b4d3d81e11f5c384bcce7173e2210bd654d75c6c9668e12cca05dfa"
dependencies = [
"cosmwasm-std",
"cw-storage-plus",
"cw-utils",
"schemars",
"serde",
"thiserror",
]
[[package]]
name = "cw-storage-plus"
version = "0.13.4"
@@ -3268,6 +3282,7 @@ dependencies = [
"nym-name-service-common",
"nym-network-defaults",
"nym-service-provider-directory-common",
"nym-sphinx",
"nym-validator-client",
"nym-vesting-contract-common",
"rand 0.6.5",
@@ -3819,6 +3834,7 @@ version = "1.1.19"
dependencies = [
"async-file-watcher",
"async-trait",
"bs58",
"clap 4.2.7",
"dirs",
"futures",
@@ -3841,11 +3857,13 @@ dependencies = [
"nym-sphinx",
"nym-statistics-common",
"nym-task",
"nym-types",
"pretty_env_logger",
"publicsuffix",
"rand 0.7.3",
"reqwest",
"serde",
"serde_json",
"sqlx 0.6.3",
"tap",
"tempfile",
@@ -3970,8 +3988,12 @@ name = "nym-service-provider-directory-common"
version = "0.1.0"
dependencies = [
"cosmwasm-std",
"cw-controllers",
"cw-utils",
"nym-contracts-common",
"schemars",
"serde",
"thiserror",
]
[[package]]
+2 -1
View File
@@ -119,8 +119,9 @@ cosmwasm-derive = "=1.0.0"
cosmwasm-schema = "=1.0.0"
cosmwasm-std = "=1.0.0"
cosmwasm-storage = "=1.0.0"
cw-utils = "=0.13.4"
cw-controllers = "=0.13.4"
cw-storage-plus = "=0.13.4"
cw-utils = "=0.13.4"
cw2 = { version = "=0.13.4" }
cw3 = { version = "=0.13.4" }
cw3-fixed-multisig = { version = "=0.13.4" }
+9 -12
View File
@@ -186,6 +186,15 @@ pub fn get_client_address(
)
}
pub fn load_identity_keys(
pathfinder: &ClientKeyPathfinder,
) -> Result<identity::KeyPair, ClientCoreError> {
let identity_keypair: identity::KeyPair =
nym_pemstore::load_keypair(&pathfinder.identity_key_pair_path())
.tap_err(|_| log::error!("Failed to read stored identity key files"))?;
Ok(identity_keypair)
}
/// Get the client address by loading the keys from stored files.
pub fn get_client_address_from_stored_keys<T>(
config: &Config<T>,
@@ -193,18 +202,6 @@ pub fn get_client_address_from_stored_keys<T>(
where
T: nym_config::NymConfig,
{
fn load_identity_keys(
pathfinder: &ClientKeyPathfinder,
) -> Result<identity::KeyPair, ClientCoreError> {
let identity_keypair: identity::KeyPair =
nym_pemstore::load_keypair(&nym_pemstore::KeyPairPath::new(
pathfinder.private_identity_key().to_owned(),
pathfinder.public_identity_key().to_owned(),
))
.tap_err(|_| log::error!("Failed to read stored identity key files"))?;
Ok(identity_keypair)
}
fn load_sphinx_keys(
pathfinder: &ClientKeyPathfinder,
) -> Result<encryption::KeyPair, ClientCoreError> {
@@ -1,12 +1,12 @@
use async_trait::async_trait;
use cosmrs::AccountId;
use nym_contracts_common::ContractBuildInformation;
use nym_contracts_common::{signing::Nonce, ContractBuildInformation};
use nym_service_provider_directory_common::{
msg::QueryMsg as SpQueryMsg,
response::{
ConfigResponse, PagedServicesListResponse, ServiceInfoResponse, ServicesListResponse,
},
NymAddress, ServiceId, ServiceInfo,
NymAddress, Service, ServiceId,
};
use serde::Deserialize;
@@ -63,17 +63,14 @@ pub trait SpDirectoryQueryClient {
.await
}
async fn get_all_services(&self) -> Result<Vec<ServiceInfo>, NyxdError> {
async fn get_all_services(&self) -> Result<Vec<Service>, NyxdError> {
let mut services = Vec::new();
let mut start_after = None;
loop {
let mut paged_response = self.get_services_paged(start_after.take(), None).await?;
let last_id = paged_response.services.last().map(|serv| serv.service_id);
services.append(&mut paged_response.services);
if let Some(start_after_res) = last_id {
if let Some(start_after_res) = paged_response.start_next_after {
start_after = Some(start_after_res)
} else {
break;
@@ -82,6 +79,13 @@ pub trait SpDirectoryQueryClient {
Ok(services)
}
async fn get_service_signing_nonce(&self, address: &AccountId) -> Result<Nonce, NyxdError> {
self.query_service_provider_contract(SpQueryMsg::SigningNonce {
address: address.to_string(),
})
.await
}
}
#[async_trait]
@@ -2,8 +2,9 @@
// SPDX-License-Identifier: Apache-2.0
use async_trait::async_trait;
use nym_contracts_common::signing::MessageSignature;
use nym_service_provider_directory_common::{
msg::ExecuteMsg as SpExecuteMsg, NymAddress, ServiceId, ServiceType,
msg::ExecuteMsg as SpExecuteMsg, NymAddress, ServiceDetails, ServiceId,
};
use crate::nyxd::{
@@ -22,16 +23,16 @@ pub trait SpDirectorySigningClient {
async fn announce_service_provider(
&self,
nym_address: NymAddress,
service_type: ServiceType,
service: ServiceDetails,
owner_signature: MessageSignature,
deposit: Coin,
fee: Option<Fee>,
) -> Result<ExecuteResult, NyxdError> {
self.execute_service_provider_directory_contract(
fee,
SpExecuteMsg::Announce {
nym_address,
service_type,
service,
owner_signature,
},
vec![deposit],
)
+1
View File
@@ -40,3 +40,4 @@ nym-coconut-dkg-common = { path = "../cosmwasm-smart-contracts/coconut-dkg" }
nym-multisig-contract-common = { path = "../cosmwasm-smart-contracts/multisig-contract" }
nym-service-provider-directory-common = { path = "../cosmwasm-smart-contracts/service-provider-directory" }
nym-name-service-common = { path = "../cosmwasm-smart-contracts/name-service" }
nym-sphinx = { path = "../../common/nymsphinx" }
@@ -14,6 +14,7 @@ pub struct Mixnet {
pub command: MixnetCommands,
}
#[allow(clippy::large_enum_variant)]
#[derive(Debug, Subcommand)]
pub enum MixnetCommands {
/// Query the mixnet directory
@@ -15,6 +15,7 @@ pub struct MixnetOperators {
pub command: MixnetOperatorsCommands,
}
#[allow(clippy::large_enum_variant)]
#[derive(Debug, Subcommand)]
pub enum MixnetOperatorsCommands {
/// Manage your mixnode
@@ -1,6 +1,7 @@
use clap::Parser;
use log::info;
use nym_service_provider_directory_common::{Coin, NymAddress, ServiceType};
use nym_contracts_common::signing::MessageSignature;
use nym_service_provider_directory_common::{Coin, NymAddress, ServiceDetails, ServiceType};
use nym_validator_client::nyxd::traits::SpDirectorySigningClient;
use crate::context::SigningClient;
@@ -10,9 +11,15 @@ pub struct Args {
#[clap(long)]
pub nym_address: String,
#[clap(long)]
pub signature: MessageSignature,
/// Deposit to be made to the service provider directory, in curent DENOMINATION (e.g. 'unym')
#[clap(long)]
pub deposit: u128,
#[clap(long)]
pub identity_key: String,
}
pub async fn announce(args: Args, client: SigningClient) {
@@ -20,12 +27,17 @@ pub async fn announce(args: Args, client: SigningClient) {
let nym_address = NymAddress::Address(args.nym_address);
let service_type = ServiceType::NetworkRequester;
let service = ServiceDetails {
nym_address,
service_type,
identity_key: args.identity_key,
};
let denom = client.current_chain_details().mix_denom.base.as_str();
let deposit = Coin::new(args.deposit, denom);
let res = client
.announce_service_provider(nym_address, service_type, deposit.into(), None)
.announce_service_provider(service, args.signature, deposit.into(), None)
.await
.expect("Failed to announce service provider");
@@ -0,0 +1,61 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::{
context::SigningClient,
utils::{account_id_to_cw_addr, DataWrapper},
};
use clap::Parser;
use cosmwasm_std::Coin;
use nym_bin_common::output_format::OutputFormat;
use nym_service_provider_directory_common::{
signing_types::construct_service_provider_announce_sign_payload, NymAddress,
ServiceType::NetworkRequester,
};
use nym_sphinx::addressing::clients::Recipient;
use nym_validator_client::nyxd::traits::SpDirectoryQueryClient;
#[derive(Debug, Parser)]
pub struct Args {
#[clap(long)]
pub nym_address: Recipient,
#[clap(long)]
pub amount: u128,
#[clap(long)]
pub identity_key: String,
#[clap(short, long, default_value_t = OutputFormat::default())]
output: OutputFormat,
}
pub async fn create_payload(args: Args, client: SigningClient) {
let service = nym_service_provider_directory_common::ServiceDetails {
nym_address: NymAddress::new(&args.nym_address.to_string()),
service_type: NetworkRequester,
identity_key: args.identity_key,
};
let denom = client.current_chain_details().mix_denom.base.as_str();
let deposit = Coin::new(args.amount, denom);
let nonce = match client.get_service_signing_nonce(client.address()).await {
Ok(nonce) => nonce,
Err(err) => {
eprint!(
"failed to query for the signing nonce of {}: {err}",
client.address()
);
return;
}
};
let address = account_id_to_cw_addr(client.address());
let payload =
construct_service_provider_announce_sign_payload(nonce, address, deposit, service);
let wrapper = DataWrapper::new(payload.to_base58_string().unwrap());
println!("{}", args.output.format(&wrapper))
}
@@ -1,6 +1,7 @@
use clap::{Args, Subcommand};
pub mod announce;
pub mod announce_sign_payload;
pub mod delete;
#[derive(Debug, Args)]
@@ -10,10 +11,13 @@ pub struct MixnetOperatorsService {
pub command: MixnetOperatorsServiceCommands,
}
#[allow(clippy::large_enum_variant)]
#[derive(Debug, Subcommand)]
pub enum MixnetOperatorsServiceCommands {
/// Announce service provider to the world
Announce(announce::Args),
/// Delete entry for service provider from the directory
Delete(delete::Args),
/// Create base58-encoded payload required for producing valid announce signature.
CreateServiceAnnounceSignPayload(announce_sign_payload::Args),
}
@@ -37,7 +37,7 @@ pub async fn query(args: Args, client: &QueryClientWithNyxd) {
for service in res.services {
table.add_row(vec![
service.service_id.to_string(),
service.service.announcer.to_string(),
service.announcer.to_string(),
service.service.service_type.to_string(),
service.service.nym_address.to_string(),
]);
@@ -11,6 +11,9 @@ use std::ops::Mul;
use std::str::FromStr;
use thiserror::Error;
pub type IdentityKey = String;
pub type IdentityKeyRef<'a> = &'a str;
pub fn truncate_decimal(amount: Decimal) -> Uint128 {
amount * Uint128::new(1)
}
@@ -4,6 +4,7 @@
use crate::error::MixnetContractError;
use crate::families::{Family, FamilyHead};
use crate::{Layer, RewardedSetNodeStatus};
use contracts_common::IdentityKey;
use cosmwasm_std::Addr;
use cosmwasm_std::Coin;
use schemars::JsonSchema;
@@ -11,8 +12,6 @@ use serde::{Deserialize, Serialize};
use std::ops::Index;
// type aliases for better reasoning about available data
pub type IdentityKey = String;
pub type IdentityKeyRef<'a> = &'a str;
pub type SphinxKey = String;
pub type SphinxKeyRef<'a> = &'a str;
pub type EpochId = u32;
@@ -7,5 +7,9 @@ edition = "2021"
[dependencies]
cosmwasm-std = { workspace = true }
nym-contracts-common = { path = "../contracts-common", version = "0.4.0" }
schemars = "0.8"
serde = { workspace = true, features = ["derive"] }
thiserror = { workspace = true }
cw-utils = { workspace = true }
cw-controllers = { workspace = true }
@@ -1,10 +1,12 @@
use cosmwasm_std::{Addr, StdError};
use cw_controllers::AdminError;
use nym_service_provider_directory_common::{NymAddress, ServiceId};
use nym_contracts_common::signing::verifier::ApiVerifierError;
use thiserror::Error;
use crate::{NymAddress, ServiceId};
#[derive(Error, Debug, PartialEq)]
pub enum ContractError {
pub enum SpContractError {
#[error("{0}")]
Std(#[from] StdError),
@@ -46,6 +48,21 @@ pub enum ContractError {
value: String,
error_message: String,
},
#[error("Failed to recover ed25519 public key from its base58 representation - {0}")]
MalformedEd25519IdentityKey(String),
#[error("Failed to recover ed25519 signature from its base58 representation - {0}")]
MalformedEd25519Signature(String),
#[error("Provided ed25519 signature did not verify correctly")]
InvalidEd25519Signature,
#[error("failed to verify message signature: {source}")]
SignatureVerificationFailure {
#[from]
source: ApiVerifierError,
},
}
pub(crate) type Result<T, E = ContractError> = std::result::Result<T, E>;
pub type Result<T, E = SpContractError> = std::result::Result<T, E>;
@@ -39,16 +39,16 @@ pub fn new_announce_event(service_id: ServiceId, service: Service) -> Event {
Event::new(ServiceProviderEventType::Announce)
.add_attribute(ACTION, ServiceProviderEventType::Announce)
.add_attribute(SERVICE_ID, service_id.to_string())
.add_attribute(SERVICE_TYPE, service.service_type.to_string())
.add_attribute(NYM_ADDRESS, service.nym_address.to_string())
.add_attribute(SERVICE_TYPE, service.service.service_type.to_string())
.add_attribute(NYM_ADDRESS, service.service.nym_address.to_string())
.add_attribute(OWNER, service.announcer.to_string())
}
pub fn new_delete_id_event(service_id: ServiceId, service: Service) -> Event {
pub fn new_delete_id_event(service: Service) -> Event {
Event::new(ServiceProviderEventType::DeleteId)
.add_attribute(ACTION, ServiceProviderEventType::DeleteId)
.add_attribute(SERVICE_ID, service_id.to_string())
.add_attribute(NYM_ADDRESS, service.nym_address.to_string())
.add_attribute(SERVICE_ID, service.service_id.to_string())
.add_attribute(NYM_ADDRESS, service.service.nym_address.to_string())
}
pub fn new_update_deposit_required_event(deposit_required: Coin) -> Event {
@@ -1,6 +1,8 @@
pub mod error;
pub mod events;
pub mod msg;
pub mod response;
pub mod signing_types;
pub mod types;
// Re-export all types at the top-level
@@ -1,5 +1,6 @@
use crate::{NymAddress, ServiceId, ServiceType};
use crate::{NymAddress, ServiceDetails, ServiceId};
use cosmwasm_std::Coin;
use nym_contracts_common::signing::MessageSignature;
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
@@ -22,8 +23,8 @@ pub struct MigrateMsg {}
#[serde(rename_all = "snake_case")]
pub enum ExecuteMsg {
Announce {
nym_address: NymAddress,
service_type: ServiceType,
service: ServiceDetails,
owner_signature: MessageSignature,
},
DeleteId {
service_id: ServiceId,
@@ -44,9 +45,12 @@ impl ExecuteMsg {
pub fn default_memo(&self) -> String {
match self {
ExecuteMsg::Announce {
nym_address,
service_type,
} => format!("announcing {nym_address} as type {service_type}"),
service,
owner_signature: _,
} => format!(
"announcing {} as type {}",
service.nym_address, service.service_type
),
ExecuteMsg::DeleteId { service_id } => {
format!("deleting service with service id {service_id}")
}
@@ -76,6 +80,9 @@ pub enum QueryMsg {
limit: Option<u32>,
start_after: Option<ServiceId>,
},
SigningNonce {
address: String,
},
Config {},
GetContractVersion {},
#[serde(rename = "get_cw2_contract_version")]
@@ -1,4 +1,4 @@
use crate::{msg::ExecuteMsg, Service, ServiceId, ServiceInfo};
use crate::{Service, ServiceId};
use cosmwasm_std::Coin;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
@@ -13,22 +13,17 @@ pub struct ServiceInfoResponse {
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub struct ServicesListResponse {
pub services: Vec<ServiceInfo>,
pub services: Vec<Service>,
}
impl ServicesListResponse {
pub fn new(services: Vec<(ServiceId, Service)>) -> ServicesListResponse {
ServicesListResponse {
services: services
.into_iter()
.map(|(service_id, service)| ServiceInfo::new(service_id, service))
.collect(),
}
pub fn new(services: Vec<Service>) -> ServicesListResponse {
ServicesListResponse { services }
}
}
impl From<&[ServiceInfo]> for ServicesListResponse {
fn from(services: &[ServiceInfo]) -> Self {
impl From<&[Service]> for ServicesListResponse {
fn from(services: &[Service]) -> Self {
Self {
services: services.to_vec(),
}
@@ -38,21 +33,17 @@ impl From<&[ServiceInfo]> for ServicesListResponse {
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
#[serde(rename_all = "snake_case")]
pub struct PagedServicesListResponse {
pub services: Vec<ServiceInfo>,
pub services: Vec<Service>,
pub per_page: usize,
pub start_next_after: Option<ServiceId>,
}
impl PagedServicesListResponse {
pub fn new(
services: Vec<(ServiceId, Service)>,
services: Vec<Service>,
per_page: usize,
start_next_after: Option<ServiceId>,
) -> PagedServicesListResponse {
let services = services
.into_iter()
.map(|(service_id, service)| ServiceInfo::new(service_id, service))
.collect();
PagedServicesListResponse {
services,
per_page,
@@ -66,12 +57,3 @@ impl PagedServicesListResponse {
pub struct ConfigResponse {
pub deposit_required: Coin,
}
impl From<Service> for ExecuteMsg {
fn from(service: Service) -> Self {
ExecuteMsg::Announce {
nym_address: service.nym_address,
service_type: service.service_type,
}
}
}
@@ -0,0 +1,33 @@
use cosmwasm_std::{Addr, Coin};
use nym_contracts_common::signing::{
ContractMessageContent, MessageType, Nonce, SignableMessage, SigningPurpose,
};
use serde::Serialize;
use crate::ServiceDetails;
pub type SignableServiceProviderAnnounceMsg =
SignableMessage<ContractMessageContent<ServiceProviderAnnounce>>;
#[derive(Serialize)]
pub struct ServiceProviderAnnounce {
service: ServiceDetails,
}
impl SigningPurpose for ServiceProviderAnnounce {
fn message_type() -> MessageType {
MessageType::new("service-provider-announce")
}
}
pub fn construct_service_provider_announce_sign_payload(
nonce: Nonce,
sender: Addr,
deposit: Coin,
service: ServiceDetails,
) -> SignableServiceProviderAnnounceMsg {
let payload = ServiceProviderAnnounce { service };
let proxy = None;
let content = ContractMessageContent::new(sender, proxy, vec![deposit], payload);
SignableMessage::new(nonce, content)
}
@@ -1,6 +1,7 @@
use std::fmt::{Display, Formatter};
use cosmwasm_std::{Addr, Coin};
use nym_contracts_common::IdentityKey;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
@@ -9,11 +10,11 @@ pub type ServiceId = u32;
#[derive(Serialize, Deserialize, Clone, PartialEq, Debug, JsonSchema)]
pub struct Service {
/// The address of the service.
pub nym_address: NymAddress,
/// The service type.
pub service_type: ServiceType,
/// Service owner.
/// Unique id assigned to the anounced service.
pub service_id: ServiceId,
/// The announced service.
pub service: ServiceDetails,
/// Address of the service owner.
pub announcer: Addr,
/// Block height at which the service was added.
pub block_height: u64,
@@ -21,6 +22,16 @@ pub struct Service {
pub deposit: Coin,
}
#[derive(Serialize, Deserialize, Clone, PartialEq, Debug, JsonSchema)]
pub struct ServiceDetails {
/// The address of the service.
pub nym_address: NymAddress,
/// The service type.
pub service_type: ServiceType,
/// The identity key of the service.
pub identity_key: IdentityKey,
}
/// The types of addresses supported.
#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug, JsonSchema)]
#[serde(rename_all = "snake_case")]
@@ -28,7 +39,7 @@ pub enum NymAddress {
/// String representation of a nym address, which is of the form
/// client_id.client_enc@gateway_id.
Address(String),
// For the future when we have a nym-dns contract
// String name that can looked up in the nym-name-service contract (once it exists)
//Name(String),
}
@@ -41,6 +52,7 @@ impl NymAddress {
pub fn as_str(&self) -> &str {
match self {
NymAddress::Address(address) => address,
//NymAddress::Name(name) => name,
}
}
}
@@ -66,19 +78,3 @@ impl std::fmt::Display for ServiceType {
write!(f, "{service_type}")
}
}
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub struct ServiceInfo {
pub service_id: ServiceId,
pub service: Service,
}
impl ServiceInfo {
pub fn new(service_id: ServiceId, service: Service) -> Self {
Self {
service_id,
service,
}
}
}
+8
View File
@@ -1265,6 +1265,7 @@ name = "nym-service-provider-directory"
version = "0.1.0"
dependencies = [
"anyhow",
"bs58",
"cosmwasm-std",
"cw-controllers",
"cw-multi-test",
@@ -1272,7 +1273,10 @@ dependencies = [
"cw-utils",
"cw2",
"nym-contracts-common",
"nym-crypto",
"nym-service-provider-directory-common",
"rand_chacha 0.2.2",
"rstest",
"semver",
"serde",
"thiserror",
@@ -1284,8 +1288,12 @@ name = "nym-service-provider-directory-common"
version = "0.1.0"
dependencies = [
"cosmwasm-std",
"cw-controllers",
"cw-utils",
"nym-contracts-common",
"schemars",
"serde",
"thiserror",
]
[[package]]
+1 -1
View File
@@ -14,7 +14,7 @@ mod mixnet_contract_settings;
mod mixnodes;
mod queued_migrations;
mod rewards;
mod signing;
pub mod signing;
mod support;
#[cfg(feature = "contract-testing")]
+1 -1
View File
@@ -188,6 +188,7 @@ mod tests {
use super::*;
type TestDeps = OwnedDeps<MemoryStorage, MockApi, MockQuerier>;
#[rstest::fixture]
fn deps() -> TestDeps {
instantiate_test_contract()
@@ -691,7 +692,6 @@ mod tests {
#[test]
#[ignore]
fn max_page_limit_is_applied() {
// WIP(JON)
todo!();
}
}
@@ -7,6 +7,7 @@ edition = "2021"
crate-type = ["cdylib", "rlib"]
[dependencies]
bs58 = "0.4.0"
cosmwasm-std = { workspace = true }
cw-controllers = { workspace = true }
cw-storage-plus = { workspace = true }
@@ -22,5 +23,8 @@ thiserror = "1.0.39"
vergen = { version = "=7.4.3", default-features = false, features = ["build", "git", "rustc"] }
[dev-dependencies]
cw-multi-test = { workspace = true }
anyhow = "1.0.40"
cw-multi-test = { workspace = true }
nym-crypto = { path = "../../common/crypto", features = ["asymmetric", "rand"] }
rand_chacha = "0.2"
rstest = "0.17.0"
@@ -13,3 +13,5 @@ pub const SERVICE_ID_COUNTER_KEY: &str = "sidc";
pub const SERVICES_PK_NAMESPACE: &str = "sernames";
pub const SERVICES_ANNOUNCER_IDX_NAMESPACE: &str = "serown";
pub const SERVICES_NYM_ADDRESS_IDX_NAMESPACE: &str = "sernyma";
pub const SIGNING_NONCES_NAMESPACE: &str = "sn";
@@ -1,6 +1,6 @@
use crate::{
error::{ContractError, Result},
state::{self, Config},
Result, SpContractError,
};
use cosmwasm_std::{to_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response};
use nym_service_provider_directory_common::msg::{
@@ -34,22 +34,25 @@ pub fn instantiate(
.add_attribute("admin", info.sender))
}
pub fn migrate(deps: DepsMut<'_>, _env: Env, _msg: MigrateMsg) -> Result<Response, ContractError> {
pub fn migrate(
deps: DepsMut<'_>,
_env: Env,
_msg: MigrateMsg,
) -> Result<Response, SpContractError> {
// Note: don't remove this particular bit of code as we have to ALWAYS check whether we have to
// update the stored version
let version: Version =
CONTRACT_VERSION
.parse()
.map_err(|error: semver::Error| ContractError::SemVerFailure {
value: CONTRACT_VERSION.to_string(),
error_message: error.to_string(),
})?;
let version: Version = CONTRACT_VERSION.parse().map_err(|error: semver::Error| {
SpContractError::SemVerFailure {
value: CONTRACT_VERSION.to_string(),
error_message: error.to_string(),
}
})?;
let storage_version_raw = cw2::get_contract_version(deps.storage)?.version;
let storage_version: Version =
storage_version_raw
.parse()
.map_err(|error: semver::Error| ContractError::SemVerFailure {
.map_err(|error: semver::Error| SpContractError::SemVerFailure {
value: storage_version_raw,
error_message: error.to_string(),
})?;
@@ -69,12 +72,12 @@ pub fn execute(
env: Env,
info: MessageInfo,
msg: ExecuteMsg,
) -> Result<Response, ContractError> {
) -> Result<Response, SpContractError> {
match msg {
ExecuteMsg::Announce {
nym_address: client_address,
service_type,
} => execute::announce(deps, env, info, client_address, service_type),
service,
owner_signature,
} => execute::announce(deps, env, info, service, owner_signature),
ExecuteMsg::DeleteId { service_id } => execute::delete_id(deps, info, service_id),
ExecuteMsg::DeleteNymAddress { nym_address } => {
execute::delete_nym_address(deps, info, nym_address)
@@ -95,6 +98,9 @@ pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> Result<Binary> {
QueryMsg::All { limit, start_after } => {
to_binary(&query::query_all_paged(deps, limit, start_after)?)
}
QueryMsg::SigningNonce { address } => {
to_binary(&query::query_current_signing_nonce(deps, address)?)
}
QueryMsg::Config {} => to_binary(&query::query_config(deps)?),
QueryMsg::GetContractVersion {} => to_binary(&query::query_contract_version()),
QueryMsg::GetCW2ContractVersion {} => to_binary(&cw2::get_contract_version(deps.storage)?),
@@ -107,16 +113,19 @@ mod tests {
use super::*;
use crate::test_helpers::{
assert::{assert_config, assert_empty, assert_not_found, assert_service, assert_services},
fixture::service_fixture,
helpers::{get_attribute, nyms},
assert::{
assert_config, assert_current_nonce, assert_empty, assert_not_found, assert_service,
assert_services,
},
fixture::new_service_details_with_sign,
helpers::{get_attribute, nyms, test_rng},
};
use cosmwasm_std::{
testing::{mock_dependencies, mock_env, mock_info},
Addr, Coin,
};
use nym_service_provider_directory_common::{msg::ExecuteMsg, ServiceId, ServiceInfo};
use nym_service_provider_directory_common::{msg::ExecuteMsg, Service, ServiceId};
const DENOM: &str = "unym";
@@ -140,27 +149,33 @@ mod tests {
}
#[test]
fn announce_fails_incorrect_deposit() {
fn announce_fails_incorrect_deposit_too_small() {
let mut rng = test_rng();
let mut deps = mock_dependencies();
let msg = InstantiateMsg::new(nyms(100));
let info = mock_info("creator", &[]);
let admin = info.sender.clone();
let res = instantiate(deps.as_mut(), mock_env(), info, msg).unwrap();
assert_eq!(res.messages.len(), 0);
// Announce
let msg: ExecuteMsg = service_fixture().into();
let announcer = service_fixture().announcer.to_string();
// Setup service
let deposit = nyms(99);
let announcer = "steve";
let (service, owner_signature) =
new_service_details_with_sign(deps.as_mut(), &mut rng, "nym", announcer, deposit);
let msg = ExecuteMsg::Announce {
service,
owner_signature,
};
assert_eq!(
execute(
deps.as_mut(),
mock_env(),
mock_info(&announcer, &[nyms(99)]),
mock_info(announcer, &[nyms(99)]),
msg.clone()
)
.unwrap_err(),
ContractError::InsufficientDeposit {
SpContractError::InsufficientDeposit {
funds: 99u128.into(),
deposit_required: 100u128.into(),
}
@@ -170,11 +185,55 @@ mod tests {
execute(
deps.as_mut(),
mock_env(),
mock_info(&announcer, &[nyms(101)]),
msg
mock_info(announcer, &[nyms(100)]),
msg,
)
.unwrap_err(),
ContractError::TooLargeDeposit {
SpContractError::InvalidEd25519Signature,
);
}
// Announcing a service fails due to the signed deposit being different from the deposit in
// the message.
#[test]
fn announce_fails_incorrect_deposit_too_large() {
let mut rng = test_rng();
let mut deps = mock_dependencies();
let msg = InstantiateMsg::new(nyms(100));
let info = mock_info("creator", &[]);
let admin = info.sender.clone();
let res = instantiate(deps.as_mut(), mock_env(), info, msg).unwrap();
assert_eq!(res.messages.len(), 0);
// Setup service
let deposit = nyms(101);
let announcer = "steve";
let (service, owner_signature) =
new_service_details_with_sign(deps.as_mut(), &mut rng, "nym", announcer, deposit);
let msg = ExecuteMsg::Announce {
service,
owner_signature,
};
assert_eq!(
execute(
deps.as_mut(),
mock_env(),
mock_info(announcer, &[nyms(100)]),
msg.clone()
)
.unwrap_err(),
SpContractError::InvalidEd25519Signature,
);
assert_eq!(
execute(
deps.as_mut(),
mock_env(),
mock_info(announcer, &[nyms(101)]),
msg,
)
.unwrap_err(),
SpContractError::TooLargeDeposit {
funds: 101u128.into(),
deposit_required: 100u128.into(),
}
@@ -186,15 +245,26 @@ mod tests {
#[test]
fn announce_success() {
let mut rng = test_rng();
let mut deps = mock_dependencies();
let msg = InstantiateMsg::new(nyms(100));
let info = mock_info("creator", &[]);
let res = instantiate(deps.as_mut(), mock_env(), info, msg).unwrap();
assert_eq!(res.messages.len(), 0);
assert_current_nonce(deps.as_ref(), &Addr::unchecked("steve"), 0);
// Setup service
let deposit = nyms(100);
let owner = "steve";
let (service, owner_signature) =
new_service_details_with_sign(deps.as_mut(), &mut rng, "nym", owner, deposit.clone());
// Announce
let msg: ExecuteMsg = service_fixture().into();
let info = mock_info("steve", &[nyms(100)]);
let msg = ExecuteMsg::Announce {
service: service.clone(),
owner_signature,
};
let info = mock_info("steve", &[deposit.clone()]);
let res = execute(deps.as_mut(), mock_env(), info, msg).unwrap();
// Check that the service has had service id assigned to it
@@ -208,10 +278,17 @@ mod tests {
"network_requester".to_string()
);
// Check that the nonce has been incremented, but only for the owner
assert_current_nonce(deps.as_ref(), &Addr::unchecked("steve"), 1);
assert_current_nonce(deps.as_ref(), &Addr::unchecked("timmy"), 0);
// The expected announced service
let expected_service = ServiceInfo {
let expected_service = Service {
service_id: expected_id,
service: service_fixture(),
service,
announcer: Addr::unchecked("steve"),
block_height: 12345,
deposit,
};
assert_services(deps.as_ref(), &[expected_service.clone()]);
assert_service(deps.as_ref(), &expected_service);
@@ -219,6 +296,7 @@ mod tests {
#[test]
fn delete() {
let mut rng = test_rng();
let mut deps = mock_dependencies();
let msg = InstantiateMsg::new(Coin::new(100, "unym"));
let info = mock_info("creator", &[]);
@@ -226,16 +304,25 @@ mod tests {
assert_eq!(res.messages.len(), 0);
// Announce
let msg: ExecuteMsg = service_fixture().into();
let info_steve = mock_info("steve", &[nyms(100)]);
assert_eq!(info_steve.sender, service_fixture().announcer);
execute(deps.as_mut(), mock_env(), info_steve, msg).unwrap();
let deposit = nyms(100);
let steve = "steve";
let (service, owner_signature) =
new_service_details_with_sign(deps.as_mut(), &mut rng, "nym", steve, deposit.clone());
let msg = ExecuteMsg::Announce {
service: service.clone(),
owner_signature,
};
let info_steve = mock_info(steve, &[deposit.clone()]);
execute(deps.as_mut(), mock_env(), info_steve.clone(), msg).unwrap();
// The expected announced service
let expected_id = 1;
let expected_service = ServiceInfo {
let expected_service = Service {
service_id: expected_id,
service: service_fixture(),
service,
announcer: Addr::unchecked(steve),
block_height: 12345,
deposit,
};
assert_services(deps.as_ref(), &[expected_service]);
@@ -244,27 +331,23 @@ mod tests {
let info_timmy = mock_info("timmy", &[]);
assert_eq!(
execute(deps.as_mut(), mock_env(), info_timmy, msg).unwrap_err(),
ContractError::Unauthorized {
SpContractError::Unauthorized {
sender: Addr::unchecked("timmy")
}
);
// Removing an non-existent service will fail
let msg = ExecuteMsg::delete_id(expected_id + 1);
let info_announcer = MessageInfo {
sender: service_fixture().announcer,
funds: vec![],
};
assert_eq!(
execute(deps.as_mut(), mock_env(), info_announcer.clone(), msg).unwrap_err(),
ContractError::NotFound {
execute(deps.as_mut(), mock_env(), info_steve.clone(), msg).unwrap_err(),
SpContractError::NotFound {
service_id: expected_id + 1
}
);
// Remove as correct announcer succeeds
let msg = ExecuteMsg::delete_id(expected_id);
let res = execute(deps.as_mut(), mock_env(), info_announcer, msg).unwrap();
let res = execute(deps.as_mut(), mock_env(), info_steve, msg).unwrap();
assert_eq!(
get_attribute(&res, "delete_id", "service_id"),
expected_id.to_string()
@@ -1,24 +1,28 @@
use crate::{
constants::{MAX_NUMBER_OF_ALIASES_FOR_NYM_ADDRESS, MAX_NUMBER_OF_PROVIDERS_PER_ANNOUNCER},
error::{ContractError, Result},
state,
state, Result, SpContractError,
};
use cosmwasm_std::{Addr, BankMsg, Coin, Deps, DepsMut, Env, MessageInfo, Response, Uint128};
use nym_contracts_common::{
signing::{MessageSignature, Verifier},
IdentityKey,
};
use nym_service_provider_directory_common::{
events::{new_announce_event, new_delete_id_event, new_update_deposit_required_event},
NymAddress, Service, ServiceId, ServiceType,
signing_types::construct_service_provider_announce_sign_payload,
NymAddress, Service, ServiceDetails, ServiceId,
};
use super::query;
fn ensure_correct_deposit(will_deposit: Uint128, deposit_required: Uint128) -> Result<()> {
match will_deposit.cmp(&deposit_required) {
std::cmp::Ordering::Less => Err(ContractError::InsufficientDeposit {
std::cmp::Ordering::Less => Err(SpContractError::InsufficientDeposit {
funds: will_deposit,
deposit_required,
}),
std::cmp::Ordering::Equal => Ok(()),
std::cmp::Ordering::Greater => Err(ContractError::TooLargeDeposit {
std::cmp::Ordering::Greater => Err(SpContractError::TooLargeDeposit {
funds: will_deposit,
deposit_required,
}),
@@ -30,7 +34,7 @@ fn ensure_max_services_per_announcer(deps: Deps, announcer: Addr) -> Result<()>
if current_entries.services.len() < MAX_NUMBER_OF_PROVIDERS_PER_ANNOUNCER as usize {
Ok(())
} else {
Err(ContractError::ReachedMaxProvidersForAdmin {
Err(SpContractError::ReachedMaxProvidersForAdmin {
max_providers: MAX_NUMBER_OF_PROVIDERS_PER_ANNOUNCER,
announcer,
})
@@ -42,7 +46,7 @@ fn ensure_max_aliases_per_nym_address(deps: Deps, nym_address: NymAddress) -> Re
if current_entries.services.len() < MAX_NUMBER_OF_ALIASES_FOR_NYM_ADDRESS as usize {
Ok(())
} else {
Err(ContractError::ReachedMaxAliasesForNymAddress {
Err(SpContractError::ReachedMaxAliasesForNymAddress {
max_aliases: MAX_NUMBER_OF_ALIASES_FOR_NYM_ADDRESS,
nym_address,
})
@@ -50,10 +54,10 @@ fn ensure_max_aliases_per_nym_address(deps: Deps, nym_address: NymAddress) -> Re
}
fn ensure_service_exists(deps: Deps, service_id: ServiceId) -> Result<()> {
if state::services::has_service(deps.storage, service_id) {
if state::has_service(deps.storage, service_id) {
Ok(())
} else {
Err(ContractError::NotFound { service_id })
Err(SpContractError::NotFound { service_id })
}
}
@@ -61,7 +65,7 @@ fn ensure_sender_authorized(info: MessageInfo, service: &Service) -> Result<()>
if info.sender == service.announcer {
Ok(())
} else {
Err(ContractError::Unauthorized {
Err(SpContractError::Unauthorized {
sender: info.sender,
})
}
@@ -74,31 +78,82 @@ fn return_deposit(service_to_delete: &Service) -> BankMsg {
}
}
fn verify_announce_signature(
deps: Deps<'_>,
sender: Addr,
deposit: Coin,
service: ServiceDetails,
signature: MessageSignature,
) -> Result<()> {
// recover the public key
let public_key = decode_ed25519_identity_key(&service.identity_key)?;
// reconstruct the payload
let nonce = state::get_signing_nonce(deps.storage, sender.clone())?;
let msg = construct_service_provider_announce_sign_payload(nonce, sender, deposit, service);
if deps.api.verify_message(msg, signature, &public_key)? {
Ok(())
} else {
Err(SpContractError::InvalidEd25519Signature)
}
}
fn decode_ed25519_identity_key(encoded: &IdentityKey) -> Result<[u8; 32]> {
let mut public_key = [0u8; 32];
let used = bs58::decode(encoded)
.into(&mut public_key)
.map_err(|err| SpContractError::MalformedEd25519IdentityKey(err.to_string()))?;
if used != 32 {
return Err(SpContractError::MalformedEd25519IdentityKey(
"Too few bytes provided for the public key".into(),
));
}
Ok(public_key)
}
/// Announce a new service. It will be assigned a new service provider id.
pub fn announce(
deps: DepsMut,
env: Env,
info: MessageInfo,
nym_address: NymAddress,
service_type: ServiceType,
service: ServiceDetails,
owner_signature: MessageSignature,
) -> Result<Response> {
ensure_max_services_per_announcer(deps.as_ref(), info.sender.clone())?;
ensure_max_aliases_per_nym_address(deps.as_ref(), nym_address.clone())?;
ensure_max_aliases_per_nym_address(deps.as_ref(), service.nym_address.clone())?;
let deposit_required = state::deposit_required(deps.storage)?;
let denom = deposit_required.denom.clone();
let will_deposit = cw_utils::must_pay(&info, &denom)
.map_err(|err| ContractError::DepositRequired { source: err })?;
.map_err(|err| SpContractError::DepositRequired { source: err })?;
ensure_correct_deposit(will_deposit, deposit_required.amount)?;
let deposit = Coin::new(will_deposit.u128(), denom);
// Check that the sender actually owns the service provider by checking the signature
verify_announce_signature(
deps.as_ref(),
info.sender.clone(),
deposit.clone(),
service.clone(),
owner_signature,
)?;
state::increment_signing_nonce(deps.storage, info.sender.clone())?;
let service_id = state::next_service_id_counter(deps.storage)?;
let new_service = Service {
nym_address,
service_type,
service_id,
service,
announcer: info.sender,
block_height: env.block.height,
deposit: Coin::new(will_deposit.u128(), denom),
deposit,
};
let service_id = state::services::save(deps.storage, &new_service)?;
state::save(deps.storage, &new_service)?;
Ok(Response::new().add_event(new_announce_event(service_id, new_service)))
}
@@ -106,15 +161,15 @@ pub fn announce(
/// Delete an exsisting service.
pub fn delete_id(deps: DepsMut, info: MessageInfo, service_id: ServiceId) -> Result<Response> {
ensure_service_exists(deps.as_ref(), service_id)?;
let service_to_delete = state::services::load_id(deps.storage, service_id)?;
let service_to_delete = state::load_id(deps.storage, service_id)?;
ensure_sender_authorized(info, &service_to_delete)?;
state::services::remove(deps.storage, service_id)?;
state::remove(deps.storage, service_id)?;
let return_deposit_msg = return_deposit(&service_to_delete);
Ok(Response::new()
.add_message(return_deposit_msg)
.add_event(new_delete_id_event(service_id, service_to_delete)))
.add_event(new_delete_id_event(service_to_delete)))
}
/// Delete an existing service by nym address. If there are multiple entries for a given nym
@@ -128,15 +183,12 @@ pub(crate) fn delete_nym_address(
let services_to_delete = query::query_nym_address(deps.as_ref(), nym_address)?.services;
for service_to_delete in services_to_delete {
if info.sender == service_to_delete.service.announcer {
state::services::remove(deps.storage, service_to_delete.service_id)?;
let return_deposit_msg = return_deposit(&service_to_delete.service);
if info.sender == service_to_delete.announcer {
state::remove(deps.storage, service_to_delete.service_id)?;
let return_deposit_msg = return_deposit(&service_to_delete);
response = response
.add_message(return_deposit_msg)
.add_event(new_delete_id_event(
service_to_delete.service_id,
service_to_delete.service,
));
.add_event(new_delete_id_event(service_to_delete));
}
}
Ok(response)
@@ -1,31 +1,27 @@
use cosmwasm_std::Deps;
use nym_contracts_common::ContractBuildInformation;
use nym_contracts_common::{signing::Nonce, ContractBuildInformation};
use nym_service_provider_directory_common::{
response::{ConfigResponse, PagedServicesListResponse, ServicesListResponse},
NymAddress, ServiceId, ServiceInfo,
NymAddress, Service, ServiceId,
};
use crate::{
error::Result,
state::{self, services::PagedLoad},
state::{self, PagedLoad},
Result,
};
pub fn query_id(deps: Deps, service_id: ServiceId) -> Result<ServiceInfo> {
let service = state::services::load_id(deps.storage, service_id)?;
Ok(ServiceInfo {
service_id,
service,
})
pub fn query_id(deps: Deps, service_id: ServiceId) -> Result<Service> {
state::load_id(deps.storage, service_id)
}
pub fn query_announcer(deps: Deps, announcer: String) -> Result<ServicesListResponse> {
let announcer = deps.api.addr_validate(&announcer)?;
let services = state::services::load_announcer(deps.storage, announcer)?;
let services = state::load_announcer(deps.storage, announcer)?;
Ok(ServicesListResponse::new(services))
}
pub fn query_nym_address(deps: Deps, nym_address: NymAddress) -> Result<ServicesListResponse> {
let services = state::services::load_nym_address(deps.storage, nym_address)?;
let services = state::load_nym_address(deps.storage, nym_address)?;
Ok(ServicesListResponse::new(services))
}
@@ -38,7 +34,7 @@ pub fn query_all_paged(
services,
limit,
start_next_after,
} = state::services::load_all_paged(deps.storage, limit, start_after)?;
} = state::load_all_paged(deps.storage, limit, start_after)?;
Ok(PagedServicesListResponse::new(
services,
limit,
@@ -46,6 +42,11 @@ pub fn query_all_paged(
))
}
pub fn query_current_signing_nonce(deps: Deps<'_>, address: String) -> Result<Nonce> {
let address = deps.api.addr_validate(&address)?;
state::get_signing_nonce(deps.storage, address)
}
pub fn query_config(deps: Deps) -> Result<ConfigResponse> {
let config = state::load_config(deps.storage)?;
Ok(config.into())
@@ -1,355 +0,0 @@
//! Integration tests using cw-multi-test.
use cosmwasm_std::Addr;
use nym_service_provider_directory_common::{
response::{ConfigResponse, PagedServicesListResponse},
NymAddress, Service, ServiceInfo, ServiceType,
};
use crate::{
constants::SERVICE_DEFAULT_RETRIEVAL_LIMIT,
error::ContractError,
test_helpers::{fixture::service_info, helpers::nyms, test_setup::TestSetup},
};
#[test]
fn instantiate_contract() {
TestSetup::new();
}
#[test]
fn query_config() {
assert_eq!(
TestSetup::new().query_config(),
ConfigResponse {
deposit_required: nyms(100),
}
);
}
#[test]
fn announce_and_query_service() {
let mut setup = TestSetup::new();
assert_eq!(
setup.query_all(),
PagedServicesListResponse {
services: vec![],
per_page: SERVICE_DEFAULT_RETRIEVAL_LIMIT as usize,
start_next_after: None,
}
);
// Announce a first service
let announcer = Addr::unchecked("announcer");
let nym_address = NymAddress::new("nymAddress");
assert_eq!(setup.contract_balance(), nyms(0));
assert_eq!(setup.balance(&announcer), nyms(250));
setup.announce_net_req(nym_address.clone(), announcer.clone());
// Deposit is deposited to contract and deducted from announcers's balance
assert_eq!(setup.contract_balance(), nyms(100));
assert_eq!(setup.balance(&announcer), nyms(150));
// We can query the full service list
assert_eq!(
setup.query_all(),
PagedServicesListResponse {
services: vec![ServiceInfo {
service_id: 1,
service: Service {
nym_address: nym_address.clone(),
service_type: ServiceType::NetworkRequester,
announcer: announcer.clone(),
block_height: 12345,
deposit: nyms(100),
},
}],
per_page: SERVICE_DEFAULT_RETRIEVAL_LIMIT as usize,
start_next_after: Some(1),
}
);
// ... and we can query by id
assert_eq!(
setup.query_id(1),
ServiceInfo {
service_id: 1,
service: Service {
nym_address: nym_address.clone(),
service_type: ServiceType::NetworkRequester,
announcer: announcer.clone(),
block_height: 12345,
deposit: nyms(100),
},
}
);
// Announce a second service
let announcer2 = Addr::unchecked("announcer2");
let nym_address2 = NymAddress::new("nymAddress2");
setup.announce_net_req(nym_address2.clone(), announcer2.clone());
assert_eq!(setup.contract_balance(), nyms(200));
assert_eq!(
setup.query_all(),
PagedServicesListResponse {
services: vec![
service_info(1, nym_address, announcer),
service_info(2, nym_address2, announcer2)
],
per_page: SERVICE_DEFAULT_RETRIEVAL_LIMIT as usize,
start_next_after: Some(2),
}
);
}
#[test]
fn delete_service() {
let mut setup = TestSetup::new();
setup.announce_net_req(NymAddress::new("nymAddress"), Addr::unchecked("announcer"));
assert_eq!(setup.contract_balance(), nyms(100));
assert_eq!(setup.balance("announcer"), nyms(150));
assert!(!setup.query_all().services.is_empty());
setup.delete(1, Addr::unchecked("announcer"));
// Deleting the service returns the deposit to the announcer
assert_eq!(setup.contract_balance(), nyms(0));
assert_eq!(setup.balance("announcer"), nyms(250));
assert!(setup.query_all().services.is_empty());
}
#[test]
fn only_announcer_can_delete_service() {
let mut setup = TestSetup::new();
assert_eq!(setup.contract_balance(), nyms(0));
setup.announce_net_req(NymAddress::new("nymAddress"), Addr::unchecked("announcer"));
assert_eq!(setup.contract_balance(), nyms(100));
assert!(!setup.query_all().services.is_empty());
let delete_resp: ContractError = setup
.try_delete(1, Addr::unchecked("not_announcer"))
.unwrap_err()
.downcast()
.unwrap();
assert_eq!(setup.contract_balance(), nyms(100));
assert_eq!(
delete_resp,
ContractError::Unauthorized {
sender: Addr::unchecked("not_announcer")
}
);
}
#[test]
fn cant_delete_service_that_does_not_exist() {
let mut setup = TestSetup::new();
setup.announce_net_req(NymAddress::new("nymAddress"), Addr::unchecked("announcer"));
assert_eq!(setup.contract_balance(), nyms(100));
assert!(!setup.query_all().services.is_empty());
let delete_resp: ContractError = setup
.try_delete(0, Addr::unchecked("announcer"))
.unwrap_err()
.downcast()
.unwrap();
assert_eq!(setup.contract_balance(), nyms(100));
assert_eq!(delete_resp, ContractError::NotFound { service_id: 0 });
let delete_resp: ContractError = setup
.try_delete(2, Addr::unchecked("announcer"))
.unwrap_err()
.downcast()
.unwrap();
assert_eq!(setup.contract_balance(), nyms(100));
assert_eq!(delete_resp, ContractError::NotFound { service_id: 2 });
assert!(!setup.query_all().services.is_empty());
setup.delete(1, Addr::unchecked("announcer"));
assert_eq!(setup.contract_balance(), nyms(0));
assert!(setup.query_all().services.is_empty());
}
#[test]
fn announce_multiple_services_and_deleting_by_name() {
let mut setup = TestSetup::new();
let announcer1 = Addr::unchecked("wealthy_announcer_1");
let announcer2 = Addr::unchecked("wealthy_announcer_2");
let nym_address1 = NymAddress::new("nymAddress1");
let nym_address2 = NymAddress::new("nymAddress2");
// We announce the same address three times, but with different annoucers
assert_eq!(setup.contract_balance(), nyms(0));
assert_eq!(setup.balance(&announcer1), nyms(1000));
setup.announce_net_req(nym_address1.clone(), announcer1.clone());
setup.announce_net_req(nym_address1.clone(), announcer1.clone());
setup.announce_net_req(nym_address2.clone(), announcer1.clone());
setup.announce_net_req(nym_address1.clone(), announcer2.clone());
setup.announce_net_req(nym_address2.clone(), announcer2.clone());
assert_eq!(setup.contract_balance(), nyms(500));
assert_eq!(setup.balance(&announcer1), nyms(700));
assert_eq!(
setup.query_all(),
PagedServicesListResponse {
services: vec![
service_info(1, nym_address1.clone(), announcer1.clone()),
service_info(2, nym_address1.clone(), announcer1.clone()),
service_info(3, nym_address2.clone(), announcer1.clone()),
service_info(4, nym_address1.clone(), announcer2.clone()),
service_info(5, nym_address2.clone(), announcer2.clone()),
],
per_page: SERVICE_DEFAULT_RETRIEVAL_LIMIT as usize,
start_next_after: Some(5),
}
);
// Even though multiple of them point to the same nym address, we only delete the ones we actually
// own.
setup.delete_nym_address(nym_address1.clone(), announcer1.clone());
assert_eq!(setup.contract_balance(), nyms(300));
assert_eq!(setup.balance(&announcer1), nyms(900));
assert_eq!(
setup.query_all(),
PagedServicesListResponse {
services: vec![
service_info(3, nym_address2.clone(), announcer1),
service_info(4, nym_address1, announcer2.clone()),
service_info(5, nym_address2, announcer2),
],
per_page: SERVICE_DEFAULT_RETRIEVAL_LIMIT as usize,
start_next_after: Some(5),
}
);
}
// add multiple services, then query all but with a paging limit less than the number of services
// added
#[test]
fn paging_works() {
let mut setup = TestSetup::new();
let announcer1 = Addr::unchecked("wealthy_announcer_1");
let announcer2 = Addr::unchecked("wealthy_announcer_2");
let nym_address1 = NymAddress::new("nymAddress1");
let nym_address2 = NymAddress::new("nymAddress2");
// We announce the same address three times, but with different announcers
setup.announce_net_req(nym_address1.clone(), announcer1.clone());
setup.announce_net_req(nym_address1.clone(), announcer1.clone());
setup.announce_net_req(nym_address2.clone(), announcer1.clone());
setup.announce_net_req(nym_address1.clone(), announcer2.clone());
setup.announce_net_req(nym_address2.clone(), announcer2.clone());
assert_eq!(
setup.query_all_with_limit(Some(10), None),
PagedServicesListResponse {
services: vec![
service_info(1, nym_address1.clone(), announcer1.clone()),
service_info(2, nym_address1.clone(), announcer1.clone()),
service_info(3, nym_address2.clone(), announcer1.clone()),
service_info(4, nym_address1.clone(), announcer2.clone()),
service_info(5, nym_address2.clone(), announcer2.clone()),
],
per_page: 10,
start_next_after: Some(5),
}
);
assert_eq!(
setup.query_all_with_limit(Some(3), None),
PagedServicesListResponse {
services: vec![
service_info(1, nym_address1.clone(), announcer1.clone()),
service_info(2, nym_address1.clone(), announcer1.clone()),
service_info(3, nym_address2.clone(), announcer1),
],
per_page: 3,
start_next_after: Some(3),
}
);
assert_eq!(
setup.query_all_with_limit(Some(3), Some(3)),
PagedServicesListResponse {
services: vec![
service_info(4, nym_address1, announcer2.clone()),
service_info(5, nym_address2, announcer2),
],
per_page: 3,
start_next_after: Some(5),
}
);
}
#[test]
fn service_id_increases_for_new_services() {
let mut setup = TestSetup::new();
setup.announce_net_req(
NymAddress::new("nymAddress1"),
Addr::unchecked("announcer1"),
);
setup.announce_net_req(
NymAddress::new("nymAddress2"),
Addr::unchecked("announcer2"),
);
assert_eq!(
setup
.query_all()
.services
.iter()
.map(|s| s.service_id)
.collect::<Vec<_>>(),
vec![1, 2],
);
}
#[test]
fn service_id_is_not_resused_when_deleting_and_then_adding_a_new_service() {
let mut setup = TestSetup::new();
setup.announce_net_req(
NymAddress::new("nymAddress1"),
Addr::unchecked("announcer1"),
);
setup.announce_net_req(
NymAddress::new("nymAddress2"),
Addr::unchecked("announcer2"),
);
setup.announce_net_req(
NymAddress::new("nymAddress3"),
Addr::unchecked("announcer3"),
);
setup.delete(1, Addr::unchecked("announcer1"));
setup.delete(3, Addr::unchecked("announcer3"));
assert_eq!(
setup.query_all().services,
vec![service_info(
2,
NymAddress::new("nymAddress2"),
Addr::unchecked("announcer2")
)]
);
setup.announce_net_req(
NymAddress::new("nymAddress4"),
Addr::unchecked("announcer4"),
);
assert_eq!(
setup.query_all().services,
vec![
service_info(
2,
NymAddress::new("nymAddress2"),
Addr::unchecked("announcer2")
),
service_info(
4,
NymAddress::new("nymAddress4"),
Addr::unchecked("announcer4")
)
]
);
}
@@ -0,0 +1,183 @@
use cosmwasm_std::Addr;
use nym_service_provider_directory_common::{
response::PagedServicesListResponse, NymAddress, Service, ServiceDetails, ServiceType,
};
use rstest::rstest;
use crate::{
constants::SERVICE_DEFAULT_RETRIEVAL_LIMIT,
test_helpers::{fixture::new_service, helpers::nyms},
SpContractError,
};
use super::test_setup::TestSetup;
#[rstest::fixture]
fn setup() -> TestSetup {
TestSetup::new()
}
#[rstest]
fn basic_announce(mut setup: TestSetup) {
assert_eq!(
setup.query_all(),
PagedServicesListResponse {
services: vec![],
per_page: SERVICE_DEFAULT_RETRIEVAL_LIMIT as usize,
start_next_after: None,
}
);
// Announce a first service
let announcer = Addr::unchecked("announcer");
let nym_address = NymAddress::new("nymAddress");
assert_eq!(setup.contract_balance(), nyms(0));
assert_eq!(setup.balance(&announcer), nyms(250));
assert_eq!(setup.query_signing_nonce(announcer.to_string()), 0);
let service = setup.new_service(&nym_address);
let payload = setup.payload_to_sign(&announcer, &nyms(100), &service.service);
let service = service.sign(payload);
setup.announce_net_req(&service, &announcer);
// Deposit is deposited to contract and deducted from announcers's balance
assert_eq!(setup.contract_balance(), nyms(100));
assert_eq!(setup.balance(&announcer), nyms(150));
// The signing nonce has been incremented
assert_eq!(setup.query_signing_nonce(announcer.to_string()), 1);
// We can query the full service list
assert_eq!(
setup.query_all(),
PagedServicesListResponse {
services: vec![Service {
service_id: 1,
service: ServiceDetails {
nym_address: nym_address.clone(),
service_type: ServiceType::NetworkRequester,
identity_key: service.identity_key().to_string(),
},
announcer: announcer.clone(),
block_height: 12345,
deposit: nyms(100),
}],
per_page: SERVICE_DEFAULT_RETRIEVAL_LIMIT as usize,
start_next_after: Some(1),
}
);
// ... and we can query by id
assert_eq!(
setup.query_id(1),
Service {
service_id: 1,
service: service.details().clone(),
announcer: announcer.clone(),
block_height: 12345,
deposit: nyms(100),
}
);
// Announce a second service
let announcer2 = Addr::unchecked("announcer2");
let nym_address2 = NymAddress::new("nymAddress2");
let service2 = setup.new_signed_service(&nym_address2, &announcer2, &nyms(100));
setup.announce_net_req(&service2, &announcer2);
assert_eq!(setup.query_signing_nonce(announcer2.to_string()), 1);
assert_eq!(setup.contract_balance(), nyms(200));
assert_eq!(
setup.query_all(),
PagedServicesListResponse {
services: vec![
new_service(1, &nym_address, &announcer, service.identity_key()),
new_service(2, &nym_address2, &announcer2, service2.identity_key())
],
per_page: SERVICE_DEFAULT_RETRIEVAL_LIMIT as usize,
start_next_after: Some(2),
}
);
}
#[rstest]
fn announce_fails_when_announcer_mismatch(mut setup: TestSetup) {
let announcer = Addr::unchecked("steve");
let nym_address = NymAddress::new("foobar");
let service = setup.new_signed_service(&nym_address, &announcer, &nyms(100));
// A difference announcer tries to announce the service
let announcer2 = Addr::unchecked("timmy");
let resp: SpContractError = setup
.try_announce_net_req(&service, &announcer2)
.unwrap_err()
.downcast()
.unwrap();
assert_eq!(resp, SpContractError::InvalidEd25519Signature);
}
#[rstest]
fn signing_nonce_is_increased_when_announcing(mut setup: TestSetup) {
let announcer1 = Addr::unchecked("announcer1");
let announcer2 = Addr::unchecked("announcer2");
assert_eq!(setup.query_signing_nonce(announcer1.to_string()), 0);
assert_eq!(setup.query_signing_nonce(announcer2.to_string()), 0);
setup.sign_and_announce_net_req(&NymAddress::new("nymAddress1"), &announcer1, &nyms(100));
assert_eq!(setup.query_signing_nonce(announcer1.to_string()), 1);
assert_eq!(setup.query_signing_nonce(announcer2.to_string()), 0);
setup.sign_and_announce_net_req(&NymAddress::new("nymAddress2"), &announcer2, &nyms(100));
assert_eq!(setup.query_signing_nonce(announcer1.to_string()), 1);
assert_eq!(setup.query_signing_nonce(announcer2.to_string()), 1);
setup.sign_and_announce_net_req(&NymAddress::new("nymAddress3"), &announcer2, &nyms(100));
assert_eq!(setup.query_signing_nonce(announcer1.to_string()), 1);
assert_eq!(setup.query_signing_nonce(announcer2.to_string()), 2);
}
#[rstest]
fn creating_two_services_in_a_row_without_announcing_fails(mut setup: TestSetup) {
let announcer = Addr::unchecked("wealthy_announcer_1");
let nym_address1 = NymAddress::new("nymAddress1");
let nym_address2 = NymAddress::new("nymAddress2");
let deposit = nyms(100);
let s1 = setup.new_signed_service(&nym_address1, &announcer, &deposit);
// This second service will be signed with the same nonce
let s2 = setup.new_signed_service(&nym_address2, &announcer, &deposit);
// Announce the first service works, and this increments the nonce
setup.announce_net_req(&s1, &announcer);
// Now the nonce has been incremented, and the signature will not match
let resp: SpContractError = setup
.try_announce_net_req(&s2, &announcer)
.unwrap_err()
.downcast()
.unwrap();
assert_eq!(resp, SpContractError::InvalidEd25519Signature,);
}
#[rstest]
fn announcing_the_same_service_twice_fails(mut setup: TestSetup) {
let announcer = Addr::unchecked("wealthy_announcer_1");
let nym_address = NymAddress::new("nymAddress1");
let s1 = setup.new_signed_service(&nym_address, &announcer, &nyms(100));
setup.announce_net_req(&s1, &announcer);
// Now the nonce has been incremented, and the signature will not match
let resp: SpContractError = setup
.try_announce_net_req(&s1, &announcer)
.unwrap_err()
.downcast()
.unwrap();
assert_eq!(resp, SpContractError::InvalidEd25519Signature);
}
@@ -0,0 +1,144 @@
use cosmwasm_std::Addr;
use nym_service_provider_directory_common::{response::PagedServicesListResponse, NymAddress};
use crate::{
constants::SERVICE_DEFAULT_RETRIEVAL_LIMIT,
test_helpers::{fixture::new_service, helpers::nyms},
SpContractError,
};
use super::test_setup::TestSetup;
#[test]
fn delete_service() {
let mut setup = TestSetup::new();
setup.sign_and_announce_net_req(
&NymAddress::new("nymAddress"),
&Addr::unchecked("announcer"),
&nyms(100),
);
assert_eq!(setup.contract_balance(), nyms(100));
assert_eq!(setup.balance("announcer"), nyms(150));
assert!(!setup.query_all().services.is_empty());
setup.delete(1, &Addr::unchecked("announcer"));
// Deleting the service returns the deposit to the announcer
assert_eq!(setup.contract_balance(), nyms(0));
assert_eq!(setup.balance("announcer"), nyms(250));
assert!(setup.query_all().services.is_empty());
}
#[test]
fn only_announcer_can_delete_service() {
let mut setup = TestSetup::new();
assert_eq!(setup.contract_balance(), nyms(0));
setup.sign_and_announce_net_req(
&NymAddress::new("nymAddress"),
&Addr::unchecked("announcer"),
&nyms(100),
);
assert_eq!(setup.contract_balance(), nyms(100));
assert!(!setup.query_all().services.is_empty());
let delete_resp: SpContractError = setup
.try_delete(1, &Addr::unchecked("not_announcer"))
.unwrap_err()
.downcast()
.unwrap();
assert_eq!(setup.contract_balance(), nyms(100));
assert_eq!(
delete_resp,
SpContractError::Unauthorized {
sender: Addr::unchecked("not_announcer")
}
);
}
#[test]
fn cant_delete_service_that_does_not_exist() {
let mut setup = TestSetup::new();
setup.sign_and_announce_net_req(
&NymAddress::new("nymAddress"),
&Addr::unchecked("announcer"),
&nyms(100),
);
assert_eq!(setup.contract_balance(), nyms(100));
assert!(!setup.query_all().services.is_empty());
let delete_resp: SpContractError = setup
.try_delete(0, &Addr::unchecked("announcer"))
.unwrap_err()
.downcast()
.unwrap();
assert_eq!(setup.contract_balance(), nyms(100));
assert_eq!(delete_resp, SpContractError::NotFound { service_id: 0 });
let delete_resp: SpContractError = setup
.try_delete(2, &Addr::unchecked("announcer"))
.unwrap_err()
.downcast()
.unwrap();
assert_eq!(setup.contract_balance(), nyms(100));
assert_eq!(delete_resp, SpContractError::NotFound { service_id: 2 });
assert!(!setup.query_all().services.is_empty());
setup.delete(1, &Addr::unchecked("announcer"));
assert_eq!(setup.contract_balance(), nyms(0));
assert!(setup.query_all().services.is_empty());
}
#[test]
fn announce_multiple_services_and_deleting_by_name() {
let mut setup = TestSetup::new();
let announcer1 = Addr::unchecked("wealthy_announcer_1");
let announcer2 = Addr::unchecked("wealthy_announcer_2");
let nym_address1 = NymAddress::new("nymAddress1");
let nym_address2 = NymAddress::new("nymAddress2");
let deposit = nyms(100);
// We announce the same address three times, but with different annoucers
assert_eq!(setup.contract_balance(), nyms(0));
assert_eq!(setup.balance(&announcer1), nyms(1000));
let s1 = setup.sign_and_announce_net_req(&nym_address1, &announcer1, &deposit);
let s2 = setup.sign_and_announce_net_req(&nym_address1, &announcer1, &deposit);
let s3 = setup.sign_and_announce_net_req(&nym_address2, &announcer1, &deposit);
let s4 = setup.sign_and_announce_net_req(&nym_address1, &announcer2, &deposit);
let s5 = setup.sign_and_announce_net_req(&nym_address2, &announcer2, &deposit);
assert_eq!(setup.contract_balance(), nyms(500));
assert_eq!(setup.balance(&announcer1), nyms(700));
assert_eq!(
setup.query_all(),
PagedServicesListResponse {
services: vec![
new_service(1, &nym_address1, &announcer1, s1.identity_key()),
new_service(2, &nym_address1, &announcer1, s2.identity_key()),
new_service(3, &nym_address2, &announcer1, s3.identity_key()),
new_service(4, &nym_address1, &announcer2, s4.identity_key()),
new_service(5, &nym_address2, &announcer2, s5.identity_key()),
],
per_page: SERVICE_DEFAULT_RETRIEVAL_LIMIT as usize,
start_next_after: Some(5),
}
);
// Even though multiple of them point to the same nym address, we only delete the ones we actually
// own.
setup.delete_nym_address(&nym_address1, &announcer1);
assert_eq!(setup.contract_balance(), nyms(300));
assert_eq!(setup.balance(&announcer1), nyms(900));
assert_eq!(
setup.query_all(),
PagedServicesListResponse {
services: vec![
new_service(3, &nym_address2, &announcer1, s3.identity_key()),
new_service(4, &nym_address1, &announcer2, s4.identity_key()),
new_service(5, &nym_address2, &announcer2, s5.identity_key()),
],
per_page: SERVICE_DEFAULT_RETRIEVAL_LIMIT as usize,
start_next_after: Some(5),
}
);
}
@@ -0,0 +1,13 @@
//! Integration tests using cw-multi-test.
mod announce;
mod delete;
mod query;
mod service_id;
mod test_service;
mod test_setup;
#[test]
fn instantiate_contract() {
test_setup::TestSetup::new();
}
@@ -0,0 +1,77 @@
use cosmwasm_std::Addr;
use nym_service_provider_directory_common::{
response::{ConfigResponse, PagedServicesListResponse},
NymAddress,
};
use crate::test_helpers::{fixture::new_service, helpers::nyms};
use super::test_setup::TestSetup;
#[test]
fn query_config() {
assert_eq!(
TestSetup::new().query_config(),
ConfigResponse {
deposit_required: nyms(100),
}
);
}
// add multiple services, then query all but with a paging limit less than the number of services
// added
#[test]
fn paging_works() {
let mut setup = TestSetup::new();
let announcer1 = Addr::unchecked("wealthy_announcer_1");
let announcer2 = Addr::unchecked("wealthy_announcer_2");
let nym_address1 = NymAddress::new("nymAddress1");
let nym_address2 = NymAddress::new("nymAddress2");
let deposit = nyms(100);
// We announce the same address three times, but with different announcers
let s1 = setup.sign_and_announce_net_req(&nym_address1, &announcer1, &deposit);
let s2 = setup.sign_and_announce_net_req(&nym_address1, &announcer1, &deposit);
let s3 = setup.sign_and_announce_net_req(&nym_address2, &announcer1, &deposit);
let s4 = setup.sign_and_announce_net_req(&nym_address1, &announcer2, &deposit);
let s5 = setup.sign_and_announce_net_req(&nym_address2, &announcer2, &deposit);
assert_eq!(
setup.query_all_with_limit(Some(10), None),
PagedServicesListResponse {
services: vec![
new_service(1, &nym_address1, &announcer1, s1.identity_key()),
new_service(2, &nym_address1, &announcer1, s2.identity_key()),
new_service(3, &nym_address2, &announcer1, s3.identity_key()),
new_service(4, &nym_address1, &announcer2, s4.identity_key()),
new_service(5, &nym_address2, &announcer2, s5.identity_key()),
],
per_page: 10,
start_next_after: Some(5),
}
);
assert_eq!(
setup.query_all_with_limit(Some(3), None),
PagedServicesListResponse {
services: vec![
new_service(1, &nym_address1, &announcer1, s1.identity_key()),
new_service(2, &nym_address1, &announcer1, s2.identity_key()),
new_service(3, &nym_address2, &announcer1, s3.identity_key()),
],
per_page: 3,
start_next_after: Some(3),
}
);
assert_eq!(
setup.query_all_with_limit(Some(3), Some(3)),
PagedServicesListResponse {
services: vec![
new_service(4, &nym_address1, &announcer2, s4.identity_key()),
new_service(5, &nym_address2, &announcer2, s5.identity_key()),
],
per_page: 3,
start_next_after: Some(5),
}
);
}
@@ -0,0 +1,89 @@
use cosmwasm_std::Addr;
use nym_service_provider_directory_common::NymAddress;
use crate::test_helpers::{fixture::new_service, helpers::nyms};
use super::test_setup::TestSetup;
#[test]
fn service_id_increases_for_new_services() {
let mut setup = TestSetup::new();
setup.sign_and_announce_net_req(
&NymAddress::new("nymAddress1"),
&Addr::unchecked("announcer1"),
&nyms(100),
);
setup.sign_and_announce_net_req(
&NymAddress::new("nymAddress2"),
&Addr::unchecked("announcer2"),
&nyms(100),
);
assert_eq!(
setup
.query_all()
.services
.iter()
.map(|s| s.service_id)
.collect::<Vec<_>>(),
vec![1, 2],
);
}
#[test]
fn service_id_is_not_resused_when_deleting_and_then_adding_a_new_service() {
let mut setup = TestSetup::new();
setup.sign_and_announce_net_req(
&NymAddress::new("nymAddress1"),
&Addr::unchecked("announcer1"),
&nyms(100),
);
let s2 = setup.sign_and_announce_net_req(
&NymAddress::new("nymAddress2"),
&Addr::unchecked("announcer2"),
&nyms(100),
);
setup.sign_and_announce_net_req(
&NymAddress::new("nymAddress3"),
&Addr::unchecked("announcer3"),
&nyms(100),
);
setup.delete(1, &Addr::unchecked("announcer1"));
setup.delete(3, &Addr::unchecked("announcer3"));
assert_eq!(
setup.query_all().services,
vec![new_service(
2,
&NymAddress::new("nymAddress2"),
&Addr::unchecked("announcer2"),
s2.identity_key(),
)]
);
let s4 = setup.new_signed_service(
&NymAddress::new("nymAddress4"),
&Addr::unchecked("announcer4"),
&nyms(100),
);
setup.announce_net_req(&s4, &Addr::unchecked("announcer4"));
assert_eq!(
setup.query_all().services,
vec![
new_service(
2,
&NymAddress::new("nymAddress2"),
&Addr::unchecked("announcer2"),
s2.identity_key(),
),
new_service(
4,
&NymAddress::new("nymAddress4"),
&Addr::unchecked("announcer4"),
s4.identity_key(),
)
]
);
}
@@ -0,0 +1,75 @@
use nym_contracts_common::{signing::MessageSignature, IdentityKey};
use nym_crypto::asymmetric::identity;
use nym_service_provider_directory_common::{
signing_types::SignableServiceProviderAnnounceMsg, NymAddress, ServiceDetails, ServiceType,
};
use rand_chacha::ChaCha20Rng;
use crate::test_helpers::signing::ed25519_sign_message;
pub struct TestService {
pub service: ServiceDetails,
pub keys: identity::KeyPair,
pub rng: ChaCha20Rng,
}
impl TestService {
pub fn new(rng: &mut ChaCha20Rng, nym_address: NymAddress) -> Self {
let keys = identity::KeyPair::new(rng);
let service = ServiceDetails {
nym_address,
service_type: ServiceType::NetworkRequester,
identity_key: keys.public_key().to_base58_string(),
};
Self {
service,
keys,
rng: rng.clone(),
}
}
pub fn identity_key(&self) -> &IdentityKey {
&self.service.identity_key
}
pub fn details(&self) -> &ServiceDetails {
&self.service
}
pub fn sign(self, payload: SignableServiceProviderAnnounceMsg) -> SignedTestService {
let owner_signature = ed25519_sign_message(payload, self.keys.private_key());
SignedTestService {
service: self.service,
keys: self.keys,
owner_signature,
}
}
}
impl From<TestService> for ServiceDetails {
fn from(test_service: TestService) -> Self {
test_service.service
}
}
pub struct SignedTestService {
pub service: ServiceDetails,
pub keys: identity::KeyPair,
pub owner_signature: MessageSignature,
}
impl SignedTestService {
pub fn identity_key(&self) -> &IdentityKey {
&self.service.identity_key
}
pub fn details(&self) -> &ServiceDetails {
&self.service
}
}
impl From<SignedTestService> for ServiceDetails {
fn from(signed_service: SignedTestService) -> Self {
signed_service.service
}
}
@@ -1,14 +1,21 @@
use anyhow::Result;
use cosmwasm_std::{coins, Addr, Coin, Uint128};
use cw_multi_test::{App, AppBuilder, AppResponse, ContractWrapper, Executor};
use nym_contracts_common::signing::Nonce;
use nym_service_provider_directory_common::{
msg::{ExecuteMsg, InstantiateMsg, QueryMsg},
response::{ConfigResponse, PagedServicesListResponse},
NymAddress, ServiceId, ServiceInfo, ServiceType,
signing_types::{
construct_service_provider_announce_sign_payload, SignableServiceProviderAnnounceMsg,
},
NymAddress, Service, ServiceDetails, ServiceId,
};
use rand_chacha::ChaCha20Rng;
use serde::de::DeserializeOwned;
use crate::test_helpers::helpers::get_app_attribute;
use crate::test_helpers::helpers::{get_app_attribute, test_rng};
use super::test_service::{SignedTestService, TestService};
const DENOM: &str = "unym";
const ADDRESSES: &[&str] = &[
@@ -19,6 +26,8 @@ const ADDRESSES: &[&str] = &[
"announcer2",
"announcer3",
"announcer4",
"steve",
"timmy",
];
const WEALTHY_ADDRESSES: &[&str] = &["wealthy_announcer_1", "wealthy_announcer_2"];
@@ -26,6 +35,7 @@ const WEALTHY_ADDRESSES: &[&str] = &["wealthy_announcer_1", "wealthy_announcer_2
pub struct TestSetup {
app: App,
addr: Addr,
rng: ChaCha20Rng,
}
impl Default for TestSetup {
@@ -51,7 +61,8 @@ impl TestSetup {
let code = ContractWrapper::new(crate::execute, crate::instantiate, crate::query);
let code_id = app.store_code(Box::new(code));
let addr = Self::instantiate(&mut app, code_id);
TestSetup { app, addr }
let rng = test_rng();
TestSetup { app, addr, rng }
}
fn instantiate(app: &mut App, code_id: u64) -> Addr {
@@ -68,11 +79,6 @@ impl TestSetup {
.unwrap()
}
#[allow(unused)]
pub fn address(&self) -> &Addr {
&self.addr
}
pub fn contract_balance(&self) -> Coin {
self.app.wrap().query_balance(&self.addr, DENOM).unwrap()
}
@@ -88,7 +94,7 @@ impl TestSetup {
self.query(&QueryMsg::Config {})
}
pub fn query_id(&self, service_id: ServiceId) -> ServiceInfo {
pub fn query_id(&self, service_id: ServiceId) -> Service {
self.query(&QueryMsg::ServiceId { service_id })
}
@@ -104,22 +110,69 @@ impl TestSetup {
self.query(&QueryMsg::All { limit, start_after })
}
pub fn announce_net_req(&mut self, address: NymAddress, announcer: Addr) -> AppResponse {
let resp = self
.app
.execute_contract(
announcer,
self.addr.clone(),
&ExecuteMsg::Announce {
nym_address: address,
service_type: ServiceType::NetworkRequester,
},
&[Coin {
denom: DENOM.to_string(),
amount: Uint128::new(100),
}],
)
.unwrap();
pub fn query_signing_nonce(&self, address: String) -> Nonce {
self.query(&QueryMsg::SigningNonce { address })
}
// Create a new service, together with its signing keypair
pub fn new_service(&mut self, nym_address: &NymAddress) -> TestService {
TestService::new(&mut self.rng, nym_address.clone())
}
// Create payload for the service operator to sign
pub fn payload_to_sign(
&mut self,
announcer: &Addr,
deposit: &Coin,
service: &ServiceDetails,
) -> SignableServiceProviderAnnounceMsg {
let nonce = self.query_signing_nonce(announcer.to_string());
construct_service_provider_announce_sign_payload(
nonce,
announcer.clone(),
deposit.clone(),
service.clone(),
)
}
// Convenience function for creating a new service and signing it.
pub fn new_signed_service(
&mut self,
nym_address: &NymAddress,
announcer: &Addr,
deposit: &Coin,
) -> SignedTestService {
let service = self.new_service(nym_address);
let payload = self.payload_to_sign(announcer, deposit, service.details());
service.sign(payload)
}
// Announce a new service
pub fn try_announce_net_req(
&mut self,
service: &SignedTestService,
announcer: &Addr,
) -> Result<AppResponse> {
self.app.execute_contract(
announcer.clone(),
self.addr.clone(),
&ExecuteMsg::Announce {
service: service.service.clone(),
owner_signature: service.owner_signature.clone(),
},
&[Coin {
denom: DENOM.to_string(),
amount: Uint128::new(100),
}],
)
}
pub fn announce_net_req(
&mut self,
service: &SignedTestService,
announcer: &Addr,
) -> AppResponse {
let resp = self.try_announce_net_req(service, announcer).unwrap();
assert_eq!(
get_app_attribute(&resp, "wasm-announce", "action"),
"announce"
@@ -127,16 +180,28 @@ impl TestSetup {
resp
}
pub fn try_delete(&mut self, service_id: ServiceId, announcer: Addr) -> Result<AppResponse> {
// Convenience function for create a new signed service and announcing it
pub fn sign_and_announce_net_req(
&mut self,
nym_address: &NymAddress,
announcer: &Addr,
deposit: &Coin,
) -> SignedTestService {
let service = self.new_signed_service(nym_address, announcer, deposit);
let _ = self.announce_net_req(&service, announcer);
service
}
pub fn try_delete(&mut self, service_id: ServiceId, announcer: &Addr) -> Result<AppResponse> {
self.app.execute_contract(
announcer,
announcer.clone(),
self.addr.clone(),
&ExecuteMsg::DeleteId { service_id },
&[],
)
}
pub fn delete(&mut self, service_id: ServiceId, announcer: Addr) -> AppResponse {
pub fn delete(&mut self, service_id: ServiceId, announcer: &Addr) -> AppResponse {
let delete_resp = self.try_delete(service_id, announcer).unwrap();
assert_eq!(
get_app_attribute(&delete_resp, "wasm-delete_id", "action"),
@@ -145,12 +210,18 @@ impl TestSetup {
delete_resp
}
pub fn delete_nym_address(&mut self, nym_address: NymAddress, announcer: Addr) -> AppResponse {
pub fn delete_nym_address(
&mut self,
nym_address: &NymAddress,
announcer: &Addr,
) -> AppResponse {
self.app
.execute_contract(
announcer,
announcer.clone(),
self.addr.clone(),
&ExecuteMsg::DeleteNymAddress { nym_address },
&ExecuteMsg::DeleteNymAddress {
nym_address: nym_address.clone(),
},
&[],
)
.unwrap()
@@ -4,7 +4,8 @@
#![warn(clippy::expect_used)]
#![warn(clippy::unwrap_used)]
use crate::error::Result;
pub use nym_service_provider_directory_common::error::{Result, SpContractError};
use nym_service_provider_directory_common::msg::{
ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg,
};
@@ -12,14 +13,11 @@ use nym_service_provider_directory_common::msg::{
#[cfg(not(feature = "library"))]
use cosmwasm_std::entry_point;
use cosmwasm_std::{Binary, Deps, DepsMut, Env, MessageInfo, Response};
use error::ContractError;
mod constants;
mod contract;
mod error;
mod state;
pub mod constants;
#[cfg(test)]
mod integration_tests;
#[cfg(test)]
@@ -38,7 +36,7 @@ pub fn instantiate(
/// Contract entry point for migrations.
#[cfg_attr(not(feature = "library"), entry_point)]
pub fn migrate(deps: DepsMut<'_>, env: Env, msg: MigrateMsg) -> Result<Response, ContractError> {
pub fn migrate(deps: DepsMut<'_>, env: Env, msg: MigrateMsg) -> Result<Response, SpContractError> {
contract::migrate(deps, env, msg)
}
@@ -49,7 +47,7 @@ pub fn execute(
env: Env,
info: MessageInfo,
msg: ExecuteMsg,
) -> Result<Response, ContractError> {
) -> Result<Response, SpContractError> {
contract::execute(deps, env, info, msg)
}
@@ -1,7 +1,7 @@
use cosmwasm_std::{Addr, Deps, DepsMut};
use cw_controllers::Admin;
use crate::{constants::ADMIN_KEY, error::Result};
use crate::{constants::ADMIN_KEY, Result};
const ADMIN: Admin = Admin::new(ADMIN_KEY);
@@ -3,7 +3,7 @@ use cw_storage_plus::Item;
use nym_service_provider_directory_common::response::ConfigResponse;
use serde::{Deserialize, Serialize};
use crate::{constants::CONFIG_KEY, error::Result};
use crate::{constants::CONFIG_KEY, Result};
const CONFIG: Item<Config> = Item::new(CONFIG_KEY);
@@ -1,8 +1,13 @@
pub mod admin;
pub mod config;
pub mod service_id_counter;
pub mod services;
mod admin;
mod config;
mod nonce;
mod service_id_counter;
mod services;
pub(crate) use admin::{assert_admin, set_admin};
pub(crate) use config::{deposit_required, load_config, save_config, Config};
pub(crate) use nonce::{get_signing_nonce, increment_signing_nonce};
pub(crate) use service_id_counter::next_service_id_counter;
pub(crate) use services::{
has_service, load_all_paged, load_announcer, load_id, load_nym_address, remove, save, PagedLoad,
};
@@ -0,0 +1,71 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::{constants::SIGNING_NONCES_NAMESPACE, Result};
use cosmwasm_std::{Addr, Storage};
use cw_storage_plus::Map;
use nym_contracts_common::signing::Nonce;
pub const NONCES: Map<'_, Addr, Nonce> = Map::new(SIGNING_NONCES_NAMESPACE);
pub fn get_signing_nonce(storage: &dyn Storage, address: Addr) -> Result<Nonce> {
let nonce = NONCES.may_load(storage, address)?.unwrap_or(0);
Ok(nonce)
}
fn update_signing_nonce(storage: &mut dyn Storage, address: Addr, value: Nonce) -> Result<()> {
NONCES
.save(storage, address, &value)
.map_err(|err| err.into())
}
pub fn increment_signing_nonce(storage: &mut dyn Storage, address: Addr) -> Result<()> {
// get the current nonce
let nonce = get_signing_nonce(storage, address.clone())?;
// increment it for the next use
update_signing_nonce(storage, address, nonce + 1)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::test_helpers::transactions::instantiate_test_contract;
use cosmwasm_std::{
testing::{MockApi, MockQuerier},
MemoryStorage, OwnedDeps,
};
use rstest::rstest;
type TestDeps = OwnedDeps<MemoryStorage, MockApi, MockQuerier>;
#[rstest::fixture]
fn deps() -> TestDeps {
instantiate_test_contract()
}
fn addr(s: &str) -> Addr {
Addr::unchecked(s)
}
#[rstest]
fn getting_signing_nonce_doesnt_increment_it(deps: TestDeps) {
assert_eq!(get_signing_nonce(&deps.storage, addr("gunnar")).unwrap(), 0);
assert_eq!(get_signing_nonce(&deps.storage, addr("gunnar")).unwrap(), 0);
}
#[rstest]
fn increment_works(mut deps: TestDeps) {
assert_eq!(get_signing_nonce(&deps.storage, addr("gunnar")).unwrap(), 0);
increment_signing_nonce(&mut deps.storage, addr("gunnar")).unwrap();
assert_eq!(get_signing_nonce(&deps.storage, addr("gunnar")).unwrap(), 1);
}
#[rstest]
fn incrementing_is_independent(mut deps: TestDeps) {
increment_signing_nonce(&mut deps.storage, addr("gunnar")).unwrap();
assert_eq!(get_signing_nonce(&deps.storage, addr("gunnar")).unwrap(), 1);
assert_eq!(get_signing_nonce(&deps.storage, addr("bjorn")).unwrap(), 0);
}
}
@@ -2,7 +2,7 @@ use cosmwasm_std::Storage;
use cw_storage_plus::Item;
use nym_service_provider_directory_common::ServiceId;
use crate::{constants::SERVICE_ID_COUNTER_KEY, error::Result};
use crate::{constants::SERVICE_ID_COUNTER_KEY, Result};
const SERVICE_ID_COUNTER: Item<ServiceId> = Item::new(SERVICE_ID_COUNTER_KEY);
@@ -17,29 +17,50 @@ pub(crate) fn next_service_id_counter(store: &mut dyn Storage) -> Result<Service
#[cfg(test)]
mod tests {
use nym_service_provider_directory_common::ServiceInfo;
use cosmwasm_std::Addr;
use nym_service_provider_directory_common::Service;
use crate::test_helpers::{
assert::assert_services,
fixture::service_fixture,
helpers::{announce_service, delete_service, instantiate_test_contract},
helpers::{nyms, test_rng},
transactions::{announce_service, delete_service, instantiate_test_contract},
};
#[test]
fn get_next_service_id() {
let mut deps = instantiate_test_contract();
let mut rng = test_rng();
assert_eq!(announce_service(deps.as_mut(), &service_fixture()), 1);
assert_services(deps.as_ref(), &[ServiceInfo::new(1, service_fixture())]);
assert_eq!(announce_service(deps.as_mut(), &service_fixture()), 2);
assert_eq!(announce_service(deps.as_mut(), &service_fixture()), 3);
let (id1, service1) = announce_service(deps.as_mut(), &mut rng, "addr1", "timmy");
let (id2, service2) = announce_service(deps.as_mut(), &mut rng, "addr2", "timmy");
let (id3, service3) = announce_service(deps.as_mut(), &mut rng, "addr3", "timmy");
assert_eq!(id1, 1);
assert_eq!(id2, 2);
assert_eq!(id3, 3);
assert_services(
deps.as_ref(),
&[
ServiceInfo::new(1, service_fixture()),
ServiceInfo::new(2, service_fixture()),
ServiceInfo::new(3, service_fixture()),
Service {
service_id: 1,
service: service1,
announcer: Addr::unchecked("timmy"),
block_height: 12345,
deposit: nyms(100),
},
Service {
service_id: 2,
service: service2,
announcer: Addr::unchecked("timmy"),
block_height: 12345,
deposit: nyms(100),
},
Service {
service_id: 3,
service: service3,
announcer: Addr::unchecked("timmy"),
block_height: 12345,
deposit: nyms(100),
},
],
);
}
@@ -47,31 +68,28 @@ mod tests {
#[test]
fn deleted_service_id_is_not_reused() {
let mut deps = instantiate_test_contract();
let mut rng = test_rng();
// Announce
assert_eq!(announce_service(deps.as_mut(), &service_fixture()), 1);
assert_eq!(announce_service(deps.as_mut(), &service_fixture()), 2);
let (_, service1) = announce_service(deps.as_mut(), &mut rng, "addr1", "timmy");
let _ = announce_service(deps.as_mut(), &mut rng, "addr2", "timmy");
//// Delete the last entry
delete_service(deps.as_mut(), 2, "timmy");
assert_services(
deps.as_ref(),
&[
ServiceInfo::new(1, service_fixture()),
ServiceInfo::new(2, service_fixture()),
],
&[Service {
service_id: 1,
service: service1,
announcer: Addr::unchecked("timmy"),
block_height: 12345,
deposit: nyms(100),
}],
);
// Delete the last entry
delete_service(deps.as_mut(), 2, "steve");
assert_services(deps.as_ref(), &[ServiceInfo::new(1, service_fixture())]);
// Create a third entry. The index should not reuse the previous entry that we just
// deleted.
assert_eq!(announce_service(deps.as_mut(), &service_fixture()), 3);
assert_services(
deps.as_ref(),
&[
ServiceInfo::new(1, service_fixture()),
ServiceInfo::new(3, service_fixture()),
],
);
let (id3, _) = announce_service(deps.as_mut(), &mut rng, "addr3", "timmy");
assert_eq!(id3, 3);
}
}
@@ -8,7 +8,7 @@ use crate::{
SERVICES_ANNOUNCER_IDX_NAMESPACE, SERVICES_NYM_ADDRESS_IDX_NAMESPACE,
SERVICES_PK_NAMESPACE, SERVICE_DEFAULT_RETRIEVAL_LIMIT, SERVICE_MAX_RETRIEVAL_LIMIT,
},
error::{ContractError, Result},
Result, SpContractError,
};
struct ServiceIndex<'a> {
@@ -26,7 +26,7 @@ impl<'a> IndexList<Service> for ServiceIndex<'a> {
fn services<'a>() -> IndexedMap<'a, ServiceId, Service, ServiceIndex<'a>> {
let indexes = ServiceIndex {
nym_address: MultiIndex::new(
|d| d.nym_address.to_string(),
|d| d.service.nym_address.to_string(),
SERVICES_PK_NAMESPACE,
SERVICES_NYM_ADDRESS_IDX_NAMESPACE,
),
@@ -39,10 +39,10 @@ fn services<'a>() -> IndexedMap<'a, ServiceId, Service, ServiceIndex<'a>> {
IndexedMap::new(SERVICES_PK_NAMESPACE, indexes)
}
pub fn save(store: &mut dyn Storage, new_service: &Service) -> Result<ServiceId> {
let service_id = super::next_service_id_counter(store)?;
pub fn save(store: &mut dyn Storage, new_service: &Service) -> Result<()> {
let service_id = new_service.service_id;
services().save(store, service_id, new_service)?;
Ok(service_id)
Ok(())
}
pub fn remove(store: &mut dyn Storage, service_id: ServiceId) -> Result<()> {
@@ -55,39 +55,38 @@ pub fn has_service(store: &dyn Storage, service_id: ServiceId) -> bool {
pub fn load_id(store: &dyn Storage, service_id: ServiceId) -> Result<Service> {
services().load(store, service_id).map_err(|err| match err {
StdError::NotFound { .. } => ContractError::NotFound { service_id },
StdError::NotFound { .. } => SpContractError::NotFound { service_id },
err => err.into(),
})
}
pub fn load_announcer(store: &dyn Storage, announcer: Addr) -> Result<Vec<(ServiceId, Service)>> {
pub fn load_announcer(store: &dyn Storage, announcer: Addr) -> Result<Vec<Service>> {
let services = services()
.idx
.announcer
.prefix(announcer)
.range(store, None, None, Order::Ascending)
.take(MAX_NUMBER_OF_PROVIDERS_PER_ANNOUNCER as usize)
.map(|res| res.map(|item| item.1))
.collect::<StdResult<Vec<_>>>()?;
Ok(services)
}
pub fn load_nym_address(
store: &dyn Storage,
nym_address: NymAddress,
) -> Result<Vec<(ServiceId, Service)>> {
pub fn load_nym_address(store: &dyn Storage, nym_address: NymAddress) -> Result<Vec<Service>> {
let services = services()
.idx
.nym_address
.prefix(nym_address.to_string())
.range(store, None, None, Order::Ascending)
.take(MAX_NUMBER_OF_ALIASES_FOR_NYM_ADDRESS as usize)
.map(|res| res.map(|item| item.1))
.collect::<StdResult<Vec<_>>>()?;
Ok(services)
}
#[derive(Debug, PartialEq)]
pub struct PagedLoad {
pub services: Vec<(ServiceId, Service)>,
pub services: Vec<Service>,
pub limit: usize,
pub start_next_after: Option<ServiceId>,
}
@@ -106,9 +105,10 @@ pub fn load_all_paged(
let services = services()
.range(store, start, None, Order::Ascending)
.take(limit)
.collect::<StdResult<Vec<_>>>()?;
.map(|res| res.map(|item| item.1))
.collect::<StdResult<Vec<Service>>>()?;
let start_next_after = services.last().map(|service| service.0);
let start_next_after = services.last().map(|service| service.service_id);
Ok(PagedLoad {
services,
@@ -119,122 +119,128 @@ pub fn load_all_paged(
#[cfg(test)]
mod tests {
use cosmwasm_std::{
testing::{MockApi, MockQuerier},
MemoryStorage, OwnedDeps,
};
use rstest::rstest;
use crate::{
error::ContractError,
test_helpers::{
fixture::{service_fixture, service_fixture_with_address},
helpers::instantiate_test_contract,
transactions::instantiate_test_contract,
},
SpContractError,
};
use super::*;
#[test]
fn save_works() {
let mut deps = instantiate_test_contract();
type TestDeps = OwnedDeps<MemoryStorage, MockApi, MockQuerier>;
#[rstest::fixture]
fn deps() -> TestDeps {
instantiate_test_contract()
}
#[rstest]
fn save_works(mut deps: TestDeps) {
assert!(!has_service(&deps.storage, 1));
save(deps.as_mut().storage, &service_fixture()).unwrap();
save(deps.as_mut().storage, &service_fixture(1)).unwrap();
assert!(has_service(&deps.storage, 1));
}
#[test]
fn save_and_check_incorrect_id_fails() {
let mut deps = instantiate_test_contract();
#[rstest]
fn save_and_check_incorrect_id_fails(mut deps: TestDeps) {
assert!(!has_service(&deps.storage, 2));
save(deps.as_mut().storage, &service_fixture()).unwrap();
save(deps.as_mut().storage, &service_fixture(1)).unwrap();
assert!(!has_service(&deps.storage, 2));
}
#[test]
fn remove_works() {
let mut deps = instantiate_test_contract();
let id = save(deps.as_mut().storage, &service_fixture()).unwrap();
#[rstest]
fn remove_works(mut deps: TestDeps) {
let id = 1;
save(deps.as_mut().storage, &service_fixture(id)).unwrap();
assert!(has_service(&deps.storage, id));
remove(deps.as_mut().storage, id).unwrap();
assert!(!has_service(&deps.storage, id));
}
#[test]
fn load_by_id_works() {
let mut deps = instantiate_test_contract();
let id = save(deps.as_mut().storage, &service_fixture()).unwrap();
#[rstest]
fn load_by_id_works(mut deps: TestDeps) {
let id = 1;
save(deps.as_mut().storage, &service_fixture(id)).unwrap();
let service = load_id(deps.as_ref().storage, id).unwrap();
assert_eq!(service, service_fixture());
assert_eq!(service, service_fixture(id));
}
#[test]
fn load_by_wrong_id_returns_not_found() {
let mut deps = instantiate_test_contract();
let id = save(deps.as_mut().storage, &service_fixture()).unwrap();
#[rstest]
fn load_by_wrong_id_returns_not_found(mut deps: TestDeps) {
let id = 1;
save(deps.as_mut().storage, &service_fixture(id)).unwrap();
assert_eq!(
load_id(deps.as_ref().storage, id + 1).unwrap_err(),
ContractError::NotFound { service_id: id + 1 }
SpContractError::NotFound { service_id: id + 1 }
);
}
#[test]
fn load_by_announcer_works() {
let mut deps = instantiate_test_contract();
save(deps.as_mut().storage, &service_fixture_with_address("a")).unwrap();
save(deps.as_mut().storage, &service_fixture_with_address("b")).unwrap();
save(deps.as_mut().storage, &service_fixture_with_address("c")).unwrap();
#[rstest]
fn load_by_announcer_works(mut deps: TestDeps) {
save(deps.as_mut().storage, &service_fixture_with_address(1, "a")).unwrap();
save(deps.as_mut().storage, &service_fixture_with_address(2, "b")).unwrap();
save(deps.as_mut().storage, &service_fixture_with_address(3, "c")).unwrap();
assert_eq!(
load_announcer(&deps.storage, Addr::unchecked("steve")).unwrap(),
vec![
(1, service_fixture_with_address("a")),
(2, service_fixture_with_address("b")),
(3, service_fixture_with_address("c")),
service_fixture_with_address(1, "a"),
service_fixture_with_address(2, "b"),
service_fixture_with_address(3, "c"),
]
);
}
#[test]
fn load_by_wrong_announcer_returns_empty() {
let mut deps = instantiate_test_contract();
save(deps.as_mut().storage, &service_fixture_with_address("a")).unwrap();
save(deps.as_mut().storage, &service_fixture_with_address("b")).unwrap();
save(deps.as_mut().storage, &service_fixture_with_address("c")).unwrap();
#[rstest]
fn load_by_wrong_announcer_returns_empty(mut deps: TestDeps) {
save(deps.as_mut().storage, &service_fixture_with_address(1, "a")).unwrap();
save(deps.as_mut().storage, &service_fixture_with_address(2, "b")).unwrap();
save(deps.as_mut().storage, &service_fixture_with_address(3, "c")).unwrap();
assert_eq!(
load_announcer(&deps.storage, Addr::unchecked("timmy")).unwrap(),
vec![]
);
}
#[test]
fn load_by_nym_address_works() {
let mut deps = instantiate_test_contract();
save(deps.as_mut().storage, &service_fixture_with_address("a")).unwrap();
save(deps.as_mut().storage, &service_fixture_with_address("b")).unwrap();
save(deps.as_mut().storage, &service_fixture_with_address("c")).unwrap();
#[rstest]
fn load_by_nym_address_works(mut deps: TestDeps) {
save(deps.as_mut().storage, &service_fixture_with_address(1, "a")).unwrap();
save(deps.as_mut().storage, &service_fixture_with_address(2, "b")).unwrap();
save(deps.as_mut().storage, &service_fixture_with_address(3, "c")).unwrap();
assert_eq!(
load_nym_address(&deps.storage, NymAddress::new("b")).unwrap(),
vec![(2, service_fixture_with_address("b"))]
vec![service_fixture_with_address(2, "b")]
);
}
#[test]
fn load_by_wrong_nym_address_returns_empty() {
let mut deps = instantiate_test_contract();
save(deps.as_mut().storage, &service_fixture_with_address("a")).unwrap();
save(deps.as_mut().storage, &service_fixture_with_address("b")).unwrap();
save(deps.as_mut().storage, &service_fixture_with_address("c")).unwrap();
#[rstest]
fn load_by_wrong_nym_address_returns_empty(mut deps: TestDeps) {
save(deps.as_mut().storage, &service_fixture_with_address(1, "a")).unwrap();
save(deps.as_mut().storage, &service_fixture_with_address(2, "b")).unwrap();
save(deps.as_mut().storage, &service_fixture_with_address(3, "c")).unwrap();
assert_eq!(
load_nym_address(&deps.storage, NymAddress::new("d")).unwrap(),
vec![]
);
}
#[test]
fn load_all_paged_with_no_limit_works() {
let mut deps = instantiate_test_contract();
save(deps.as_mut().storage, &service_fixture_with_address("a")).unwrap();
save(deps.as_mut().storage, &service_fixture_with_address("b")).unwrap();
#[rstest]
fn load_all_paged_with_no_limit_works(mut deps: TestDeps) {
save(deps.as_mut().storage, &service_fixture_with_address(1, "a")).unwrap();
save(deps.as_mut().storage, &service_fixture_with_address(2, "b")).unwrap();
assert_eq!(
load_all_paged(&deps.storage, None, None).unwrap(),
PagedLoad {
services: vec![
(1, service_fixture_with_address("a")),
(2, service_fixture_with_address("b"))
service_fixture_with_address(1, "a"),
service_fixture_with_address(2, "b")
],
start_next_after: Some(2),
limit: 100,
@@ -242,20 +248,19 @@ mod tests {
);
}
#[test]
fn load_all_paged_with_limit_works() {
let mut deps = instantiate_test_contract();
save(deps.as_mut().storage, &service_fixture_with_address("a")).unwrap();
save(deps.as_mut().storage, &service_fixture_with_address("b")).unwrap();
save(deps.as_mut().storage, &service_fixture_with_address("c")).unwrap();
save(deps.as_mut().storage, &service_fixture_with_address("d")).unwrap();
save(deps.as_mut().storage, &service_fixture_with_address("e")).unwrap();
#[rstest]
fn load_all_paged_with_limit_works(mut deps: TestDeps) {
save(deps.as_mut().storage, &service_fixture_with_address(1, "a")).unwrap();
save(deps.as_mut().storage, &service_fixture_with_address(2, "b")).unwrap();
save(deps.as_mut().storage, &service_fixture_with_address(3, "c")).unwrap();
save(deps.as_mut().storage, &service_fixture_with_address(4, "d")).unwrap();
save(deps.as_mut().storage, &service_fixture_with_address(5, "e")).unwrap();
assert_eq!(
load_all_paged(&deps.storage, Some(2), None).unwrap(),
PagedLoad {
services: vec![
(1, service_fixture_with_address("a")),
(2, service_fixture_with_address("b"))
service_fixture_with_address(1, "a"),
service_fixture_with_address(2, "b")
],
limit: 2,
start_next_after: Some(2),
@@ -265,8 +270,8 @@ mod tests {
load_all_paged(&deps.storage, Some(2), Some(2)).unwrap(),
PagedLoad {
services: vec![
(3, service_fixture_with_address("c")),
(4, service_fixture_with_address("d"))
service_fixture_with_address(3, "c"),
service_fixture_with_address(4, "d")
],
limit: 2,
start_next_after: Some(4),
@@ -275,7 +280,7 @@ mod tests {
assert_eq!(
load_all_paged(&deps.storage, Some(2), Some(4)).unwrap(),
PagedLoad {
services: vec![(5, service_fixture_with_address("e")),],
services: vec![service_fixture_with_address(5, "e")],
start_next_after: Some(5),
limit: 2,
}
@@ -1,11 +1,12 @@
use cosmwasm_std::{from_binary, testing::mock_env, Addr, Coin, Deps};
use nym_contracts_common::signing::Nonce;
use nym_service_provider_directory_common::{
msg::QueryMsg,
response::{ConfigResponse, PagedServicesListResponse},
ServiceId, ServiceInfo,
Service, ServiceId,
};
use crate::{constants::SERVICE_DEFAULT_RETRIEVAL_LIMIT, error::ContractError};
use crate::{constants::SERVICE_DEFAULT_RETRIEVAL_LIMIT, SpContractError};
pub fn assert_config(deps: Deps, admin: &Addr, deposit_required: Coin) {
crate::state::assert_admin(deps, admin).unwrap();
@@ -14,7 +15,7 @@ pub fn assert_config(deps: Deps, admin: &Addr, deposit_required: Coin) {
assert_eq!(config, ConfigResponse { deposit_required });
}
pub fn assert_services(deps: Deps, expected_services: &[ServiceInfo]) {
pub fn assert_services(deps: Deps, expected_services: &[Service]) {
let res = crate::contract::query(deps, mock_env(), QueryMsg::all()).unwrap();
let services: PagedServicesListResponse = from_binary(&res).unwrap();
let start_next_after = expected_services.iter().last().map(|s| s.service_id);
@@ -28,7 +29,7 @@ pub fn assert_services(deps: Deps, expected_services: &[ServiceInfo]) {
);
}
pub fn assert_service(deps: Deps, expected_service: &ServiceInfo) {
pub fn assert_service(deps: Deps, expected_service: &Service) {
let res = crate::contract::query(
deps,
mock_env(),
@@ -37,7 +38,7 @@ pub fn assert_service(deps: Deps, expected_service: &ServiceInfo) {
},
)
.unwrap();
let services: ServiceInfo = from_binary(&res).unwrap();
let services: Service = from_binary(&res).unwrap();
assert_eq!(&services, expected_service);
}
@@ -58,8 +59,21 @@ pub fn assert_not_found(deps: Deps, expected_id: ServiceId) {
.unwrap_err();
assert!(matches!(
res,
ContractError::NotFound {
SpContractError::NotFound {
service_id: _expected_id
}
));
}
pub fn assert_current_nonce(deps: Deps, address: &Addr, expected_nonce: Nonce) {
let res = crate::contract::query(
deps,
mock_env(),
QueryMsg::SigningNonce {
address: address.to_string(),
},
)
.unwrap();
let nonce: Nonce = from_binary(&res).unwrap();
assert_eq!(nonce, expected_nonce);
}
@@ -1,43 +1,87 @@
use cosmwasm_std::Addr;
use cosmwasm_std::{Addr, Coin, DepsMut};
use nym_contracts_common::{signing::MessageSignature, IdentityKeyRef};
use nym_crypto::asymmetric::identity;
use nym_service_provider_directory_common::{
NymAddress, Service, ServiceId, ServiceInfo, ServiceType,
NymAddress, Service, ServiceDetails, ServiceId, ServiceType,
};
use rand_chacha::rand_core::{CryptoRng, RngCore};
use super::{
helpers::nyms,
signing::{ed25519_sign_message, service_provider_announce_sign_payload},
};
use super::helpers::nyms;
pub fn service_fixture() -> Service {
Service {
nym_address: NymAddress::new("nym"),
service_type: ServiceType::NetworkRequester,
announcer: Addr::unchecked("steve"),
block_height: 12345,
deposit: nyms(100),
}
}
pub fn service_fixture_with_address(nym_address: &str) -> Service {
Service {
nym_address: NymAddress::new(nym_address),
service_type: ServiceType::NetworkRequester,
announcer: Addr::unchecked("steve"),
block_height: 12345,
deposit: nyms(100),
}
}
pub fn service_info(
pub fn new_service(
service_id: ServiceId,
nym_address: NymAddress,
announcer: Addr,
) -> ServiceInfo {
ServiceInfo {
nym_address: &NymAddress,
announcer: &Addr,
identity_key: IdentityKeyRef,
) -> Service {
Service {
service_id,
service: Service {
nym_address,
service: ServiceDetails {
nym_address: nym_address.clone(),
service_type: ServiceType::NetworkRequester,
announcer,
block_height: 12345,
deposit: nyms(100),
identity_key: identity_key.to_string(),
},
announcer: announcer.clone(),
block_height: 12345,
deposit: nyms(100),
}
}
pub fn service_fixture(service_id: ServiceId) -> Service {
new_service(
service_id,
&NymAddress::new("nym"),
&Addr::unchecked("steve"),
"identity",
)
}
pub fn service_fixture_with_address(service_id: ServiceId, nym_address: &str) -> Service {
new_service(
service_id,
&NymAddress::new(nym_address),
&Addr::unchecked("steve"),
"identity",
)
}
// Create a new service, using a correctly generted identity key
pub fn new_service_details<R>(rng: &mut R, nym_address: &str) -> (ServiceDetails, identity::KeyPair)
where
R: RngCore + CryptoRng,
{
let keypair = identity::KeyPair::new(rng);
(
ServiceDetails {
nym_address: NymAddress::new(nym_address),
service_type: ServiceType::NetworkRequester,
identity_key: keypair.public_key().to_base58_string(),
},
keypair,
)
}
// Create a new service, with a correctly generated identity key, and sign it
pub fn new_service_details_with_sign<R>(
deps: DepsMut<'_>,
rng: &mut R,
nym_address: &str,
announcer: &str,
deposit: Coin,
) -> (ServiceDetails, MessageSignature)
where
R: RngCore + CryptoRng,
{
// Service
let (service, keypair) = new_service_details(rng, nym_address);
// Sign
let sign_msg =
service_provider_announce_sign_payload(deps.as_ref(), announcer, service.clone(), deposit);
let owner_signature = ed25519_sign_message(sign_msg, keypair.private_key());
(service, owner_signature)
}
@@ -1,19 +1,16 @@
use cosmwasm_std::{
coin, coins,
testing::{mock_dependencies, mock_env, mock_info, MockApi, MockQuerier},
Coin, DepsMut, Event, MemoryStorage, OwnedDeps, Response,
};
use cosmwasm_std::{Coin, Event, Response};
use cw_multi_test::AppResponse;
use nym_service_provider_directory_common::{
events::{ServiceProviderEventType, SERVICE_ID},
msg::{ExecuteMsg, InstantiateMsg},
Service, ServiceId,
};
use rand_chacha::{rand_core::SeedableRng, ChaCha20Rng};
pub fn nyms(amount: u64) -> Coin {
Coin::new(amount.into(), "unym")
}
pub fn test_rng() -> ChaCha20Rng {
let dummy_seed = [42u8; 32];
ChaCha20Rng::from_seed(dummy_seed)
}
pub fn get_event_types(response: &Response, event_type: &str) -> Vec<Event> {
response
.events
@@ -55,50 +52,3 @@ pub fn get_app_attribute(response: &AppResponse, event_type: &str, key: &str) ->
.value
.clone()
}
#[allow(dead_code)]
pub fn get_app_attributes(response: &AppResponse, event_type: &str, key: &str) -> Vec<String> {
get_app_event_types(response, event_type)
.iter()
.map(|ev| {
ev.attributes
.iter()
.find(|attr| attr.key == key)
.unwrap()
.value
.clone()
})
.collect::<Vec<_>>()
}
pub fn instantiate_test_contract() -> OwnedDeps<MemoryStorage, MockApi, MockQuerier> {
let mut deps = mock_dependencies();
let msg = InstantiateMsg {
deposit_required: coin(100, "unym"),
};
let env = mock_env();
let info = mock_info("creator", &[]);
let res = crate::instantiate(deps.as_mut(), env, info, msg).unwrap();
assert_eq!(res.messages.len(), 0);
deps
}
pub fn announce_service(deps: DepsMut<'_>, service: &Service) -> ServiceId {
let msg: ExecuteMsg = service.clone().into();
let info = mock_info(service.announcer.as_str(), &coins(100, "unym"));
let res = crate::execute(deps, mock_env(), info, msg).unwrap();
let service_id: ServiceId = get_attribute(
&res,
&ServiceProviderEventType::Announce.to_string(),
SERVICE_ID,
)
.parse()
.unwrap();
service_id
}
pub fn delete_service(deps: DepsMut<'_>, service_id: ServiceId, announcer: &str) {
let msg = ExecuteMsg::DeleteId { service_id };
let info = mock_info(announcer, &[]);
crate::execute(deps, mock_env(), info, msg).unwrap();
}
@@ -1,4 +1,5 @@
pub mod assert;
pub mod fixture;
pub mod helpers;
pub mod test_setup;
pub mod signing;
pub mod transactions;
@@ -0,0 +1,41 @@
use cosmwasm_std::{Addr, Coin, Deps};
use nym_contracts_common::signing::{
MessageSignature, SignableMessage, SigningAlgorithm, SigningPurpose,
};
use nym_crypto::asymmetric::identity;
use nym_service_provider_directory_common::{
signing_types::{
construct_service_provider_announce_sign_payload, SignableServiceProviderAnnounceMsg,
},
ServiceDetails,
};
use serde::Serialize;
use crate::state;
pub fn service_provider_announce_sign_payload(
deps: Deps<'_>,
owner: &str,
service: ServiceDetails,
deposit: Coin,
) -> SignableServiceProviderAnnounceMsg {
let owner = Addr::unchecked(owner);
let nonce = state::get_signing_nonce(deps.storage, owner.clone()).unwrap();
construct_service_provider_announce_sign_payload(nonce, owner, deposit, service)
}
pub fn ed25519_sign_message<T: Serialize + SigningPurpose>(
message: SignableMessage<T>,
private_key: &identity::PrivateKey,
) -> MessageSignature {
match message.algorithm {
SigningAlgorithm::Ed25519 => {
let plaintext = message.to_plaintext().unwrap();
let signature = private_key.sign(&plaintext);
MessageSignature::from(signature.to_bytes().as_ref())
}
SigningAlgorithm::Secp256k1 => {
unimplemented!()
}
}
}
@@ -0,0 +1,69 @@
use cosmwasm_std::{
coin,
testing::{mock_dependencies, mock_env, mock_info, MockApi, MockQuerier},
DepsMut, MemoryStorage, OwnedDeps,
};
use nym_service_provider_directory_common::{
events::{ServiceProviderEventType, SERVICE_ID},
msg::{ExecuteMsg, InstantiateMsg},
ServiceDetails, ServiceId,
};
use rand_chacha::rand_core::{CryptoRng, RngCore};
use super::helpers::{get_attribute, nyms};
pub fn instantiate_test_contract() -> OwnedDeps<MemoryStorage, MockApi, MockQuerier> {
let mut deps = mock_dependencies();
let msg = InstantiateMsg {
deposit_required: coin(100, "unym"),
};
let env = mock_env();
let info = mock_info("creator", &[]);
let res = crate::instantiate(deps.as_mut(), env, info, msg).unwrap();
assert_eq!(res.messages.len(), 0);
deps
}
pub fn announce_service<R>(
mut deps: DepsMut<'_>,
rng: &mut R,
nym_address: &str,
announcer: &str,
) -> (ServiceId, ServiceDetails)
where
R: RngCore + CryptoRng,
{
let deposit = nyms(100);
let (service, owner_signature) = super::fixture::new_service_details_with_sign(
deps.branch(),
rng,
nym_address,
announcer,
deposit.clone(),
);
// Announce
let msg = ExecuteMsg::Announce {
service: service.clone(),
owner_signature,
};
let info = mock_info(announcer, &[deposit]);
let res = crate::execute(deps, mock_env(), info, msg).unwrap();
let service_id: ServiceId = get_attribute(
&res,
&ServiceProviderEventType::Announce.to_string(),
SERVICE_ID,
)
.parse()
.unwrap();
(service_id, service)
}
pub fn delete_service(deps: DepsMut<'_>, service_id: ServiceId, announcer: &str) {
let msg = ExecuteMsg::DeleteId { service_id };
let info = mock_info(announcer, &[]);
crate::execute(deps, mock_env(), info, msg).unwrap();
}
+1 -1
View File
@@ -17,7 +17,7 @@ GROUP_CONTRACT_ADDRESS=n1fqquzw4mk0pkamgr2ywt2v7h2j9nuyjjn4gvpy8zlpp6xn0uyuzqfm2
MULTISIG_CONTRACT_ADDRESS=n1gaq3666chd5348apj8cka8t2mckv7azp9espyr7wgpxyuzur5d0sazpysy
COCONUT_DKG_CONTRACT_ADDRESS=n18yadscxw8v35dds7ksv3j0svmjh3h6e7tmxpadk96mvgz27zygkshuf4vs
REWARDING_VALIDATOR_ADDRESS=n10yyd98e2tuwu0f7ypz9dy3hhjw7v772q6287gy
SERVICE_PROVIDER_DIRECTORY_CONTRACT_ADDRESS=n1ryt076cufyddallg5x0gz3qjz0pd3wg0m4cwkg9njhmlnp6u88qq6nczgj
SERVICE_PROVIDER_DIRECTORY_CONTRACT_ADDRESS=n1qsn2655eflc0nx2uwqtwyv5kad5dwm4c0gn72yr4q4de5r3jaz2slvqjgt
NAME_SERVICE_CONTRACT_ADDRESS=n1cm2u5vfjd3zalfw0p65xyh4tcrw3hjlm0960gzhewga449h4mgas77mjkl
STATISTICS_SERVICE_DOMAIN_ADDRESS="https://mainnet-stats.nymte.ch:8090"
NYXD="https://qwerty-validator.qa.nymte.ch/"
+11 -24
View File
@@ -17,6 +17,7 @@ rust-version = "1.56"
[dependencies]
anyhow = "1.0.53"
async-trait = { workspace = true }
atty = "0.2"
bip39 = { workspace = true }
bs58 = "0.4.0"
clap = { version = "4.0", features = ["cargo", "derive"] }
@@ -32,20 +33,11 @@ once_cell = "1.7.2"
pretty_env_logger = "0.4"
rand = "0.7"
serde = { workspace = true, features = ["derive"] }
sqlx = { version = "0.5", features = [
"runtime-tokio-rustls",
"sqlite",
"macros",
"migrate",
] }
serde_json = { workspace = true }
sqlx = { version = "0.5", features = [ "runtime-tokio-rustls", "sqlite", "macros", "migrate", ] }
subtle-encoding = { version = "0.5", features = ["bech32-preview"] }
thiserror = "1"
tokio = { version = "1.24.1", features = [
"rt-multi-thread",
"net",
"signal",
"fs",
] }
tokio = { version = "1.24.1", features = [ "rt-multi-thread", "net", "signal", "fs", ] }
tokio-stream = { version = "0.1.11", features = ["fs"] }
tokio-tungstenite = "0.14"
tokio-util = { version = "0.7.4", features = ["codec"] }
@@ -53,27 +45,22 @@ url = { version = "2.2", features = ["serde"] }
zeroize = { workspace = true }
# internal
nym-coconut-interface = { path = "../common/coconut-interface" }
nym-credentials = { path = "../common/credentials" }
nym-config = { path = "../common/config" }
nym-crypto = { path = "../common/crypto" }
nym-api-requests = { path = "../nym-api/nym-api-requests" }
nym-bin-common = { path = "../common/bin-common", features = ["output_format"] }
nym-coconut-interface = { path = "../common/coconut-interface" }
nym-config = { path = "../common/config" }
nym-credentials = { path = "../common/credentials" }
nym-crypto = { path = "../common/crypto" }
nym-gateway-requests = { path = "gateway-requests" }
nym-mixnet-client = { path = "../common/client-libs/mixnet-client" }
nym-mixnode-common = { path = "../common/mixnode-common" }
nym-network-defaults = { path = "../common/network-defaults" }
nym-sphinx = { path = "../common/nymsphinx" }
nym-pemstore = { path = "../common/pemstore" }
nym-sphinx = { path = "../common/nymsphinx" }
nym-statistics-common = { path = "../common/statistics" }
nym-api-requests = { path = "../nym-api/nym-api-requests" }
nym-task = { path = "../common/task" }
nym-validator-client = { path = "../common/client-libs/validator-client", features = [
"nyxd-client",
] }
nym-types = { path = "../common/types" }
serde_json = { workspace = true }
atty = "0.2"
nym-validator-client = { path = "../common/client-libs/validator-client", features = [ "nyxd-client" ] }
[build-dependencies]
tokio = { version = "1.24.1", features = ["rt-multi-thread", "macros"] }
+2 -2
View File
@@ -7,7 +7,7 @@ use nym_mixnet_contract_common::{
RewardingParams,
};
use nym_name_service_common::NameEntry;
use nym_service_provider_directory_common::ServiceInfo;
use nym_service_provider_directory_common::Service;
use std::collections::HashSet;
pub(crate) struct ValidatorCacheData {
@@ -25,7 +25,7 @@ pub(crate) struct ValidatorCacheData {
pub(crate) mix_to_family: Cache<Vec<(IdentityKey, FamilyHead)>>,
pub(crate) service_providers: Cache<Vec<ServiceInfo>>,
pub(crate) service_providers: Cache<Vec<Service>>,
pub(crate) registered_names: Cache<Vec<NameEntry>>,
}
+3 -3
View File
@@ -6,7 +6,7 @@ use nym_mixnet_contract_common::{
RewardingParams,
};
use nym_name_service_common::NameEntry;
use nym_service_provider_directory_common::ServiceInfo;
use nym_service_provider_directory_common::Service;
use rocket::fairing::AdHoc;
use std::{
collections::HashSet,
@@ -52,7 +52,7 @@ impl NymContractCache {
rewarding_params: RewardingParams,
current_interval: Interval,
mix_to_family: Vec<(IdentityKey, FamilyHead)>,
services: Option<Vec<ServiceInfo>>,
services: Option<Vec<Service>>,
names: Option<Vec<NameEntry>>,
) {
match time::timeout(Duration::from_millis(100), self.inner.write()).await {
@@ -293,7 +293,7 @@ impl NymContractCache {
self.mixnode_details(mix_id).await.1
}
pub(crate) async fn services(&self) -> Cache<Vec<ServiceInfo>> {
pub(crate) async fn services(&self) -> Cache<Vec<Service>> {
match time::timeout(Duration::from_millis(100), self.inner.read()).await {
Ok(cache) => cache.service_providers.clone(),
Err(err) => {
+18
View File
@@ -1053,6 +1053,20 @@ dependencies = [
"zeroize",
]
[[package]]
name = "cw-controllers"
version = "0.13.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4f0bc6019b4d3d81e11f5c384bcce7173e2210bd654d75c6c9668e12cca05dfa"
dependencies = [
"cosmwasm-std",
"cw-storage-plus",
"cw-utils",
"schemars",
"serde",
"thiserror",
]
[[package]]
name = "cw-storage-plus"
version = "0.13.4"
@@ -3598,8 +3612,12 @@ name = "nym-service-provider-directory-common"
version = "0.1.0"
dependencies = [
"cosmwasm-std",
"cw-controllers",
"cw-utils",
"nym-contracts-common",
"schemars",
"serde",
"thiserror",
]
[[package]]
@@ -42,7 +42,7 @@ pub async fn get_services() -> Result<Vec<DirectoryServiceProvider>> {
let mut filtered: Vec<DirectoryService> = vec![];
for service in &services_res {
let items: _ = service
let items = service
.items
.clone()
.into_iter()
+18
View File
@@ -934,6 +934,20 @@ dependencies = [
"zeroize",
]
[[package]]
name = "cw-controllers"
version = "0.13.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4f0bc6019b4d3d81e11f5c384bcce7173e2210bd654d75c6c9668e12cca05dfa"
dependencies = [
"cosmwasm-std",
"cw-storage-plus",
"cw-utils",
"schemars",
"serde",
"thiserror",
]
[[package]]
name = "cw-storage-plus"
version = "0.13.4"
@@ -3012,8 +3026,12 @@ name = "nym-service-provider-directory-common"
version = "0.1.0"
dependencies = [
"cosmwasm-std",
"cw-controllers",
"cw-utils",
"nym-contracts-common",
"schemars",
"serde",
"thiserror",
]
[[package]]
@@ -12,6 +12,7 @@ rust-version = "1.65"
[dependencies]
async-trait = { workspace = true }
bs58 = "0.4.0"
clap = {version = "4.0", features = ["cargo", "derive"]}
dirs = "4.0"
futures = "0.3.24"
@@ -24,6 +25,7 @@ publicsuffix = "1.5" # Can't update this until bip updates to support newer idna
rand = "0.7.3"
reqwest = { version = "0.11.11", features = ["json"] }
serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true }
sqlx = { version = "0.6.1", features = ["runtime-tokio-rustls", "chrono"]}
tap = { workspace = true }
thiserror = "1.0"
@@ -47,6 +49,7 @@ nym-service-providers-common = { path = "../common" }
nym-socks5-requests = { path = "../../common/socks5/requests" }
nym-statistics-common = { path = "../../common/statistics" }
nym-task = { path = "../../common/task" }
nym-types = { path = "../../common/types" }
nym-client-websocket-requests = { path = "../../clients/native/websocket-requests" }
[dev-dependencies]
@@ -5,6 +5,7 @@ use clap::{CommandFactory, Parser, Subcommand};
use log::info;
use nym_bin_common::build_information::BinaryBuildInformation;
use nym_bin_common::completions::{fig_generate, ArgShell};
use nym_bin_common::version_checker;
use nym_config::NymConfig;
use crate::config::old_config_v1_1_13::OldConfigV1_1_13;
@@ -15,6 +16,7 @@ use crate::{
mod init;
mod run;
mod sign;
lazy_static::lazy_static! {
pub static ref PRETTY_BUILD_INFORMATION: String =
@@ -47,6 +49,9 @@ pub(crate) enum Commands {
/// parameters.
Run(run::Run),
/// Sign to prove ownership of this network requester
Sign(sign::Sign),
/// Generate shell completions
Completions(ArgShell),
@@ -91,6 +96,7 @@ pub(crate) async fn execute(args: Cli) -> Result<(), NetworkRequesterError> {
match &args.command {
Commands::Init(m) => init::execute(m).await?,
Commands::Run(m) => run::execute(m).await?,
Commands::Sign(m) => sign::execute(m).await?,
Commands::Completions(s) => s.generate(&mut Cli::command(), bin_name),
Commands::GenerateFigSpec => fig_generate(&mut Cli::command(), bin_name),
}
@@ -111,6 +117,36 @@ fn try_upgrade_v1_1_13_config(id: &str) -> std::io::Result<()> {
updated.save_to_file(None)
}
// this only checks compatibility between config the binary. It does not take into consideration
// network version. It might do so in the future.
fn version_check(cfg: &Config) -> bool {
let binary_version = env!("CARGO_PKG_VERSION");
let config_version = cfg.get_base().get_version();
if binary_version == config_version {
true
} else {
log::warn!(
"The native-client binary has different version than what is specified \
in config file! {} and {}",
binary_version,
config_version
);
if version_checker::is_minor_version_compatible(binary_version, config_version) {
log::info!(
"but they are still semver compatible. \
However, consider running the `upgrade` command"
);
true
} else {
log::error!(
"and they are semver incompatible! - \
please run the `upgrade` command before attempting `run` again"
);
false
}
}
}
#[cfg(test)]
mod tests {
use super::*;
@@ -1,14 +1,13 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::cli::try_upgrade_v1_1_13_config;
use crate::cli::{try_upgrade_v1_1_13_config, version_check};
use crate::{
cli::{override_config, OverrideConfig},
config::Config,
error::NetworkRequesterError,
};
use clap::Args;
use nym_bin_common::version_checker;
use nym_config::NymConfig;
use nym_sphinx::addressing::clients::Recipient;
@@ -61,36 +60,6 @@ impl From<Run> for OverrideConfig {
}
}
// this only checks compatibility between config the binary. It does not take into consideration
// network version. It might do so in the future.
fn version_check(cfg: &Config) -> bool {
let binary_version = env!("CARGO_PKG_VERSION");
let config_version = cfg.get_base().get_version();
if binary_version == config_version {
true
} else {
log::warn!(
"The native-client binary has different version than what is specified \
in config file! {} and {}",
binary_version,
config_version
);
if version_checker::is_minor_version_compatible(binary_version, config_version) {
log::info!(
"but they are still semver compatible. \
However, consider running the `upgrade` command"
);
true
} else {
log::error!(
"and they are semver incompatible! - \
please run the `upgrade` command before attempting `run` again"
);
false
}
}
}
pub(crate) async fn execute(args: &Run) -> Result<(), NetworkRequesterError> {
if args.open_proxy {
println!(
@@ -145,7 +114,7 @@ pub(crate) async fn execute(args: &Run) -> Result<(), NetworkRequesterError> {
return Err(NetworkRequesterError::FailedLocalVersionCheck);
}
// TODO: consider incorporating statistics_recipient, open_proxuy and enable_statistics in
// TODO: consider incorporating statistics_recipient, open_proxy and enable_statistics in
// `Config`.
let stats_provider_addr = args
@@ -0,0 +1,96 @@
use clap::Args;
use nym_bin_common::output_format::OutputFormat;
use nym_client_core::config::persistence::key_pathfinder::ClientKeyPathfinder;
use nym_config::NymConfig;
use nym_crypto::asymmetric::identity;
use nym_types::helpers::ConsoleSigningOutput;
use crate::{config::Config, error::NetworkRequesterError};
use super::version_check;
#[derive(Args, Clone)]
pub(crate) struct Sign {
/// The id of the mixnode you want to sign with
#[clap(long)]
id: String,
/// Signs a transaction-specific payload, that is going to be sent to the smart contract, with your identity key
#[clap(long)]
contract_msg: String,
#[clap(short, long, default_value_t = OutputFormat::default())]
output: OutputFormat,
}
fn print_signed_contract_msg(
private_key: &identity::PrivateKey,
raw_msg: &str,
output: OutputFormat,
) {
let trimmed = raw_msg.trim();
eprintln!(">>> attempting to sign {trimmed}");
let Ok(decoded) = bs58::decode(trimmed).into_vec() else {
println!("it seems you have incorrectly copied the message to sign. Make sure you didn't accidentally skip any characters");
return;
};
eprintln!(">>> decoding the message...");
// we don't really care about what particular information is embedded inside of it,
// we just want to know if user correctly copied the string, i.e. whether it's a valid bs58 encoded json
if serde_json::from_slice::<serde_json::Value>(&decoded).is_err() {
println!("it seems you have incorrectly copied the message to sign. Make sure you didn't accidentally skip any characters");
return;
};
// if this is a valid json, it MUST be a valid string
let decoded_string = String::from_utf8(decoded.clone()).unwrap();
let signature = private_key.sign(&decoded).to_base58_string();
let sign_output = ConsoleSigningOutput::new(decoded_string, signature);
println!("{}", output.format(&sign_output));
}
pub(crate) async fn execute(args: &Sign) -> Result<(), NetworkRequesterError> {
let id = &args.id;
let mut config = match Config::load_from_file(id) {
Ok(cfg) => cfg,
Err(err) => {
log::error!(
"Failed to load config for {}. Are you sure you have run `init` before? (Error was: {err})",
id,
);
return Err(NetworkRequesterError::FailedToLoadConfig(id.to_string()));
}
};
if !config.validate() {
return Err(NetworkRequesterError::ConfigValidationFailure);
}
if config.get_base_mut().set_empty_fields_to_defaults() {
log::warn!(
"Some of the core config options were left unset. \
The default values are going to get used instead."
);
}
if !version_check(&config) {
log::error!("Failed the local version check");
return Err(NetworkRequesterError::FailedLocalVersionCheck);
}
let pathfinder = ClientKeyPathfinder::new_from_config(config.get_base());
let identity_keypair = nym_client_core::init::load_identity_keys(&pathfinder)?;
print_signed_contract_msg(
identity_keypair.private_key(),
&args.contract_msg,
args.output,
);
Ok(())
}
+1
View File
@@ -54,6 +54,7 @@ pub(crate) struct Cli {
command: Commands,
}
#[allow(clippy::large_enum_variant)]
#[derive(Debug, Subcommand)]
pub(crate) enum Commands {
/// Query and manage Nyx blockchain accounts
@@ -9,6 +9,7 @@ pub(crate) async fn execute(
match service.command {
nym_cli_commands::validator::mixnet::operators::service::MixnetOperatorsServiceCommands::Announce(announce) => nym_cli_commands::validator::mixnet::operators::service::announce::announce(announce, create_signing_client(global_args, network_details)?).await,
nym_cli_commands::validator::mixnet::operators::service::MixnetOperatorsServiceCommands::Delete(delete) => nym_cli_commands::validator::mixnet::operators::service::delete::delete(delete, create_signing_client(global_args, network_details)?).await,
nym_cli_commands::validator::mixnet::operators::service::MixnetOperatorsServiceCommands::CreateServiceAnnounceSignPayload(args) => nym_cli_commands::validator::mixnet::operators::service::announce_sign_payload::create_payload(args, create_signing_client(global_args, network_details)?).await,
}
Ok(())
}