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:
Generated
+22
@@ -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
@@ -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" }
|
||||
|
||||
@@ -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],
|
||||
)
|
||||
|
||||
@@ -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 }
|
||||
|
||||
+20
-3
@@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Generated
+8
@@ -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]]
|
||||
|
||||
@@ -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")]
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
+102
-31
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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) => {
|
||||
|
||||
Generated
+18
@@ -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()
|
||||
|
||||
Generated
+18
@@ -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(())
|
||||
}
|
||||
@@ -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(())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user