Initial version of nym-name-service contract (only) (#3380)

* Initial version of nym-name-service, based on nym-service-provider-directory

* rustfmt

* Rename to NameEntry

* Restrict address format

* Remove deprecated random test

* Fix clippy

* Add to top-level Makefile

* Restore wasm-opt Makefile rule

* Restore NymAddress as enum

* rustfmt

* Add contract address to qa-qwerty.env

* Rename NymAddress to Address

* Tweak event output

* rustfmt

* add event_tag()

* qwerty contract address
This commit is contained in:
Jon Häggblad
2023-05-04 15:57:41 +02:00
committed by GitHub
parent 5621e7d22e
commit b7cfe31d72
31 changed files with 2916 additions and 11 deletions
Generated
+8
View File
@@ -3730,6 +3730,14 @@ dependencies = [
"thiserror",
]
[[package]]
name = "nym-name-service-common"
version = "0.1.0"
dependencies = [
"cosmwasm-std",
"serde",
]
[[package]]
name = "nym-network-defaults"
version = "0.1.0"
+1
View File
@@ -37,6 +37,7 @@ members = [
"common/cosmwasm-smart-contracts/group-contract",
"common/cosmwasm-smart-contracts/mixnet-contract",
"common/cosmwasm-smart-contracts/multisig-contract",
"common/cosmwasm-smart-contracts/name-service",
"common/cosmwasm-smart-contracts/service-provider-directory",
"common/cosmwasm-smart-contracts/vesting-contract",
"common/credential-storage",
+2
View File
@@ -99,6 +99,7 @@ CONTRACTS_OUT_DIR=contracts/target/wasm32-unknown-unknown/release
VESTING_CONTRACT=$(CONTRACTS_OUT_DIR)/vesting_contract.wasm
MIXNET_CONTRACT=$(CONTRACTS_OUT_DIR)/mixnet_contract.wasm
SERVICE_PROVIDER_DIRECTORY_CONTRACT=$(CONTRACTS_OUT_DIR)/nym_service_provider_directory.wasm
NAME_SERVICE_CONTRACT=$(CONTRACTS_OUT_DIR)/nym_name_service.wasm
wasm: wasm-build wasm-opt
@@ -109,6 +110,7 @@ wasm-opt:
wasm-opt --disable-sign-ext -Os $(VESTING_CONTRACT) -o $(VESTING_CONTRACT)
wasm-opt --disable-sign-ext -Os $(MIXNET_CONTRACT) -o $(MIXNET_CONTRACT)
wasm-opt --disable-sign-ext -Os $(SERVICE_PROVIDER_DIRECTORY_CONTRACT) -o $(SERVICE_PROVIDER_DIRECTORY_CONTRACT)
wasm-opt --disable-sign-ext -Os $(NAME_SERVICE_CONTRACT) -o $(NAME_SERVICE_CONTRACT)
# -----------------------------------------------------------------------------
# Misc
@@ -0,0 +1,11 @@
[package]
name = "nym-name-service-common"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
cosmwasm-std = { workspace = true }
serde = { workspace = true, default-features = false, features = ["derive"] }
#nym-sphinx-addressing = { path = "../../nymsphinx/addressing" }
@@ -0,0 +1,66 @@
use cosmwasm_std::{Coin, Event};
use crate::{NameId, RegisteredName};
pub enum NameEventType {
Register,
DeleteId,
DeleteName,
UpdateDepositRequired,
}
impl std::fmt::Display for NameEventType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
NameEventType::Register => write!(f, "register"),
NameEventType::DeleteId => write!(f, "delete_id"),
NameEventType::DeleteName => write!(f, "delete_name"),
NameEventType::UpdateDepositRequired => write!(f, "update_deposit_required"),
}
}
}
impl From<NameEventType> for String {
fn from(event_type: NameEventType) -> Self {
event_type.to_string()
}
}
pub const ACTION: &str = "action";
pub const NAME_ID: &str = "name_id";
pub const NAME: &str = "name";
pub const OWNER: &str = "owner";
pub const DEPOSIT_REQUIRED: &str = "deposit_required";
pub fn new_register_event(name_id: NameId, name: RegisteredName) -> Event {
Event::new(NameEventType::Register)
.add_attribute(ACTION, NameEventType::Register)
.add_attribute(NAME_ID, name_id.to_string())
.add_attribute(NAME, name.name.to_string())
.add_attribute(name.address.event_tag(), name.address.to_string())
.add_attribute(OWNER, name.owner.to_string())
}
pub fn new_delete_id_event(name_id: NameId, name: RegisteredName) -> Event {
Event::new(NameEventType::DeleteId)
.add_attribute(ACTION, NameEventType::DeleteId)
.add_attribute(NAME_ID, name_id.to_string())
.add_attribute(NAME, name.name.to_string())
.add_attribute(name.address.event_tag(), name.address.to_string())
}
pub fn new_delete_name_event(name_id: NameId, name: RegisteredName) -> Event {
Event::new(NameEventType::DeleteId)
.add_attribute(ACTION, NameEventType::DeleteName)
.add_attribute(NAME_ID, name_id.to_string())
.add_attribute(NAME, name.name.to_string())
.add_attribute(name.address.event_tag(), name.address.to_string())
}
pub fn new_update_deposit_required_event(deposit_required: Coin) -> Event {
Event::new(NameEventType::UpdateDepositRequired)
.add_attribute(ACTION, NameEventType::UpdateDepositRequired)
.add_attribute(DEPOSIT_REQUIRED, deposit_required.to_string())
}
@@ -0,0 +1,7 @@
pub mod events;
pub mod msg;
pub mod response;
pub mod types;
// Re-export all types at the top-level
pub use types::*;
@@ -0,0 +1,74 @@
use crate::{Address, NameId, NymName};
use cosmwasm_std::Coin;
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
#[serde(rename_all = "snake_case")]
pub struct InstantiateMsg {
pub deposit_required: Coin,
}
impl InstantiateMsg {
pub fn new(deposit_required: Coin) -> Self {
Self { deposit_required }
}
}
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
#[serde(rename_all = "snake_case")]
pub struct MigrateMsg {}
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
#[serde(rename_all = "snake_case")]
pub enum ExecuteMsg {
/// Announcing a name pointing to a nym-address
Register { name: NymName, address: Address },
/// Delete a name entry by id
DeleteId { name_id: NameId },
/// Delete a name entry by name
DeleteName { name: NymName },
/// Change the deposit required for announcing a name
UpdateDepositRequired { deposit_required: Coin },
}
impl ExecuteMsg {
pub fn delete_id(name_id: NameId) -> Self {
ExecuteMsg::DeleteId { name_id }
}
}
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
#[serde(rename_all = "snake_case")]
pub enum QueryMsg {
/// Query the name by it's assigned id
NameId {
name_id: NameId,
},
// Query the names by the registrator
ByOwner {
owner: String,
},
ByName {
name: NymName,
},
ByAddress {
address: Address,
},
All {
limit: Option<u32>,
start_after: Option<NameId>,
},
Config {},
GetContractVersion {},
#[serde(rename = "get_cw2_contract_version")]
GetCW2ContractVersion {},
}
impl QueryMsg {
pub fn all() -> QueryMsg {
QueryMsg::All {
limit: None,
start_after: None,
}
}
}
@@ -0,0 +1,70 @@
use crate::{msg::ExecuteMsg, NameEntry, NameId, RegisteredName};
use cosmwasm_std::Coin;
use serde::{Deserialize, Serialize};
/// Like [`NameEntry`] but since it's a response type the name is an option depending on if
/// the name exists or not.
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
#[serde(rename_all = "snake_case")]
pub struct NameEntryResponse {
pub name_id: NameId,
pub name: Option<RegisteredName>,
}
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
#[serde(rename_all = "snake_case")]
pub struct NamesListResponse {
pub names: Vec<NameEntry>,
}
impl NamesListResponse {
pub fn new(names: Vec<(NameId, RegisteredName)>) -> NamesListResponse {
NamesListResponse {
names: names
.into_iter()
.map(|(name_id, name)| NameEntry::new(name_id, name))
.collect(),
}
}
}
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
#[serde(rename_all = "snake_case")]
pub struct PagedNamesListResponse {
pub names: Vec<NameEntry>,
pub per_page: usize,
pub start_next_after: Option<NameId>,
}
impl PagedNamesListResponse {
pub fn new(
names: Vec<(NameId, RegisteredName)>,
per_page: usize,
start_next_after: Option<NameId>,
) -> PagedNamesListResponse {
let names = names
.into_iter()
.map(|(name_id, name)| NameEntry::new(name_id, name))
.collect();
PagedNamesListResponse {
names,
per_page,
start_next_after,
}
}
}
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
#[serde(rename_all = "snake_case")]
pub struct ConfigResponse {
pub deposit_required: Coin,
}
impl From<RegisteredName> for ExecuteMsg {
fn from(name: RegisteredName) -> Self {
ExecuteMsg::Register {
name: name.name,
address: name.address,
}
}
}
@@ -0,0 +1,164 @@
use std::fmt::{Display, Formatter};
use cosmwasm_std::{Addr, Coin};
use serde::{Deserialize, Serialize};
/// The directory of services are indexed by [`ServiceId`].
pub type NameId = u32;
#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)]
pub struct RegisteredName {
/// The name pointing to the nym address
pub name: NymName,
/// The address of the service.
pub address: Address,
/// Service owner.
pub owner: Addr,
/// Block height at which the service was added.
pub block_height: u64,
/// The deposit used to announce the service.
pub deposit: Coin,
}
/// String representation of a nym address, which is of the form
/// client_id.client_enc@gateway_id.
/// NOTE: entirely unvalidated.
#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]
#[serde(rename_all = "snake_case")]
pub enum Address {
NymAddress(String),
// Possibly extension:
//Gateway(String)
}
impl Address {
/// Create a new nym address.
pub fn new(address: &str) -> Self {
Self::NymAddress(address.to_string())
}
pub fn as_str(&self) -> &str {
match self {
Address::NymAddress(address) => address,
}
}
pub fn event_tag(&self) -> &str {
match self {
Address::NymAddress(_) => "nym_address",
//Address::Gateway(_) => "gatway_address",
}
}
}
impl Display for Address {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.as_str())
}
}
/// Name stored and pointing a to a nym-address
#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]
#[serde(rename_all = "snake_case")]
pub struct NymName(String);
#[derive(Debug)]
pub enum NymNameError {
InvalidName,
}
fn is_valid_name_char(c: char) -> bool {
// Normal lowercase letters
(c.is_alphabetic() && c.is_lowercase())
// or numbers
|| c.is_numeric()
// special case hyphen or underscore
|| c == '-' || c == '_'
}
impl NymName {
pub fn new(name: &str) -> Result<NymName, NymNameError> {
// We are a bit restrictive in which names we allow, to start out with. Consider relaxing
// this in the future.
if !name.chars().all(is_valid_name_char) {
return Err(NymNameError::InvalidName);
}
Ok(Self(name.to_string()))
}
pub fn as_str(&self) -> &str {
&self.0
}
}
impl Display for NymName {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
/// [`RegisterdName`] together with the assigned [`NameId`].
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
#[serde(rename_all = "snake_case")]
pub struct NameEntry {
pub name_id: NameId,
pub name: RegisteredName,
}
impl NameEntry {
pub fn new(name_id: NameId, name: RegisteredName) -> Self {
Self { name_id, name }
}
}
#[cfg(test)]
mod tests {
use super::NymName;
#[test]
fn parse_nym_name() {
// Test some valid cases
assert!(NymName::new("foo").is_ok());
assert!(NymName::new("foo-bar").is_ok());
assert!(NymName::new("foo-bar-123").is_ok());
assert!(NymName::new("foo_bar").is_ok());
assert!(NymName::new("foo_bar_123").is_ok());
// And now test all some invalid ones
assert!(NymName::new("Foo").is_err());
assert!(NymName::new("foo bar").is_err());
assert!(NymName::new("foo!bar").is_err());
assert!(NymName::new("foo#bar").is_err());
assert!(NymName::new("foo$bar").is_err());
assert!(NymName::new("foo%bar").is_err());
assert!(NymName::new("foo&bar").is_err());
assert!(NymName::new("foo'bar").is_err());
assert!(NymName::new("foo(bar").is_err());
assert!(NymName::new("foo)bar").is_err());
assert!(NymName::new("foo*bar").is_err());
assert!(NymName::new("foo+bar").is_err());
assert!(NymName::new("foo,bar").is_err());
assert!(NymName::new("foo.bar").is_err());
assert!(NymName::new("foo.bar").is_err());
assert!(NymName::new("foo/bar").is_err());
assert!(NymName::new("foo/bar").is_err());
assert!(NymName::new("foo:bar").is_err());
assert!(NymName::new("foo;bar").is_err());
assert!(NymName::new("foo<bar").is_err());
assert!(NymName::new("foo=bar").is_err());
assert!(NymName::new("foo>bar").is_err());
assert!(NymName::new("foo?bar").is_err());
assert!(NymName::new("foo@bar").is_err());
assert!(NymName::new("fooBar").is_err());
assert!(NymName::new("foo[bar").is_err());
assert!(NymName::new("foo\"bar").is_err());
assert!(NymName::new("foo\\bar").is_err());
assert!(NymName::new("foo]bar").is_err());
assert!(NymName::new("foo^bar").is_err());
assert!(NymName::new("foo`bar").is_err());
assert!(NymName::new("foo{bar").is_err());
assert!(NymName::new("foo|bar").is_err());
assert!(NymName::new("foo}bar").is_err());
assert!(NymName::new("foo~bar").is_err());
}
}
+199 -11
View File
@@ -536,7 +536,7 @@ checksum = "c762bae6dcaf24c4c84667b8579785430908723d5c889f469d76a41d59cc7a9d"
dependencies = [
"curve25519-dalek",
"ed25519",
"rand",
"rand 0.7.3",
"serde",
"sha2",
"zeroize",
@@ -662,6 +662,101 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8cbd1169bd7b4a0a20d92b9af7a7e0422888bd38a6f5ec29c1fd8c1558a272e"
[[package]]
name = "futures"
version = "0.3.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40"
dependencies = [
"futures-channel",
"futures-core",
"futures-executor",
"futures-io",
"futures-sink",
"futures-task",
"futures-util",
]
[[package]]
name = "futures-channel"
version = "0.3.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2"
dependencies = [
"futures-core",
"futures-sink",
]
[[package]]
name = "futures-core"
version = "0.3.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c"
[[package]]
name = "futures-executor"
version = "0.3.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ccecee823288125bd88b4d7f565c9e58e41858e47ab72e8ea2d64e93624386e0"
dependencies = [
"futures-core",
"futures-task",
"futures-util",
]
[[package]]
name = "futures-io"
version = "0.3.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964"
[[package]]
name = "futures-macro"
version = "0.3.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.12",
]
[[package]]
name = "futures-sink"
version = "0.3.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e"
[[package]]
name = "futures-task"
version = "0.3.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65"
[[package]]
name = "futures-timer"
version = "3.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e64b03909df88034c26dc1547e8970b91f98bdb65165d6a4e9110d94263dbb2c"
[[package]]
name = "futures-util"
version = "0.3.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533"
dependencies = [
"futures-channel",
"futures-core",
"futures-io",
"futures-macro",
"futures-sink",
"futures-task",
"memchr",
"pin-project-lite",
"pin-utils",
"slab",
]
[[package]]
name = "generic-array"
version = "0.12.4"
@@ -949,6 +1044,12 @@ dependencies = [
"cfg-if",
]
[[package]]
name = "memchr"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
[[package]]
name = "mixnet-vesting-integration-tests"
version = "0.1.0"
@@ -1051,7 +1152,7 @@ dependencies = [
"ed25519-dalek",
"nym-pemstore",
"nym-sphinx-types",
"rand",
"rand 0.7.3",
"subtle-encoding",
"thiserror",
"x25519-dalek",
@@ -1121,6 +1222,35 @@ dependencies = [
"thiserror",
]
[[package]]
name = "nym-name-service"
version = "0.1.0"
dependencies = [
"anyhow",
"cosmwasm-std",
"cw-controllers",
"cw-multi-test",
"cw-storage-plus",
"cw-utils",
"cw2",
"nym-contracts-common",
"nym-name-service-common",
"rand 0.8.5",
"rstest",
"semver",
"serde",
"thiserror",
"vergen",
]
[[package]]
name = "nym-name-service-common"
version = "0.1.0"
dependencies = [
"cosmwasm-std",
"serde",
]
[[package]]
name = "nym-pemstore"
version = "0.2.0"
@@ -1232,6 +1362,18 @@ version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e"
[[package]]
name = "pin-project-lite"
version = "0.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116"
[[package]]
name = "pin-utils"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
[[package]]
name = "pkcs8"
version = "0.8.0"
@@ -1339,6 +1481,17 @@ dependencies = [
"rand_hc",
]
[[package]]
name = "rand"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
dependencies = [
"libc",
"rand_chacha 0.3.1",
"rand_core 0.6.4",
]
[[package]]
name = "rand_chacha"
version = "0.2.2"
@@ -1384,7 +1537,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c9e9532ada3929fb8b2e9dbe28d1e06c9b2cc65813f074fcb6bd5fbefeff9d56"
dependencies = [
"num-traits",
"rand",
"rand 0.7.3",
]
[[package]]
@@ -1431,6 +1584,32 @@ dependencies = [
"zeroize",
]
[[package]]
name = "rstest"
version = "0.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "de1bb486a691878cd320c2f0d319ba91eeaa2e894066d8b5f8f117c000e9d962"
dependencies = [
"futures",
"futures-timer",
"rstest_macros",
"rustc_version",
]
[[package]]
name = "rstest_macros"
version = "0.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "290ca1a1c8ca7edb7c3283bd44dc35dd54fdec6253a3912e201ba1072018fca8"
dependencies = [
"cfg-if",
"proc-macro2",
"quote",
"rustc_version",
"syn 1.0.109",
"unicode-ident",
]
[[package]]
name = "rustc_version"
version = "0.4.0"
@@ -1547,7 +1726,7 @@ checksum = "e801c1712f48475582b7696ac71e0ca34ebb30e09338425384269d9717c62cad"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.3",
"syn 2.0.12",
]
[[package]]
@@ -1563,9 +1742,9 @@ dependencies = [
[[package]]
name = "serde_json"
version = "1.0.94"
version = "1.0.96"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1c533a59c9d8a93a09c6ab31f0fd5e5f4dd1b8fc9434804029839884765d04ea"
checksum = "057d394a50403bcac12672b2b18fb387ab6d289d957dab67dd201875391e52f1"
dependencies = [
"itoa",
"ryu",
@@ -1580,7 +1759,7 @@ checksum = "bcec881020c684085e55a25f7fd888954d56609ef363479dc5a1305eb0d40cab"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.3",
"syn 2.0.12",
]
[[package]]
@@ -1606,6 +1785,15 @@ dependencies = [
"rand_core 0.6.4",
]
[[package]]
name = "slab"
version = "0.4.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d"
dependencies = [
"autocfg",
]
[[package]]
name = "sphinx-packet"
version = "0.1.0"
@@ -1624,7 +1812,7 @@ dependencies = [
"hmac",
"lioness",
"log",
"rand",
"rand 0.7.3",
"rand_distr",
"sha2",
"subtle 2.4.1",
@@ -1680,9 +1868,9 @@ dependencies = [
[[package]]
name = "syn"
version = "2.0.3"
version = "2.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8234ae35e70582bfa0f1fedffa6daa248e41dd045310b19800c4a36382c8f60"
checksum = "79d9531f94112cfc3e4c8f5f02cb2b58f72c97b7efd85f70203cc6d8efda5927"
dependencies = [
"proc-macro2",
"quote",
@@ -1731,7 +1919,7 @@ checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.3",
"syn 2.0.12",
]
[[package]]
+1
View File
@@ -7,6 +7,7 @@ members = [
"mixnet-vesting-integration-tests",
"multisig/cw3-flex-multisig",
"multisig/cw4-group",
"name-service",
"service-provider-directory",
"vesting",
]
+28
View File
@@ -0,0 +1,28 @@
[package]
name = "nym-name-service"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["cdylib", "rlib"]
[dependencies]
cosmwasm-std = { workspace = true }
cw-controllers = { workspace = true }
cw-storage-plus = { workspace = true }
cw-utils = { workspace = true }
cw2 = { workspace = true }
nym-contracts-common = { path = "../../common/cosmwasm-smart-contracts/contracts-common", version = "0.4.0" }
nym-name-service-common = { path = "../../common/cosmwasm-smart-contracts/name-service" }
semver = { version = "1.0.16", default-features = false }
serde = { version = "1.0.155", default-features = false, features = ["derive"] }
thiserror = "1.0.39"
[build-dependencies]
vergen = { version = "=7.4.3", default-features = false, features = ["build", "git", "rustc"] }
[dev-dependencies]
anyhow = "1.0.40"
cw-multi-test = { workspace = true }
rand = "0.8.5"
rstest = "0.17.0"
+13
View File
@@ -0,0 +1,13 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use vergen::{vergen, Config};
fn main() {
let mut config = Config::default();
if std::env::var("DOCS_RS").is_ok() {
// If we don't have access to git information, such as in a docs.rs build, don't error
*config.git_mut().skip_if_error_mut() = true;
}
vergen(config).expect("failed to extract build metadata");
}
+16
View File
@@ -0,0 +1,16 @@
// We limit the these for simplicity and to avoid having to deal with paging.
pub const MAX_NUMBER_OF_NAMES_PER_OWNER: u32 = 100;
pub const MAX_NUMBER_OF_NAMES_FOR_ADDRESS: u32 = 100;
pub const NAME_DEFAULT_RETRIEVAL_LIMIT: u32 = 100;
pub const NAME_MAX_RETRIEVAL_LIMIT: u32 = 150;
// Storage keys
pub const CONFIG_KEY: &str = "config";
pub const ADMIN_KEY: &str = "admin";
pub const NAME_ID_COUNTER_KEY: &str = "nidc";
pub const NAMES_PK_NAMESPACE: &str = "nanames";
pub const NAMES_OWNER_IDX_NAMESPACE: &str = "naowner";
pub const NAMES_ADDRESS_IDX_NAMESPACE: &str = "naaddress";
pub const NAMES_NAME_IDX_NAMESPACE: &str = "naname";
+272
View File
@@ -0,0 +1,272 @@
use crate::{
error::{NameServiceError, Result},
state::{self, Config},
};
use cosmwasm_std::{to_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response};
use nym_name_service_common::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg};
use semver::Version;
mod execute;
mod query;
// version info for migration info
const CONTRACT_NAME: &str = "crate:nym-name-service";
const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION");
pub fn instantiate(
mut deps: DepsMut<'_>,
_env: Env,
info: MessageInfo,
msg: InstantiateMsg,
) -> Result<Response> {
state::set_admin(deps.branch(), info.sender.clone())?;
let config = Config {
deposit_required: msg.deposit_required,
};
cw2::set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?;
state::save_config(deps.storage, &config)?;
Ok(Response::new()
.add_attribute("method", "instantiate")
.add_attribute("admin", info.sender))
}
pub fn migrate(
deps: DepsMut<'_>,
_env: Env,
_msg: MigrateMsg,
) -> Result<Response, NameServiceError> {
// 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| {
NameServiceError::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| NameServiceError::SemVerFailure {
value: storage_version_raw,
error_message: error.to_string(),
})?;
if storage_version < version {
cw2::set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?;
// If state structure changed in any contract version in the way migration is needed, it
// should occur here, for example anything from `crate::queued_migrations::`
}
Ok(Response::new())
}
pub fn execute(
deps: DepsMut<'_>,
env: Env,
info: MessageInfo,
msg: ExecuteMsg,
) -> Result<Response, NameServiceError> {
match msg {
ExecuteMsg::Register { name, address } => execute::register(deps, env, info, name, address),
ExecuteMsg::DeleteId { name_id } => execute::delete_id(deps, info, name_id),
ExecuteMsg::DeleteName { name } => execute::delete_name(deps, info, name),
ExecuteMsg::UpdateDepositRequired { deposit_required } => {
execute::update_deposit_required(deps, info, deposit_required)
}
}
}
pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> Result<Binary> {
let response = match msg {
QueryMsg::NameId { name_id } => to_binary(&query::query_id(deps, name_id)?),
QueryMsg::ByOwner { owner } => to_binary(&query::query_owner(deps, owner)?),
QueryMsg::ByAddress { address } => to_binary(&query::query_address(deps, address)?),
QueryMsg::ByName { name } => to_binary(&query::query_name(deps, name)?),
QueryMsg::All { limit, start_after } => {
to_binary(&query::query_all_paged(deps, limit, start_after)?)
}
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)?),
};
Ok(response?)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::test_helpers::{
assert::{assert_config, assert_empty, assert_name, assert_names, assert_not_found},
fixture::name_fixture,
helpers::{get_attribute, nyms},
};
use cosmwasm_std::{
testing::{mock_dependencies, mock_env, mock_info},
Addr, Coin,
};
use nym_name_service_common::{msg::ExecuteMsg, NameEntry, NameId};
const DENOM: &str = "unym";
#[test]
fn instantiate_contract() {
let mut deps = mock_dependencies();
let msg = InstantiateMsg {
deposit_required: Coin::new(100u128, DENOM),
};
let info = mock_info("creator", &[]);
let admin = info.sender.clone();
// Instantiate contract
let res = instantiate(deps.as_mut(), mock_env(), info, msg).unwrap();
assert_eq!(res.messages.len(), 0);
// Check that it worked by querying the config, and checking that the list of names is
// empty
assert_config(deps.as_ref(), &admin, Coin::new(100u128, DENOM));
assert_empty(deps.as_ref());
}
#[test]
fn register_fails_incorrect_deposit() {
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);
// Register
let msg: ExecuteMsg = name_fixture().into();
let owner = name_fixture().owner.to_string();
assert_eq!(
execute(
deps.as_mut(),
mock_env(),
mock_info(&owner, &[nyms(99)]),
msg.clone()
)
.unwrap_err(),
NameServiceError::InsufficientDeposit {
funds: 99u128.into(),
deposit_required: 100u128.into(),
}
);
assert_eq!(
execute(
deps.as_mut(),
mock_env(),
mock_info(&owner, &[nyms(101)]),
msg
)
.unwrap_err(),
NameServiceError::TooLargeDeposit {
funds: 101u128.into(),
deposit_required: 100u128.into(),
}
);
assert_config(deps.as_ref(), &admin, Coin::new(100, DENOM));
assert_empty(deps.as_ref());
}
#[test]
fn register_success() {
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);
// Register
let msg: ExecuteMsg = name_fixture().into();
let info = mock_info("steve", &[nyms(100)]);
let res = execute(deps.as_mut(), mock_env(), info, msg).unwrap();
// Check that the name has had name id assigned to it
let expected_id = 1;
let id: NameId = get_attribute(&res, "register", "name_id").parse().unwrap();
assert_eq!(id, expected_id);
assert_eq!(
get_attribute(&res, "register", "name"),
"my-service".to_string()
);
assert_eq!(
get_attribute(&res, "register", "nym_address"),
"client_id.client_key@gateway_id".to_string()
);
// The expected registered name
let expected_name = NameEntry {
name_id: expected_id,
name: name_fixture(),
};
assert_names(deps.as_ref(), &[expected_name.clone()]);
assert_name(deps.as_ref(), &expected_name);
}
#[test]
fn delete() {
let mut deps = mock_dependencies();
let msg = InstantiateMsg::new(Coin::new(100, "unym"));
let info = mock_info("creator", &[]);
let res = instantiate(deps.as_mut(), mock_env(), info, msg).unwrap();
assert_eq!(res.messages.len(), 0);
// Register
let msg: ExecuteMsg = name_fixture().into();
let info_steve = mock_info("steve", &[nyms(100)]);
assert_eq!(info_steve.sender, name_fixture().owner);
execute(deps.as_mut(), mock_env(), info_steve, msg).unwrap();
// The expected registerd name
let expected_id = 1;
let expected_name = NameEntry {
name_id: expected_id,
name: name_fixture(),
};
assert_names(deps.as_ref(), &[expected_name]);
// Removing someone else's name will fail
let msg = ExecuteMsg::delete_id(expected_id);
let info_timmy = mock_info("timmy", &[]);
assert_eq!(
execute(deps.as_mut(), mock_env(), info_timmy, msg).unwrap_err(),
NameServiceError::Unauthorized {
sender: Addr::unchecked("timmy")
}
);
// Removing an non-existent name will fail
let msg = ExecuteMsg::delete_id(expected_id + 1);
let info_owner = MessageInfo {
sender: name_fixture().owner,
funds: vec![],
};
assert_eq!(
execute(deps.as_mut(), mock_env(), info_owner.clone(), msg).unwrap_err(),
NameServiceError::NotFound {
name_id: expected_id + 1
}
);
// Remove as correct owner succeeds
let msg = ExecuteMsg::delete_id(expected_id);
let res = execute(deps.as_mut(), mock_env(), info_owner, msg).unwrap();
assert_eq!(
get_attribute(&res, "delete_id", "name_id"),
expected_id.to_string()
);
assert_names(deps.as_ref(), &[]);
assert_not_found(deps.as_ref(), expected_id);
}
}
@@ -0,0 +1,162 @@
use crate::{
constants::{MAX_NUMBER_OF_NAMES_FOR_ADDRESS, MAX_NUMBER_OF_NAMES_PER_OWNER},
error::{NameServiceError, Result},
state,
};
use cosmwasm_std::{Addr, BankMsg, Coin, Deps, DepsMut, Env, MessageInfo, Response, Uint128};
use nym_name_service_common::{
events::{
new_delete_id_event, new_delete_name_event, new_register_event,
new_update_deposit_required_event,
},
Address, NameId, NymName, RegisteredName,
};
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(NameServiceError::InsufficientDeposit {
funds: will_deposit,
deposit_required,
}),
std::cmp::Ordering::Equal => Ok(()),
std::cmp::Ordering::Greater => Err(NameServiceError::TooLargeDeposit {
funds: will_deposit,
deposit_required,
}),
}
}
fn ensure_max_names_per_owner(deps: Deps, owner: Addr) -> Result<()> {
let current_entries = query::query_owner(deps, owner.to_string())?;
if current_entries.names.len() < MAX_NUMBER_OF_NAMES_PER_OWNER as usize {
Ok(())
} else {
Err(NameServiceError::ReachedMaxNamesForOwner {
max_names: MAX_NUMBER_OF_NAMES_PER_OWNER,
owner,
})
}
}
fn ensure_max_names_per_address(deps: Deps, address: Address) -> Result<()> {
let current_entries = query::query_address(deps, address.clone())?;
if current_entries.names.len() < MAX_NUMBER_OF_NAMES_FOR_ADDRESS as usize {
Ok(())
} else {
Err(NameServiceError::ReachedMaxNamesForAddress {
max_names: MAX_NUMBER_OF_NAMES_FOR_ADDRESS,
address,
})
}
}
fn ensure_name_exists(deps: Deps, name_id: NameId) -> Result<()> {
if state::names::has_name_id(deps.storage, name_id) {
Ok(())
} else {
Err(NameServiceError::NotFound { name_id })
}
}
fn ensure_name_not_exists(deps: Deps, name: &NymName) -> Result<()> {
if state::names::has_name(deps.storage, name) {
println!("name already exists");
Err(NameServiceError::NameAlreadyRegistered { name: name.clone() })
} else {
Ok(())
}
}
fn ensure_sender_authorized(info: MessageInfo, names: &RegisteredName) -> Result<()> {
if info.sender == names.owner {
Ok(())
} else {
Err(NameServiceError::Unauthorized {
sender: info.sender,
})
}
}
fn return_deposit(name_to_delete: &RegisteredName) -> BankMsg {
BankMsg::Send {
to_address: name_to_delete.owner.to_string(),
amount: vec![name_to_delete.deposit.clone()],
}
}
/// Register a new name. It will be assigned a new name id.
pub fn register(
deps: DepsMut,
env: Env,
info: MessageInfo,
name: NymName,
address: Address,
) -> Result<Response> {
ensure_name_not_exists(deps.as_ref(), &name)?;
ensure_max_names_per_owner(deps.as_ref(), info.sender.clone())?;
ensure_max_names_per_address(deps.as_ref(), 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| NameServiceError::DepositRequired { source: err })?;
ensure_correct_deposit(will_deposit, deposit_required.amount)?;
let new_name = RegisteredName {
address,
name,
owner: info.sender,
block_height: env.block.height,
deposit: Coin::new(will_deposit.u128(), denom),
};
let name_id = state::names::save(deps.storage, &new_name)?;
Ok(Response::new().add_event(new_register_event(name_id, new_name)))
}
/// Delete an exsisting name.
pub fn delete_id(deps: DepsMut, info: MessageInfo, name_id: NameId) -> Result<Response> {
ensure_name_exists(deps.as_ref(), name_id)?;
let name_to_delete = state::names::load_id(deps.storage, name_id)?;
ensure_sender_authorized(info, &name_to_delete)?;
state::names::remove_id(deps.storage, name_id)?;
let return_deposit_msg = return_deposit(&name_to_delete);
Ok(Response::new()
.add_message(return_deposit_msg)
.add_event(new_delete_id_event(name_id, name_to_delete)))
}
/// Delete an existing name by name.
pub(crate) fn delete_name(deps: DepsMut, info: MessageInfo, name: NymName) -> Result<Response> {
let name_to_delete = query::query_name(deps.as_ref(), name)?;
ensure_sender_authorized(info, &name_to_delete.name)?;
state::names::remove_id(deps.storage, name_to_delete.name_id)?;
let return_deposit_msg = return_deposit(&name_to_delete.name);
Ok(Response::new()
.add_message(return_deposit_msg)
.add_event(new_delete_name_event(
name_to_delete.name_id,
name_to_delete.name,
)))
}
/// Update the deposit required to register new names
pub(crate) fn update_deposit_required(
deps: DepsMut,
info: MessageInfo,
deposit_required: Coin,
) -> Result<Response> {
state::assert_admin(deps.as_ref(), &info.sender)?;
let mut config = state::load_config(deps.storage)?;
config.deposit_required = deposit_required.clone();
state::save_config(deps.storage, &config)?;
Ok(Response::new().add_event(new_update_deposit_required_event(deposit_required)))
}
@@ -0,0 +1,68 @@
use cosmwasm_std::Deps;
use nym_contracts_common::ContractBuildInformation;
use nym_name_service_common::{
response::{ConfigResponse, NamesListResponse, PagedNamesListResponse},
Address, NameEntry, NameId, NymName,
};
use crate::{
error::Result,
state::{self, names::PagedLoad},
};
pub fn query_id(deps: Deps, name_id: NameId) -> Result<NameEntry> {
let name = state::names::load_id(deps.storage, name_id)?;
Ok(NameEntry { name_id, name })
}
pub fn query_owner(deps: Deps, owner: String) -> Result<NamesListResponse> {
let owner = deps.api.addr_validate(&owner)?;
let names = state::names::load_owner(deps.storage, owner)?;
Ok(NamesListResponse::new(names))
}
pub fn query_address(deps: Deps, address: Address) -> Result<NamesListResponse> {
let names = state::names::load_address(deps.storage, &address)?;
Ok(NamesListResponse::new(names))
}
pub fn query_name(deps: Deps, name: NymName) -> Result<NameEntry> {
state::names::load_name_entry(deps.storage, &name)
.map(|(name_id, name)| NameEntry::new(name_id, name))
}
pub fn query_all_paged(
deps: Deps,
limit: Option<u32>,
start_after: Option<NameId>,
) -> Result<PagedNamesListResponse> {
let PagedLoad {
names,
limit,
start_next_after,
} = state::names::load_all_paged(deps.storage, limit, start_after)?;
Ok(PagedNamesListResponse::new(names, limit, start_next_after))
}
pub fn query_config(deps: Deps) -> Result<ConfigResponse> {
let config = state::load_config(deps.storage)?;
Ok(config.into())
}
pub fn query_contract_version() -> ContractBuildInformation {
// as per docs
// env! macro will expand to the value of the named environment variable at
// compile time, yielding an expression of type `&'static str`
ContractBuildInformation {
build_timestamp: env!("VERGEN_BUILD_TIMESTAMP").to_string(),
build_version: env!("VERGEN_BUILD_SEMVER").to_string(),
commit_sha: option_env!("VERGEN_GIT_SHA").unwrap_or("NONE").to_string(),
commit_timestamp: option_env!("VERGEN_GIT_COMMIT_TIMESTAMP")
.unwrap_or("NONE")
.to_string(),
commit_branch: option_env!("VERGEN_GIT_BRANCH")
.unwrap_or("NONE")
.to_string(),
rustc_version: env!("VERGEN_RUSTC_SEMVER").to_string(),
}
}
+57
View File
@@ -0,0 +1,57 @@
use cosmwasm_std::{Addr, StdError};
use cw_controllers::AdminError;
use nym_name_service_common::{Address, NameId, NymName};
use thiserror::Error;
#[derive(Error, Debug, PartialEq)]
pub enum NameServiceError {
#[error("{0}")]
Std(#[from] StdError),
#[error("{0}")]
AdminError(#[from] AdminError),
#[error("name id entry not found: {name_id}")]
NotFound { name_id: NameId },
#[error("name entry not found: {name}")]
NameNotFound { name: NymName },
#[error("{sender} is not registrator of name")]
Unauthorized { sender: Addr },
#[error("deposit required to register a name")]
DepositRequired { source: cw_utils::PaymentError },
#[error("insufficiant deposit: {funds}, required: {deposit_required}")]
InsufficientDeposit {
funds: cosmwasm_std::Uint128,
deposit_required: cosmwasm_std::Uint128,
},
#[error("deposit too large: {funds}, required: {deposit_required}")]
TooLargeDeposit {
funds: cosmwasm_std::Uint128,
deposit_required: cosmwasm_std::Uint128,
},
#[error("reached the max number of names ({max_names}) for owner {owner}")]
ReachedMaxNamesForOwner { max_names: u32, owner: Addr },
#[error("reached the max number of names ({max_names}) for address {0}", address.to_string())]
ReachedMaxNamesForAddress { max_names: u32, address: Address },
#[error("failed to parse {value} into a valid SemVer version: {error_message}")]
SemVerFailure {
value: String,
error_message: String,
},
#[error("duplicate entries detected for name: {name}")]
DuplicateNames { name: NymName },
#[error("name already registered: {name}")]
NameAlreadyRegistered { name: NymName },
}
pub(crate) type Result<T, E = NameServiceError> = std::result::Result<T, E>;
@@ -0,0 +1,445 @@
//! Integration tests using cw-multi-test.
use cosmwasm_std::Addr;
use nym_name_service_common::{
response::{ConfigResponse, PagedNamesListResponse},
Address, NameEntry, NymName, RegisteredName,
};
use crate::{
constants::NAME_DEFAULT_RETRIEVAL_LIMIT,
error::NameServiceError,
test_helpers::{fixture::name_entry, 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 register_and_query_name() {
let mut setup = TestSetup::new();
assert_eq!(
setup.query_all(),
PagedNamesListResponse {
names: vec![],
per_page: NAME_DEFAULT_RETRIEVAL_LIMIT as usize,
start_next_after: None,
}
);
// Register a first name
let owner = Addr::unchecked("owner");
let name = NymName::new("steves-server").unwrap();
let nym_address = Address::new("nym-address");
assert_eq!(setup.contract_balance(), nyms(0));
assert_eq!(setup.balance(&owner), nyms(250));
setup.register(name.clone(), nym_address.clone(), owner.clone());
// Deposit is deposited to contract and deducted from owners's balance
assert_eq!(setup.contract_balance(), nyms(100));
assert_eq!(setup.balance(&owner), nyms(150));
// We can query the full name list
assert_eq!(
setup.query_all(),
PagedNamesListResponse {
names: vec![NameEntry {
name_id: 1,
name: RegisteredName {
address: nym_address.clone(),
name: name.clone(),
owner: owner.clone(),
block_height: 12345,
deposit: nyms(100),
},
}],
per_page: NAME_DEFAULT_RETRIEVAL_LIMIT as usize,
start_next_after: Some(1),
}
);
// ... and we can query by id
assert_eq!(
setup.query_id(1),
NameEntry {
name_id: 1,
name: RegisteredName {
address: nym_address.clone(),
name: name.clone(),
owner: owner.clone(),
block_height: 12345,
deposit: nyms(100),
},
}
);
// Register a second name
let owner2 = Addr::unchecked("owner2");
let name2 = NymName::new("another_server").unwrap();
let nym_address2 = Address::new("nymAddress2");
setup.register(name2.clone(), nym_address2.clone(), owner2.clone());
assert_eq!(setup.contract_balance(), nyms(200));
assert_eq!(
setup.query_all(),
PagedNamesListResponse {
names: vec![
name_entry(1, name, nym_address, owner),
name_entry(2, name2, nym_address2, owner2)
],
per_page: NAME_DEFAULT_RETRIEVAL_LIMIT as usize,
start_next_after: Some(2),
}
);
}
#[test]
fn cant_register_a_name_without_funds() {
let mut setup = TestSetup::new();
assert_eq!(setup.contract_balance(), nyms(0));
assert_eq!(setup.balance("owner"), nyms(250));
setup.register(
NymName::new("my_name").unwrap(),
Address::new("nymAddress"),
Addr::unchecked("owner"),
);
assert_eq!(setup.contract_balance(), nyms(100));
assert_eq!(setup.balance("owner"), nyms(150));
setup.register(
NymName::new("my_name2").unwrap(),
Address::new("nymAddress"),
Addr::unchecked("owner"),
);
assert_eq!(setup.contract_balance(), nyms(200));
assert_eq!(setup.balance("owner"), nyms(50));
let res = setup
.try_register(
NymName::new("my_name3").unwrap(),
Address::new("nymAddress"),
Addr::unchecked("owner"),
)
.unwrap_err();
assert_eq!(
res.downcast::<cosmwasm_std::StdError>().unwrap(),
cosmwasm_std::StdError::Overflow {
source: cosmwasm_std::OverflowError::new(
cosmwasm_std::OverflowOperation::Sub,
"50",
"100"
)
}
);
}
#[test]
fn delete_name() {
let mut setup = TestSetup::new();
setup.register(
NymName::new("my_name").unwrap(),
Address::new("nymAddress"),
Addr::unchecked("owner"),
);
assert_eq!(setup.contract_balance(), nyms(100));
assert_eq!(setup.balance("owner"), nyms(150));
assert!(!setup.query_all().names.is_empty());
setup.delete(1, Addr::unchecked("owner"));
// Deleting the name returns the deposit to the owner
assert_eq!(setup.contract_balance(), nyms(0));
assert_eq!(setup.balance("owner"), nyms(250));
assert!(setup.query_all().names.is_empty());
}
#[test]
fn only_owner_can_delete_name() {
let mut setup = TestSetup::new();
assert_eq!(setup.contract_balance(), nyms(0));
setup.register(
NymName::new("name").unwrap(),
Address::new("nymAddress"),
Addr::unchecked("owner"),
);
assert_eq!(setup.contract_balance(), nyms(100));
assert!(!setup.query_all().names.is_empty());
let delete_resp = setup
.try_delete(1, Addr::unchecked("not_owner"))
.unwrap_err();
assert_eq!(setup.contract_balance(), nyms(100));
assert_eq!(
delete_resp.downcast::<NameServiceError>().unwrap(),
NameServiceError::Unauthorized {
sender: Addr::unchecked("not_owner")
}
);
}
#[test]
fn cant_delete_name_that_does_not_exist() {
let mut setup = TestSetup::new();
setup.register(
NymName::new("foo").unwrap(),
Address::new("nymAddress"),
Addr::unchecked("owner"),
);
assert_eq!(setup.contract_balance(), nyms(100));
assert!(!setup.query_all().names.is_empty());
let delete_resp = setup.try_delete(0, Addr::unchecked("owner")).unwrap_err();
assert_eq!(setup.contract_balance(), nyms(100));
assert_eq!(
delete_resp.downcast::<NameServiceError>().unwrap(),
NameServiceError::NotFound { name_id: 0 }
);
let delete_resp = setup.try_delete(2, Addr::unchecked("owner")).unwrap_err();
assert_eq!(setup.contract_balance(), nyms(100));
assert_eq!(
delete_resp.downcast::<NameServiceError>().unwrap(),
NameServiceError::NotFound { name_id: 2 }
);
assert!(!setup.query_all().names.is_empty());
setup.delete(1, Addr::unchecked("owner"));
assert_eq!(setup.contract_balance(), nyms(0));
assert!(setup.query_all().names.is_empty());
}
#[test]
fn cant_register_the_same_name_multiple_times() {
let mut setup = TestSetup::new();
setup.register(
NymName::new("name").unwrap(),
Address::new("nymAddress"),
Addr::unchecked("owner"),
);
let resp = setup
.try_register(
NymName::new("name").unwrap(),
Address::new("nymAddress"),
Addr::unchecked("owner"),
)
.unwrap_err();
assert_eq!(
resp.downcast::<NameServiceError>().unwrap(),
NameServiceError::NameAlreadyRegistered {
name: NymName::new("name").unwrap()
}
);
}
#[test]
fn can_register_multiple_names_for_the_same_nym_address() {
let mut setup = TestSetup::new();
let name1 = NymName::new("name1").unwrap();
let name2 = NymName::new("name2").unwrap();
let address = Address::new("nymaddress");
let owner = Addr::unchecked("owner");
setup.register(name1.clone(), address.clone(), owner.clone());
setup.register(name2.clone(), address.clone(), owner.clone());
assert_eq!(
setup.query_all().names,
vec![
name_entry(1, name1, address.clone(), owner.clone()),
name_entry(2, name2, address, owner)
],
);
}
#[test]
fn register_multiple_names_and_deleting_by_name() {
let mut setup = TestSetup::new();
let owner1 = Addr::unchecked("wealthy_owner_1");
let owner2 = Addr::unchecked("wealthy_owner_2");
let nym_address1 = Address::new("nymaddress1");
let nym_address2 = Address::new("nymaddress2");
let name1 = NymName::new("name1").unwrap();
let name2 = NymName::new("name2").unwrap();
let name3 = NymName::new("name3").unwrap();
let name4 = NymName::new("name4").unwrap();
let name5 = NymName::new("name5").unwrap();
// We register the same address three times, but with different owners
assert_eq!(setup.contract_balance(), nyms(0));
assert_eq!(setup.balance(&owner1), nyms(1000));
setup.register(name1.clone(), nym_address1.clone(), owner1.clone());
setup.register(name2.clone(), nym_address1.clone(), owner1.clone());
setup.register(name3.clone(), nym_address2.clone(), owner1.clone());
setup.register(name4.clone(), nym_address1.clone(), owner2.clone());
setup.register(name5.clone(), nym_address2.clone(), owner2.clone());
assert_eq!(setup.contract_balance(), nyms(500));
assert_eq!(setup.balance(&owner1), nyms(700));
assert_eq!(
setup.query_all(),
PagedNamesListResponse {
names: vec![
name_entry(1, name1.clone(), nym_address1.clone(), owner1.clone()),
name_entry(2, name2.clone(), nym_address1.clone(), owner1.clone()),
name_entry(3, name3.clone(), nym_address2.clone(), owner1.clone()),
name_entry(4, name4.clone(), nym_address1.clone(), owner2.clone()),
name_entry(5, name5.clone(), nym_address2.clone(), owner2.clone()),
],
per_page: NAME_DEFAULT_RETRIEVAL_LIMIT as usize,
start_next_after: Some(5),
}
);
setup.delete_name(name1, owner1.clone());
assert_eq!(setup.contract_balance(), nyms(400));
assert_eq!(setup.balance(&owner1), nyms(800));
assert_eq!(
setup.query_all(),
PagedNamesListResponse {
names: vec![
name_entry(2, name2, nym_address1.clone(), owner1.clone()),
name_entry(3, name3, nym_address2.clone(), owner1),
name_entry(4, name4, nym_address1, owner2.clone()),
name_entry(5, name5, nym_address2, owner2),
],
per_page: NAME_DEFAULT_RETRIEVAL_LIMIT as usize,
start_next_after: Some(5),
}
);
}
#[test]
fn check_paging() {
let mut setup = TestSetup::new();
let owner1 = Addr::unchecked("wealthy_owner_1");
let owner2 = Addr::unchecked("wealthy_owner_2");
let nym_address1 = Address::new("nymAddress1");
let nym_address2 = Address::new("nymAddress2");
let name1 = NymName::new("name1").unwrap();
let name2 = NymName::new("name2").unwrap();
let name3 = NymName::new("name3").unwrap();
let name4 = NymName::new("name4").unwrap();
let name5 = NymName::new("name5").unwrap();
// We register the same address three times, but with different owners
assert_eq!(setup.contract_balance(), nyms(0));
assert_eq!(setup.balance(&owner1), nyms(1000));
setup.register(name1.clone(), nym_address1.clone(), owner1.clone());
setup.register(name2.clone(), nym_address1.clone(), owner1.clone());
setup.register(name3.clone(), nym_address2.clone(), owner1.clone());
setup.register(name4.clone(), nym_address1.clone(), owner2.clone());
setup.register(name5.clone(), nym_address2.clone(), owner2.clone());
assert_eq!(setup.contract_balance(), nyms(500));
assert_eq!(setup.balance(&owner1), nyms(700));
assert_eq!(
setup.query_all(),
PagedNamesListResponse {
names: vec![
name_entry(1, name1.clone(), nym_address1.clone(), owner1.clone()),
name_entry(2, name2.clone(), nym_address1.clone(), owner1.clone()),
name_entry(3, name3.clone(), nym_address2.clone(), owner1.clone()),
name_entry(4, name4.clone(), nym_address1.clone(), owner2.clone()),
name_entry(5, name5, nym_address2.clone(), owner2.clone()),
],
per_page: NAME_DEFAULT_RETRIEVAL_LIMIT as usize,
start_next_after: Some(5),
}
);
setup.delete_name(name1, owner1.clone());
assert_eq!(
setup.query_all_with_limit(Some(2), None),
PagedNamesListResponse {
names: vec![
name_entry(2, name2, nym_address1.clone(), owner1.clone()),
name_entry(3, name3.clone(), nym_address2.clone(), owner1.clone()),
],
per_page: 2,
start_next_after: Some(3),
}
);
assert_eq!(
setup.query_all_with_limit(Some(2), Some(2)),
PagedNamesListResponse {
names: vec![
name_entry(3, name3, nym_address2, owner1),
name_entry(4, name4, nym_address1, owner2),
],
per_page: 2,
start_next_after: Some(4),
}
);
}
#[test]
fn name_id_is_not_resused_when_deleting_and_then_adding_a_new_names() {
let mut setup = TestSetup::new();
setup.register(
NymName::new("myname1").unwrap(),
Address::new("nymAddress1"),
Addr::unchecked("owner1"),
);
setup.register(
NymName::new("myname2").unwrap(),
Address::new("nymAddress2"),
Addr::unchecked("owner2"),
);
setup.register(
NymName::new("myname3").unwrap(),
Address::new("nymAddress3"),
Addr::unchecked("owner3"),
);
setup.delete(1, Addr::unchecked("owner1"));
setup.delete(3, Addr::unchecked("owner3"));
assert_eq!(
setup.query_all().names,
vec![name_entry(
2,
NymName::new("myname2").unwrap(),
Address::new("nymAddress2"),
Addr::unchecked("owner2")
)]
);
setup.register(
NymName::new("myname4").unwrap(),
Address::new("nymAddress4"),
Addr::unchecked("owner4"),
);
assert_eq!(
setup.query_all().names,
vec![
name_entry(
2,
NymName::new("myname2").unwrap(),
Address::new("nymAddress2"),
Addr::unchecked("owner2")
),
name_entry(
4,
NymName::new("myname4").unwrap(),
Address::new("nymAddress4"),
Addr::unchecked("owner4")
)
]
);
}
+57
View File
@@ -0,0 +1,57 @@
//! The nym name service contract is for users to register names for the nym addresses.
#![warn(clippy::expect_used)]
#![warn(clippy::unwrap_used)]
use crate::error::Result;
use nym_name_service_common::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg};
#[cfg(not(feature = "library"))]
use cosmwasm_std::entry_point;
use cosmwasm_std::{Binary, Deps, DepsMut, Env, MessageInfo, Response};
use error::NameServiceError;
mod contract;
mod error;
mod state;
pub mod constants;
#[cfg(test)]
mod integration_tests;
#[cfg(test)]
mod test_helpers;
/// Contract entry point for instantiation.
#[cfg_attr(not(feature = "library"), entry_point)]
pub fn instantiate(
deps: DepsMut<'_>,
env: Env,
info: MessageInfo,
msg: InstantiateMsg,
) -> Result<Response> {
contract::instantiate(deps, env, info, msg)
}
/// Contract entry point for migrations.
#[cfg_attr(not(feature = "library"), entry_point)]
pub fn migrate(deps: DepsMut<'_>, env: Env, msg: MigrateMsg) -> Result<Response, NameServiceError> {
contract::migrate(deps, env, msg)
}
/// Contract entry point for execution
#[cfg_attr(not(feature = "library"), entry_point)]
pub fn execute(
deps: DepsMut<'_>,
env: Env,
info: MessageInfo,
msg: ExecuteMsg,
) -> Result<Response, NameServiceError> {
contract::execute(deps, env, info, msg)
}
/// Contract entry point for queries
#[cfg_attr(not(feature = "library"), entry_point)]
pub fn query(deps: Deps<'_>, env: Env, msg: QueryMsg) -> Result<Binary> {
contract::query(deps, env, msg)
}
+14
View File
@@ -0,0 +1,14 @@
use cosmwasm_std::{Addr, Deps, DepsMut};
use cw_controllers::Admin;
use crate::{constants::ADMIN_KEY, error::Result};
const ADMIN: Admin = Admin::new(ADMIN_KEY);
pub(crate) fn set_admin(deps: DepsMut<'_>, admin: Addr) -> Result<()> {
Ok(ADMIN.set(deps, Some(admin))?)
}
pub(crate) fn assert_admin(deps: Deps, sender: &Addr) -> Result<()> {
Ok(ADMIN.assert_admin(deps, sender)?)
}
@@ -0,0 +1,34 @@
use cosmwasm_std::{Coin, Storage};
use cw_storage_plus::Item;
use nym_name_service_common::response::ConfigResponse;
use serde::{Deserialize, Serialize};
use crate::{constants::CONFIG_KEY, error::Result};
const CONFIG: Item<Config> = Item::new(CONFIG_KEY);
#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)]
pub(crate) struct Config {
pub deposit_required: Coin,
}
impl From<Config> for ConfigResponse {
fn from(config: Config) -> Self {
ConfigResponse {
deposit_required: config.deposit_required,
}
}
}
pub(crate) fn save_config(store: &mut dyn Storage, config: &Config) -> Result<()> {
Ok(CONFIG.save(store, config)?)
}
pub(crate) fn load_config(store: &dyn Storage) -> Result<Config> {
Ok(CONFIG.load(store)?)
}
/// Return the deposit required to register a name.
pub(crate) fn deposit_required(store: &dyn Storage) -> Result<Coin> {
Ok(CONFIG.load(store).map(|config| config.deposit_required)?)
}
+8
View File
@@ -0,0 +1,8 @@
pub mod admin;
pub mod config;
pub mod name_id_counter;
pub mod names;
pub(crate) use admin::{assert_admin, set_admin};
pub(crate) use config::{deposit_required, load_config, save_config, Config};
pub(crate) use name_id_counter::next_name_id_counter;
@@ -0,0 +1,83 @@
use cosmwasm_std::Storage;
use cw_storage_plus::Item;
use nym_name_service_common::NameId;
use crate::{constants::NAME_ID_COUNTER_KEY, error::Result};
const NAME_ID_COUNTER: Item<NameId> = Item::new(NAME_ID_COUNTER_KEY);
/// Generate the next name id, store it and return it
pub(crate) fn next_name_id_counter(store: &mut dyn Storage) -> Result<NameId> {
// The first id is 1.
let id = NAME_ID_COUNTER.may_load(store)?.unwrap_or_default() + 1;
NAME_ID_COUNTER.save(store, &id)?;
Ok(id)
}
#[cfg(test)]
mod tests {
use nym_name_service_common::NameEntry;
use crate::test_helpers::{
assert::assert_names,
fixture::name_fixture_name,
helpers::{delete_name_id, instantiate_test_contract, register_name},
};
#[test]
fn get_next_name_id() {
let mut deps = instantiate_test_contract();
assert_eq!(register_name(deps.as_mut(), &name_fixture_name("foo")), 1);
assert_names(
deps.as_ref(),
&[NameEntry::new(1, name_fixture_name("foo"))],
);
assert_eq!(register_name(deps.as_mut(), &name_fixture_name("bar")), 2);
assert_eq!(register_name(deps.as_mut(), &name_fixture_name("baz")), 3);
assert_names(
deps.as_ref(),
&[
NameEntry::new(1, name_fixture_name("foo")),
NameEntry::new(2, name_fixture_name("bar")),
NameEntry::new(3, name_fixture_name("baz")),
],
);
}
#[test]
fn deleted_name_id_is_not_reused() {
let mut deps = instantiate_test_contract();
// Register two names
assert_eq!(register_name(deps.as_mut(), &name_fixture_name("one")), 1);
assert_eq!(register_name(deps.as_mut(), &name_fixture_name("two")), 2);
assert_names(
deps.as_ref(),
&[
NameEntry::new(1, name_fixture_name("one")),
NameEntry::new(2, name_fixture_name("two")),
],
);
// Delete the last entry
delete_name_id(deps.as_mut(), 2, "steve");
assert_names(
deps.as_ref(),
&[NameEntry::new(1, name_fixture_name("one"))],
);
// Create a third entry. The index should not reuse the previous entry that we just
// deleted.
assert_eq!(register_name(deps.as_mut(), &name_fixture_name("two")), 3);
assert_names(
deps.as_ref(),
&[
NameEntry::new(1, name_fixture_name("one")),
NameEntry::new(3, name_fixture_name("two")),
],
);
}
}
+697
View File
@@ -0,0 +1,697 @@
use cosmwasm_std::{Addr, Order, StdError, StdResult, Storage};
use cw_storage_plus::{Bound, Index, IndexList, IndexedMap, MultiIndex, UniqueIndex};
use nym_name_service_common::{Address, NameId, NymName, RegisteredName};
use crate::{
constants::{
MAX_NUMBER_OF_NAMES_FOR_ADDRESS, MAX_NUMBER_OF_NAMES_PER_OWNER,
NAMES_ADDRESS_IDX_NAMESPACE, NAMES_NAME_IDX_NAMESPACE, NAMES_OWNER_IDX_NAMESPACE,
NAMES_PK_NAMESPACE, NAME_DEFAULT_RETRIEVAL_LIMIT, NAME_MAX_RETRIEVAL_LIMIT,
},
error::{NameServiceError, Result},
};
struct NameIndex<'a> {
// A name can only point to a single address
pub(crate) name: UniqueIndex<'a, String, RegisteredName, NameId>,
// An addresses can be pointed to by multiple names.
pub(crate) address: MultiIndex<'a, String, RegisteredName, NameId>,
pub(crate) owner: MultiIndex<'a, Addr, RegisteredName, NameId>,
}
impl<'a> IndexList<RegisteredName> for NameIndex<'a> {
fn get_indexes(&'_ self) -> Box<dyn Iterator<Item = &'_ dyn Index<RegisteredName>> + '_> {
let v: Vec<&dyn Index<RegisteredName>> = vec![&self.name, &self.address, &self.owner];
Box::new(v.into_iter())
}
}
fn names<'a>() -> IndexedMap<'a, NameId, RegisteredName, NameIndex<'a>> {
let indexes = NameIndex {
name: UniqueIndex::new(|d| d.name.to_string(), NAMES_NAME_IDX_NAMESPACE),
address: MultiIndex::new(
|d| d.address.to_string(),
NAMES_PK_NAMESPACE,
NAMES_ADDRESS_IDX_NAMESPACE,
),
owner: MultiIndex::new(
|d| d.owner.clone(),
NAMES_PK_NAMESPACE,
NAMES_OWNER_IDX_NAMESPACE,
),
};
IndexedMap::new(NAMES_PK_NAMESPACE, indexes)
}
pub fn save(store: &mut dyn Storage, new_name: &RegisteredName) -> Result<NameId> {
let name_id = super::next_name_id_counter(store)?;
names().save(store, name_id, new_name)?;
Ok(name_id)
}
#[cfg(test)]
pub fn save_all(state: &mut dyn Storage, names: &[RegisteredName]) -> Result<Vec<NameId>> {
let mut ids = vec![];
for name in names {
ids.push(save(state, name)?);
}
Ok(ids)
}
pub fn has_name_id(store: &dyn Storage, name_id: NameId) -> bool {
names().has(store, name_id)
}
pub fn has_name(store: &dyn Storage, name: &NymName) -> bool {
load_name(store, name).is_ok()
}
// Get the (key, name) entry for a given name
pub fn load_name_entry(store: &dyn Storage, name: &NymName) -> Result<(NameId, RegisteredName)> {
names()
.idx
.name
.range(store, None, None, Order::Ascending)
.find(|entry| {
if let Ok(entry) = entry {
&entry.1.name == name
} else {
false
}
})
.ok_or(NameServiceError::NameNotFound { name: name.clone() })?
.map_err(NameServiceError::from)
}
pub fn load_id(store: &dyn Storage, name_id: NameId) -> Result<RegisteredName> {
names().load(store, name_id).map_err(|err| match err {
StdError::NotFound { .. } => NameServiceError::NotFound { name_id },
err => err.into(),
})
}
pub fn load_name(store: &dyn Storage, name: &NymName) -> Result<RegisteredName> {
names()
.idx
.name
.item(store, name.to_string())?
.map(|record| record.1)
.ok_or(NameServiceError::NameNotFound { name: name.clone() })
}
pub fn load_address(
store: &dyn Storage,
address: &Address,
) -> Result<Vec<(NameId, RegisteredName)>> {
names()
.idx
.address
.prefix(address.to_string())
.range(store, None, None, Order::Ascending)
.take(MAX_NUMBER_OF_NAMES_FOR_ADDRESS as usize)
.collect::<StdResult<Vec<_>>>()
.map_err(NameServiceError::from)
}
pub fn load_owner(store: &dyn Storage, owner: Addr) -> Result<Vec<(NameId, RegisteredName)>> {
names()
.idx
.owner
.prefix(owner)
.range(store, None, None, Order::Ascending)
.take(MAX_NUMBER_OF_NAMES_PER_OWNER as usize)
.collect::<StdResult<Vec<_>>>()
.map_err(NameServiceError::from)
}
pub fn remove_id(store: &mut dyn Storage, name_id: NameId) -> Result<()> {
Ok(names().remove(store, name_id)?)
}
#[cfg(test)]
pub fn remove_name(store: &mut dyn Storage, name: NymName) -> Result<NameId> {
let name_info = load_name_entry(store, &name)?;
remove_id(store, name_info.0)?;
Ok(name_info.0)
}
#[derive(Debug, PartialEq)]
pub struct PagedLoad {
pub names: Vec<(NameId, RegisteredName)>,
pub limit: usize,
pub start_next_after: Option<NameId>,
}
pub fn load_all_paged(
store: &dyn Storage,
limit: Option<u32>,
start_after: Option<NameId>,
) -> Result<PagedLoad> {
let limit = limit
.unwrap_or(NAME_DEFAULT_RETRIEVAL_LIMIT)
.min(NAME_MAX_RETRIEVAL_LIMIT) as usize;
let start = start_after.map(Bound::exclusive);
let names = names()
.range(store, start, None, Order::Ascending)
.take(limit)
.collect::<StdResult<Vec<_>>>()?;
let start_next_after = names.last().map(|name| name.0);
Ok(PagedLoad {
names,
limit,
start_next_after,
})
}
#[cfg(test)]
mod tests {
use std::iter::zip;
use cosmwasm_std::{
testing::{MockApi, MockQuerier},
MemoryStorage, OwnedDeps,
};
use rstest::rstest;
use crate::{
error::NameServiceError,
test_helpers::{
fixture::{name_fixture, name_fixture_full},
helpers::instantiate_test_contract,
},
};
use super::*;
type TestDeps = OwnedDeps<MemoryStorage, MockApi, MockQuerier>;
#[rstest::fixture]
fn deps() -> TestDeps {
instantiate_test_contract()
}
#[rstest::fixture]
fn uniq_names() -> Vec<RegisteredName> {
vec![
name_fixture_full("one", "address_one", "owner_one"),
name_fixture_full("two", "address_two", "owner_two"),
name_fixture_full("three", "address_three", "owner_three"),
]
}
#[rstest::fixture]
fn overlapping_addresses() -> Vec<RegisteredName> {
vec![
name_fixture_full("one", "address_one", "owner_one"),
name_fixture_full("two", "address_two", "owner_two"),
name_fixture_full("three", "address_two", "owner_three"),
]
}
#[rstest::fixture]
fn overlapping_owners() -> Vec<RegisteredName> {
vec![
name_fixture_full("one", "address_one", "owner_one"),
name_fixture_full("two", "address_two", "owner_two"),
name_fixture_full("three", "address_three", "owner_two"),
]
}
fn assert_not_registered(store: &dyn Storage, names: Vec<RegisteredName>, ids: Vec<NameId>) {
let names: Vec<(NameId, RegisteredName)> = zip(ids, names).collect();
let loaded = load_all_paged(store, None, None).unwrap();
for (id, name) in &names {
assert!(!has_name_id(store, *id));
assert!(!has_name(store, &name.name));
assert!(!loaded.names.iter().any(|(i, _)| i == id));
assert!(!loaded.names.iter().any(|(_, n)| n == name));
}
}
fn assert_registered(store: &dyn Storage, names: Vec<RegisteredName>, ids: Vec<NameId>) {
assert!(names.len() == ids.len());
let names: Vec<(NameId, RegisteredName)> = zip(ids, names).collect();
let loaded = load_all_paged(store, None, None).unwrap();
for (id, name) in &names {
assert!(has_name_id(store, *id));
assert!(has_name(store, &name.name));
assert!(loaded.names.iter().filter(|(i, _)| i == id).count() == 1);
assert!(loaded.names.iter().filter(|(_, n)| n == name).count() == 1);
}
}
fn assert_only_these_registered(
store: &dyn Storage,
names: Vec<RegisteredName>,
ids: Vec<NameId>,
) {
let last_id = *ids.last().unwrap();
let names: Vec<(NameId, RegisteredName)> = zip(ids, names).collect();
for (id, name) in &names {
assert!(has_name_id(store, *id));
assert!(has_name(store, &name.name));
}
assert_eq!(
load_all_paged(store, None, None).unwrap(),
PagedLoad {
names,
limit: NAME_DEFAULT_RETRIEVAL_LIMIT as usize,
start_next_after: Some(last_id),
}
)
}
#[rstest]
fn single_basic_save_works(mut deps: TestDeps) {
save(deps.as_mut().storage, &name_fixture()).unwrap();
}
#[rstest]
fn save_same_name_twice_fails(mut deps: TestDeps) {
save(deps.as_mut().storage, &name_fixture()).unwrap();
assert!(matches!(
save(deps.as_mut().storage, &name_fixture()).unwrap_err(),
NameServiceError::Std(StdError::GenericErr { .. })
));
}
#[rstest]
fn has_name_works(mut deps: TestDeps) {
assert!(!has_name(&deps.storage, &name_fixture().name));
save(deps.as_mut().storage, &name_fixture()).unwrap();
assert!(has_name(&deps.storage, &name_fixture().name));
}
#[rstest]
fn has_name_id_works(mut deps: TestDeps) {
assert!(!has_name_id(&deps.storage, 1));
save(deps.as_mut().storage, &name_fixture()).unwrap();
assert!(has_name_id(&deps.storage, 1));
}
#[rstest]
fn has_name_id_with_incorrect_id_fails(mut deps: TestDeps) {
assert!(!has_name_id(&deps.storage, 2));
save(deps.as_mut().storage, &name_fixture()).unwrap();
assert!(!has_name_id(&deps.storage, 2));
}
#[rstest]
fn load_name_entry_works(mut deps: TestDeps) {
assert_eq!(
load_name_entry(deps.as_ref().storage, &name_fixture().name).unwrap_err(),
NameServiceError::NameNotFound {
name: name_fixture().name
}
);
save(deps.as_mut().storage, &name_fixture()).unwrap();
assert_eq!(
load_name_entry(deps.as_ref().storage, &name_fixture().name).unwrap(),
(1, name_fixture())
);
}
#[rstest]
fn load_id_works(mut deps: TestDeps) {
assert_eq!(
load_id(deps.as_ref().storage, 1).unwrap_err(),
NameServiceError::NotFound { name_id: 1 }
);
save(deps.as_mut().storage, &name_fixture()).unwrap();
assert_eq!(load_id(deps.as_ref().storage, 1).unwrap(), name_fixture(),);
}
#[rstest]
fn load_name_works(mut deps: TestDeps) {
assert_eq!(
load_name(deps.as_ref().storage, &name_fixture().name).unwrap_err(),
NameServiceError::NameNotFound {
name: name_fixture().name
}
);
save(deps.as_mut().storage, &name_fixture()).unwrap();
assert_eq!(
load_name(deps.as_ref().storage, &name_fixture().name).unwrap(),
name_fixture(),
);
}
#[rstest]
fn load_address_works(mut deps: TestDeps) {
assert_eq!(
load_address(deps.as_ref().storage, &name_fixture().address).unwrap(),
vec![],
);
save(deps.as_mut().storage, &name_fixture()).unwrap();
assert_eq!(
load_address(deps.as_ref().storage, &name_fixture().address).unwrap(),
vec![(1, name_fixture())],
);
}
#[rstest]
fn load_owner_works(mut deps: TestDeps) {
assert_eq!(
load_owner(deps.as_ref().storage, name_fixture().owner).unwrap(),
vec![],
);
save(deps.as_mut().storage, &name_fixture()).unwrap();
assert_eq!(
load_owner(deps.as_ref().storage, name_fixture().owner).unwrap(),
vec![(1, name_fixture())],
);
}
#[rstest]
fn load_all_paged_works(mut deps: TestDeps, uniq_names: Vec<RegisteredName>) {
assert_eq!(
load_all_paged(&deps.storage, None, None).unwrap(),
PagedLoad {
names: vec![],
limit: NAME_DEFAULT_RETRIEVAL_LIMIT as usize,
start_next_after: None
}
);
save(deps.as_mut().storage, &uniq_names[0]).unwrap();
assert_eq!(
load_all_paged(&deps.storage, None, None).unwrap(),
PagedLoad {
names: vec![(1, uniq_names[0].clone())],
limit: NAME_DEFAULT_RETRIEVAL_LIMIT as usize,
start_next_after: Some(1),
}
);
}
#[rstest]
fn remove_id_works(mut deps: TestDeps) {
save(deps.as_mut().storage, &name_fixture()).unwrap();
assert!(has_name_id(&deps.storage, 1));
assert!(has_name(&deps.storage, &name_fixture().name));
remove_id(deps.as_mut().storage, 1).unwrap();
assert!(!has_name_id(&deps.storage, 1));
assert!(!has_name(&deps.storage, &name_fixture().name));
}
#[rstest]
fn remove_name_works(mut deps: TestDeps) {
save(deps.as_mut().storage, &name_fixture()).unwrap();
assert!(has_name_id(&deps.storage, 1));
assert!(has_name(&deps.storage, &name_fixture().name));
remove_name(deps.as_mut().storage, name_fixture().name).unwrap();
assert!(!has_name_id(&deps.storage, 1));
assert!(!has_name(&deps.storage, &name_fixture().name));
}
#[rstest]
fn save_set_of_unique_names_works(mut deps: TestDeps, uniq_names: Vec<RegisteredName>) {
let num = uniq_names.len() as NameId;
let ids = (1..=num).collect::<Vec<NameId>>();
assert_not_registered(&deps.storage, uniq_names.clone(), ids.clone());
let saved_ids = save_all(deps.as_mut().storage, &uniq_names).unwrap();
assert_eq!(saved_ids, ids);
assert_registered(&deps.storage, uniq_names.clone(), ids.clone());
assert_only_these_registered(&deps.storage, uniq_names, ids);
}
#[rstest]
fn save_set_of_unique_names_generates_ids(mut deps: TestDeps, uniq_names: Vec<RegisteredName>) {
let num = uniq_names.len() as NameId;
let ids = save_all(deps.as_mut().storage, &uniq_names).unwrap();
assert_eq!(ids, (1..=num).collect::<Vec<NameId>>());
}
#[rstest]
fn load_name_entry_for_unique_set_works(mut deps: TestDeps, uniq_names: Vec<RegisteredName>) {
save_all(deps.as_mut().storage, &uniq_names).unwrap();
for (id, name) in uniq_names.iter().enumerate() {
assert_eq!(
load_name_entry(deps.as_ref().storage, &name.name).unwrap(),
(id as NameId + 1, name.clone()),
);
}
}
#[rstest]
fn load_name_for_unique_set_works(mut deps: TestDeps, uniq_names: Vec<RegisteredName>) {
save_all(deps.as_mut().storage, &uniq_names).unwrap();
for name in uniq_names {
assert_eq!(
load_name(deps.as_ref().storage, &name.name).unwrap(),
name.clone(),
);
}
}
#[rstest]
fn save_and_remove_name_id_from_set_of_unique_set_works(
mut deps: TestDeps,
uniq_names: Vec<RegisteredName>,
) {
let ids = save_all(deps.as_mut().storage, &uniq_names).unwrap();
remove_id(deps.as_mut().storage, ids[1]).unwrap();
assert_eq!(
load_all_paged(deps.as_ref().storage, None, None).unwrap(),
PagedLoad {
names: vec![
(1, name_fixture_full("one", "address_one", "owner_one")),
(
3,
name_fixture_full("three", "address_three", "owner_three")
),
],
limit: NAME_DEFAULT_RETRIEVAL_LIMIT as usize,
start_next_after: Some(3),
}
);
}
#[rstest]
fn save_and_remove_name_from_set_of_unique_set_works(
mut deps: TestDeps,
uniq_names: Vec<RegisteredName>,
) {
save_all(deps.as_mut().storage, &uniq_names).unwrap();
remove_name(deps.as_mut().storage, uniq_names[1].name.clone()).unwrap();
assert_eq!(
load_all_paged(deps.as_ref().storage, None, None).unwrap(),
PagedLoad {
names: vec![
(1, name_fixture_full("one", "address_one", "owner_one")),
(
3,
name_fixture_full("three", "address_three", "owner_three")
),
],
limit: NAME_DEFAULT_RETRIEVAL_LIMIT as usize,
start_next_after: Some(3),
}
);
}
#[rstest]
fn save_set_of_overlapping_addressed_works(
mut deps: TestDeps,
overlapping_addresses: Vec<RegisteredName>,
) {
let ids = save_all(deps.as_mut().storage, &overlapping_addresses).unwrap();
assert_registered(&deps.storage, overlapping_addresses.clone(), ids.clone());
assert_only_these_registered(&deps.storage, overlapping_addresses, ids);
}
#[rstest]
fn load_address_with_overlapping_addresses_works(
mut deps: TestDeps,
overlapping_addresses: Vec<RegisteredName>,
) {
save_all(deps.as_mut().storage, &overlapping_addresses).unwrap();
assert_eq!(
load_all_paged(deps.as_ref().storage, None, None).unwrap(),
PagedLoad {
names: vec![
(1, name_fixture_full("one", "address_one", "owner_one")),
(2, name_fixture_full("two", "address_two", "owner_two")),
(3, name_fixture_full("three", "address_two", "owner_three")),
],
limit: NAME_DEFAULT_RETRIEVAL_LIMIT as usize,
start_next_after: Some(3),
}
);
assert_eq!(
load_address(deps.as_ref().storage, &Address::new("address_two")).unwrap(),
vec![
(2, name_fixture_full("two", "address_two", "owner_two")),
(3, name_fixture_full("three", "address_two", "owner_three")),
]
);
}
#[rstest]
fn save_and_remove_name_id_from_set_of_overlapping_addresses_works(
mut deps: TestDeps,
overlapping_addresses: Vec<RegisteredName>,
) {
let ids = save_all(deps.as_mut().storage, &overlapping_addresses).unwrap();
remove_id(deps.as_mut().storage, ids[1]).unwrap();
assert_eq!(
load_all_paged(deps.as_ref().storage, None, None).unwrap(),
PagedLoad {
names: vec![
(1, name_fixture_full("one", "address_one", "owner_one")),
(3, name_fixture_full("three", "address_two", "owner_three")),
],
limit: NAME_DEFAULT_RETRIEVAL_LIMIT as usize,
start_next_after: Some(3),
}
);
}
#[rstest]
fn save_and_remove_name_from_set_of_overlapping_addresses_works(
mut deps: TestDeps,
overlapping_addresses: Vec<RegisteredName>,
) {
save_all(deps.as_mut().storage, &overlapping_addresses).unwrap();
remove_name(deps.as_mut().storage, overlapping_addresses[1].name.clone()).unwrap();
assert_eq!(
load_all_paged(deps.as_ref().storage, None, None).unwrap(),
PagedLoad {
names: vec![
(1, name_fixture_full("one", "address_one", "owner_one")),
(3, name_fixture_full("three", "address_two", "owner_three")),
],
limit: NAME_DEFAULT_RETRIEVAL_LIMIT as usize,
start_next_after: Some(3),
}
);
}
#[rstest]
fn save_set_of_overlapping_owners_works(
mut deps: TestDeps,
overlapping_owners: Vec<RegisteredName>,
) {
let ids = save_all(deps.as_mut().storage, &overlapping_owners).unwrap();
assert_registered(&deps.storage, overlapping_owners.clone(), ids.clone());
assert_only_these_registered(&deps.storage, overlapping_owners, ids);
}
#[rstest]
fn load_owner_with_overlapping_owners_works(
mut deps: TestDeps,
overlapping_owners: Vec<RegisteredName>,
) {
save_all(deps.as_mut().storage, &overlapping_owners).unwrap();
assert_eq!(
load_all_paged(deps.as_ref().storage, None, None).unwrap(),
PagedLoad {
names: vec![
(1, name_fixture_full("one", "address_one", "owner_one")),
(2, name_fixture_full("two", "address_two", "owner_two")),
(3, name_fixture_full("three", "address_three", "owner_two")),
],
limit: NAME_DEFAULT_RETRIEVAL_LIMIT as usize,
start_next_after: Some(3),
}
);
assert_eq!(
load_owner(deps.as_ref().storage, Addr::unchecked("owner_two")).unwrap(),
vec![
(2, name_fixture_full("two", "address_two", "owner_two")),
(3, name_fixture_full("three", "address_three", "owner_two")),
]
);
}
#[rstest]
fn save_and_remove_name_id_from_set_of_overlapping_owners_works(
mut deps: TestDeps,
overlapping_owners: Vec<RegisteredName>,
) {
let ids = save_all(deps.as_mut().storage, &overlapping_owners).unwrap();
assert_registered(&deps.storage, overlapping_owners.clone(), ids.clone());
assert_only_these_registered(&deps.storage, overlapping_owners, ids);
remove_id(deps.as_mut().storage, 2).unwrap();
assert_eq!(
load_all_paged(deps.as_ref().storage, None, None).unwrap(),
PagedLoad {
names: vec![
(1, name_fixture_full("one", "address_one", "owner_one")),
(3, name_fixture_full("three", "address_three", "owner_two")),
],
limit: NAME_DEFAULT_RETRIEVAL_LIMIT as usize,
start_next_after: Some(3),
}
);
}
#[rstest]
fn save_and_remove_name_from_set_of_overlapping_owners_works(
mut deps: TestDeps,
overlapping_owners: Vec<RegisteredName>,
) {
let ids = save_all(deps.as_mut().storage, &overlapping_owners).unwrap();
assert_registered(&deps.storage, overlapping_owners.clone(), ids.clone());
assert_only_these_registered(&deps.storage, overlapping_owners.clone(), ids);
remove_name(deps.as_mut().storage, overlapping_owners[1].name.clone()).unwrap();
assert_eq!(
load_all_paged(deps.as_ref().storage, None, None).unwrap(),
PagedLoad {
names: vec![
(1, name_fixture_full("one", "address_one", "owner_one")),
(3, name_fixture_full("three", "address_three", "owner_two")),
],
limit: NAME_DEFAULT_RETRIEVAL_LIMIT as usize,
start_next_after: Some(3),
}
);
}
#[rstest]
fn load_all_paged_with_limit_works(mut deps: TestDeps, uniq_names: Vec<RegisteredName>) {
save_all(deps.as_mut().storage, &uniq_names).unwrap();
assert_eq!(
load_all_paged(&deps.storage, Some(2), None).unwrap(),
PagedLoad {
names: vec![
(1, name_fixture_full("one", "address_one", "owner_one")),
(2, name_fixture_full("two", "address_two", "owner_two")),
],
limit: 2,
start_next_after: Some(2),
}
);
assert_eq!(
load_all_paged(&deps.storage, Some(1), Some(2)).unwrap(),
PagedLoad {
names: vec![(
3,
name_fixture_full("three", "address_three", "owner_three")
),],
limit: 1,
start_next_after: Some(3),
}
);
assert_eq!(
load_all_paged(&deps.storage, Some(2), Some(2)).unwrap(),
PagedLoad {
names: vec![(
3,
name_fixture_full("three", "address_three", "owner_three")
),],
limit: 2,
start_next_after: Some(3),
}
);
}
#[test]
#[ignore]
fn max_page_limit_is_applied() {
// WIP(JON)
todo!();
}
}
@@ -0,0 +1,65 @@
use cosmwasm_std::{from_binary, testing::mock_env, Addr, Coin, Deps};
use nym_name_service_common::{
msg::QueryMsg,
response::{ConfigResponse, PagedNamesListResponse},
NameEntry, NameId,
};
use crate::{constants::NAME_DEFAULT_RETRIEVAL_LIMIT, error::NameServiceError};
pub fn assert_config(deps: Deps, admin: &Addr, deposit_required: Coin) {
crate::state::assert_admin(deps, admin).unwrap();
let res = crate::contract::query(deps, mock_env(), QueryMsg::Config {}).unwrap();
let config: ConfigResponse = from_binary(&res).unwrap();
assert_eq!(config, ConfigResponse { deposit_required });
}
pub fn assert_names(deps: Deps, expected_names: &[NameEntry]) {
let res = crate::contract::query(deps, mock_env(), QueryMsg::all()).unwrap();
let names: PagedNamesListResponse = from_binary(&res).unwrap();
let start_next_after = expected_names.iter().last().map(|s| s.name_id);
assert_eq!(
names,
PagedNamesListResponse {
names: expected_names.to_vec(),
per_page: NAME_DEFAULT_RETRIEVAL_LIMIT as usize,
start_next_after,
}
);
}
pub fn assert_name(deps: Deps, expected_name: &NameEntry) {
let res = crate::contract::query(
deps,
mock_env(),
QueryMsg::NameId {
name_id: expected_name.name_id,
},
)
.unwrap();
let names: NameEntry = from_binary(&res).unwrap();
assert_eq!(&names, expected_name);
}
pub fn assert_empty(deps: Deps) {
let res = crate::contract::query(deps, mock_env(), QueryMsg::all()).unwrap();
let names: PagedNamesListResponse = from_binary(&res).unwrap();
assert!(names.names.is_empty());
}
pub fn assert_not_found(deps: Deps, expected_id: NameId) {
let res = crate::contract::query(
deps,
mock_env(),
QueryMsg::NameId {
name_id: expected_id,
},
)
.unwrap_err();
assert!(matches!(
res,
NameServiceError::NotFound {
name_id: _expected_id
}
));
}
@@ -0,0 +1,29 @@
use cosmwasm_std::Addr;
use nym_name_service_common::{Address, NameEntry, NameId, NymName, RegisteredName};
use super::helpers::nyms;
pub fn name_fixture_full(name: &str, nym_address: &str, owner: &str) -> RegisteredName {
RegisteredName {
name: NymName::new(name).unwrap(),
address: Address::new(nym_address),
owner: Addr::unchecked(owner),
block_height: 12345,
deposit: nyms(100),
}
}
pub fn name_fixture() -> RegisteredName {
name_fixture_full("my-service", "client_id.client_key@gateway_id", "steve")
}
pub fn name_fixture_name(name: &str) -> RegisteredName {
name_fixture_full(name, "client_id.client_key@gateway_id", "steve")
}
pub fn name_entry(name_id: NameId, name: NymName, address: Address, owner: Addr) -> NameEntry {
NameEntry {
name_id,
name: name_fixture_full(name.as_str(), address.as_str(), owner.as_str()),
}
}
@@ -0,0 +1,107 @@
use cosmwasm_std::{
coin, coins,
testing::{mock_dependencies, mock_env, mock_info, MockApi, MockQuerier},
Coin, DepsMut, Event, MemoryStorage, OwnedDeps, Response,
};
use cw_multi_test::AppResponse;
use nym_name_service_common::{
events::{NameEventType, NAME_ID},
msg::{ExecuteMsg, InstantiateMsg},
NameId, NymName, RegisteredName,
};
pub fn nyms(amount: u64) -> Coin {
Coin::new(amount.into(), "unym")
}
pub fn get_event_types(response: &Response, event_type: &str) -> Vec<Event> {
response
.events
.iter()
.filter(|ev| ev.ty == event_type)
.cloned()
.collect()
}
pub fn get_attribute(response: &Response, event_type: &str, key: &str) -> String {
get_event_types(response, event_type)
.first()
.unwrap()
.attributes
.iter()
.find(|attr| attr.key == key)
.unwrap()
.value
.clone()
}
pub fn get_app_event_types(response: &AppResponse, event_type: &str) -> Vec<Event> {
response
.events
.iter()
.filter(|ev| ev.ty == event_type)
.cloned()
.collect()
}
pub fn get_app_attribute(response: &AppResponse, event_type: &str, key: &str) -> String {
get_app_event_types(response, event_type)
.first()
.unwrap()
.attributes
.iter()
.find(|attr| attr.key == key)
.unwrap()
.value
.clone()
}
#[allow(unused)]
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 register_name(deps: DepsMut<'_>, name: &RegisteredName) -> NameId {
let msg: ExecuteMsg = name.clone().into();
let info = mock_info(name.owner.as_str(), &coins(100, "unym"));
let res = crate::execute(deps, mock_env(), info, msg).unwrap();
let name_id: NameId = get_attribute(&res, &NameEventType::Register.to_string(), NAME_ID)
.parse()
.unwrap();
name_id
}
pub fn delete_name_id(deps: DepsMut<'_>, name_id: NameId, owner: &str) {
let msg = ExecuteMsg::DeleteId { name_id };
let info = mock_info(owner, &[]);
crate::execute(deps, mock_env(), info, msg).unwrap();
}
#[allow(unused)]
pub fn delete_name(deps: DepsMut<'_>, name: NymName, owner: &str) {
let msg = ExecuteMsg::DeleteName { name };
let info = mock_info(owner, &[]);
crate::execute(deps, mock_env(), info, msg).unwrap();
}
@@ -0,0 +1,4 @@
pub mod assert;
pub mod fixture;
pub mod helpers;
pub mod test_setup;
@@ -0,0 +1,153 @@
use cosmwasm_std::{coins, Addr, Coin, Uint128};
use cw_multi_test::{App, AppBuilder, AppResponse, ContractWrapper, Executor};
use nym_name_service_common::{
msg::{ExecuteMsg, InstantiateMsg, QueryMsg},
response::{ConfigResponse, PagedNamesListResponse},
Address, NameEntry, NameId, NymName,
};
use serde::de::DeserializeOwned;
use crate::test_helpers::helpers::get_app_attribute;
const DENOM: &str = "unym";
const ADDRESSES: &[&str] = &[
"user", "admin", "owner", "owner1", "owner2", "owner3", "owner4",
];
const WEALTHY_ADDRESSES: &[&str] = &["wealthy_owner_1", "wealthy_owner_2"];
/// Helper for being able to systematic integration tests
pub struct TestSetup {
app: App,
addr: Addr,
}
impl Default for TestSetup {
fn default() -> Self {
TestSetup::new()
}
}
impl TestSetup {
pub fn new() -> Self {
let mut app = AppBuilder::new().build(|router, _, storage| {
let mut init_balance = |account: &str, amount: u128| {
router
.bank
.init_balance(storage, &Addr::unchecked(account), coins(amount, DENOM))
.unwrap();
};
ADDRESSES.iter().for_each(|addr| init_balance(addr, 250));
WEALTHY_ADDRESSES
.iter()
.for_each(|addr| init_balance(addr, 1000));
});
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 }
}
fn instantiate(app: &mut App, code_id: u64) -> Addr {
app.instantiate_contract(
code_id,
Addr::unchecked("admin"),
&InstantiateMsg {
deposit_required: Coin::new(100, DENOM),
},
&[],
"contract_label",
None,
)
.unwrap()
}
pub fn contract_balance(&self) -> Coin {
self.app.wrap().query_balance(&self.addr, DENOM).unwrap()
}
pub fn query<T: DeserializeOwned>(&self, query_msg: &QueryMsg) -> T {
self.app
.wrap()
.query_wasm_smart(&self.addr, query_msg)
.unwrap()
}
pub fn query_config(&self) -> ConfigResponse {
self.query(&QueryMsg::Config {})
}
pub fn query_id(&self, name_id: NameId) -> NameEntry {
self.query(&QueryMsg::NameId { name_id })
}
pub fn query_all(&self) -> PagedNamesListResponse {
self.query(&QueryMsg::all())
}
pub fn query_all_with_limit(
&self,
limit: Option<u32>,
start_after: Option<u32>,
) -> PagedNamesListResponse {
self.query(&QueryMsg::All { limit, start_after })
}
pub fn try_register(
&mut self,
name: NymName,
address: Address,
owner: Addr,
) -> anyhow::Result<AppResponse> {
self.app.execute_contract(
owner,
self.addr.clone(),
&ExecuteMsg::Register { name, address },
&[Coin {
denom: DENOM.to_string(),
amount: Uint128::new(100),
}],
)
}
pub fn register(&mut self, name: NymName, address: Address, owner: Addr) -> AppResponse {
let resp = self.try_register(name, address, owner).unwrap();
assert_eq!(
get_app_attribute(&resp, "wasm-register", "action"),
"register"
);
resp
}
pub fn try_delete(&mut self, name_id: NameId, owner: Addr) -> anyhow::Result<AppResponse> {
self.app.execute_contract(
owner,
self.addr.clone(),
&ExecuteMsg::DeleteId { name_id },
&[],
)
}
pub fn delete(&mut self, name_id: NameId, owner: Addr) -> AppResponse {
let delete_resp = self.try_delete(name_id, owner).unwrap();
assert_eq!(
get_app_attribute(&delete_resp, "wasm-delete_id", "action"),
"delete_id"
);
delete_resp
}
pub fn delete_name(&mut self, name: NymName, owner: Addr) -> AppResponse {
self.app
.execute_contract(
owner,
self.addr.clone(),
&ExecuteMsg::DeleteName { name },
&[],
)
.unwrap()
}
pub fn balance(&self, address: impl Into<String>) -> Coin {
self.app.wrap().query_balance(address, DENOM).unwrap()
}
}
+1
View File
@@ -18,6 +18,7 @@ MULTISIG_CONTRACT_ADDRESS=n1gaq3666chd5348apj8cka8t2mckv7azp9espyr7wgpxyuzur5d0s
COCONUT_DKG_CONTRACT_ADDRESS=n18yadscxw8v35dds7ksv3j0svmjh3h6e7tmxpadk96mvgz27zygkshuf4vs
REWARDING_VALIDATOR_ADDRESS=n10yyd98e2tuwu0f7ypz9dy3hhjw7v772q6287gy
SERVICE_PROVIDER_DIRECTORY_CONTRACT_ADDRESS=n1ryt076cufyddallg5x0gz3qjz0pd3wg0m4cwkg9njhmlnp6u88qq6nczgj
NAME_SERVICE_CONTRACT_ADDRESS=n1cm2u5vfjd3zalfw0p65xyh4tcrw3hjlm0960gzhewga449h4mgas77mjkl
STATISTICS_SERVICE_DOMAIN_ADDRESS="https://mainnet-stats.nymte.ch:8090"
NYXD="https://qwerty-validator.qa.nymte.ch/"
NYM_API="https://qwerty-validator-api.qa.nymte.ch/api"