From f4fafbfea5fa9453d1eec57e4cf621e97a79d25e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Tue, 23 Jul 2024 18:20:29 +0100 Subject: [PATCH] updated ecash-contract parameters and generated schema --- .../bandwidth-controller/src/acquire/mod.rs | 12 +- .../contract_traits/ecash_signing_client.rs | 35 +- .../cosmwasm/generators/ecash_bandwidth.rs | 11 +- .../coconut-bandwidth-contract/src/events.rs | 3 - .../ecash-contract/src/counters.rs | 12 + .../ecash-contract/src/error.rs | 12 +- .../ecash-contract/src/events.rs | 9 - .../ecash-contract/src/lib.rs | 1 + .../ecash-contract/src/msg.rs | 22 +- common/credential-utils/src/utils.rs | 4 +- contracts/Cargo.lock | 6 + contracts/Makefile | 5 +- contracts/ecash/.cargo/config | 4 + contracts/ecash/Cargo.toml | 17 +- contracts/ecash/Makefile | 5 + contracts/ecash/schema/nym-ecash.json | 585 ++++++++++++++++++ contracts/ecash/schema/raw/execute.json | 189 ++++++ contracts/ecash/schema/raw/instantiate.json | 26 + contracts/ecash/schema/raw/migrate.json | 6 + contracts/ecash/schema/raw/query.json | 123 ++++ .../raw/response_to_get_blacklist_paged.json | 70 +++ .../response_to_get_blacklisted_account.json | 42 ++ .../schema/raw/response_to_get_deposit.json | 40 ++ .../raw/response_to_get_deposits_paged.json | 58 ++ ...sponse_to_get_required_deposit_amount.json | 23 + contracts/ecash/src/bin/schema.rs | 14 + contracts/ecash/src/constants.rs | 5 + contracts/ecash/src/contract/helpers.rs | 22 +- contracts/ecash/src/contract/mod.rs | 221 ++++--- contracts/ecash/src/helpers.rs | 14 +- contracts/ecash/src/lib.rs | 1 + contracts/name-service/Cargo.toml | 36 -- .../service-provider-directory/Cargo.toml | 34 - .../src/manager/local_client.rs | 2 +- .../src/manager/network_init.rs | 2 +- 35 files changed, 1455 insertions(+), 216 deletions(-) create mode 100644 common/cosmwasm-smart-contracts/ecash-contract/src/counters.rs create mode 100644 contracts/ecash/.cargo/config create mode 100644 contracts/ecash/Makefile create mode 100644 contracts/ecash/schema/nym-ecash.json create mode 100644 contracts/ecash/schema/raw/execute.json create mode 100644 contracts/ecash/schema/raw/instantiate.json create mode 100644 contracts/ecash/schema/raw/migrate.json create mode 100644 contracts/ecash/schema/raw/query.json create mode 100644 contracts/ecash/schema/raw/response_to_get_blacklist_paged.json create mode 100644 contracts/ecash/schema/raw/response_to_get_blacklisted_account.json create mode 100644 contracts/ecash/schema/raw/response_to_get_deposit.json create mode 100644 contracts/ecash/schema/raw/response_to_get_deposits_paged.json create mode 100644 contracts/ecash/schema/raw/response_to_get_required_deposit_amount.json create mode 100644 contracts/ecash/src/bin/schema.rs create mode 100644 contracts/ecash/src/constants.rs delete mode 100644 contracts/name-service/Cargo.toml delete mode 100644 contracts/service-provider-directory/Cargo.toml diff --git a/common/bandwidth-controller/src/acquire/mod.rs b/common/bandwidth-controller/src/acquire/mod.rs index fadb2581bf..e9f3b528d4 100644 --- a/common/bandwidth-controller/src/acquire/mod.rs +++ b/common/bandwidth-controller/src/acquire/mod.rs @@ -12,8 +12,8 @@ use nym_crypto::asymmetric::identity; use nym_ecash_time::{ecash_default_expiration_date, Date}; use nym_validator_client::coconut::all_ecash_api_clients; use nym_validator_client::nym_api::EpochId; -use nym_validator_client::nyxd::contract_traits::DkgQueryClient; use nym_validator_client::nyxd::contract_traits::EcashSigningClient; +use nym_validator_client::nyxd::contract_traits::{DkgQueryClient, EcashQueryClient}; use nym_validator_client::nyxd::cosmwasm_client::ToSingletonContractData; use nym_validator_client::EcashApiClient; use rand::rngs::OsRng; @@ -24,14 +24,20 @@ pub async fn make_deposit( expiration: Option, ) -> Result where - C: EcashSigningClient + Sync, + C: EcashSigningClient + EcashQueryClient + Sync, { let mut rng = OsRng; let signing_key = identity::PrivateKey::new(&mut rng); let expiration = expiration.unwrap_or_else(ecash_default_expiration_date); + let deposit_amount = client.get_required_deposit_amount().await?; + info!("we'll need to deposit {deposit_amount} to obtain the ticketbook"); let result = client - .make_ticketbook_deposit(signing_key.public_key().to_base58_string(), None) + .make_ticketbook_deposit( + signing_key.public_key().to_base58_string(), + deposit_amount.into(), + None, + ) .await?; let deposit_id = result.parse_singleton_u32_contract_data()?; diff --git a/common/client-libs/validator-client/src/nyxd/contract_traits/ecash_signing_client.rs b/common/client-libs/validator-client/src/nyxd/contract_traits/ecash_signing_client.rs index 98a3e2675b..4c40a2b2fb 100644 --- a/common/client-libs/validator-client/src/nyxd/contract_traits/ecash_signing_client.rs +++ b/common/client-libs/validator-client/src/nyxd/contract_traits/ecash_signing_client.rs @@ -7,7 +7,6 @@ use crate::nyxd::error::NyxdError; use crate::nyxd::{Coin, Fee, SigningCosmWasmClient}; use crate::signing::signer::OfflineSigner; use async_trait::async_trait; -use nym_ecash_contract_common::events::TICKET_BOOK_VALUE; use nym_ecash_contract_common::msg::ExecuteMsg as EcashExecuteMsg; #[cfg_attr(target_arch = "wasm32", async_trait(?Send))] @@ -24,13 +23,13 @@ pub trait EcashSigningClient { async fn make_ticketbook_deposit( &self, public_key: String, + deposit_amount: Coin, fee: Option, ) -> Result { let req = EcashExecuteMsg::DepositTicketBookFunds { identity_key: public_key, }; - let amount = Coin::new(TICKET_BOOK_VALUE, "unym"); - self.execute_ecash_contract(fee, req, "Ecash::Deposit".to_string(), vec![amount]) + self.execute_ecash_contract(fee, req, "Ecash::Deposit".to_string(), vec![deposit_amount]) .await } @@ -48,6 +47,28 @@ pub trait EcashSigningClient { .await } + async fn update_admin( + &self, + admin: String, + fee: Option, + ) -> Result { + let req = EcashExecuteMsg::UpdateAdmin { admin }; + self.execute_ecash_contract(fee, req, "Ecash::UpdateAdmin".to_string(), vec![]) + .await + } + + async fn update_deposit_value( + &self, + new_deposit: Coin, + fee: Option, + ) -> Result { + let req = EcashExecuteMsg::UpdateDepositValue { + new_deposit: new_deposit.into(), + }; + self.execute_ecash_contract(fee, req, "Ecash::UpdateDepositValue".to_string(), vec![]) + .await + } + async fn propose_for_blacklist( &self, public_key: String, @@ -95,7 +116,7 @@ where #[cfg(test)] mod tests { use super::*; - use crate::nyxd::contract_traits::tests::IgnoreValue; + use crate::nyxd::contract_traits::tests::{mock_coin, IgnoreValue}; use nym_ecash_contract_common::msg::ExecuteMsg; // it's enough that this compiles and clippy is happy about it @@ -106,7 +127,7 @@ mod tests { ) { match msg { EcashExecuteMsg::DepositTicketBookFunds { identity_key } => client - .make_ticketbook_deposit(identity_key.to_string(), None) + .make_ticketbook_deposit(identity_key.to_string(), mock_coin(), None) .ignore(), EcashExecuteMsg::AddToBlacklist { public_key: _ } => unimplemented!(), //no add to blacklist method on client EcashExecuteMsg::ProposeToBlacklist { public_key } => { @@ -119,6 +140,10 @@ mod tests { .request_ticket_redemption(commitment_bs58, number_of_tickets, None) .ignore(), ExecuteMsg::RedeemTickets { .. } => unimplemented!(), // no redeem tickets method for the client + ExecuteMsg::UpdateAdmin { admin } => client.update_admin(admin, None).ignore(), + ExecuteMsg::UpdateDepositValue { new_deposit } => client + .update_deposit_value(new_deposit.into(), None) + .ignore(), }; } } diff --git a/common/commands/src/validator/cosmwasm/generators/ecash_bandwidth.rs b/common/commands/src/validator/cosmwasm/generators/ecash_bandwidth.rs index c3632b1130..f13bb65151 100644 --- a/common/commands/src/validator/cosmwasm/generators/ecash_bandwidth.rs +++ b/common/commands/src/validator/cosmwasm/generators/ecash_bandwidth.rs @@ -4,6 +4,7 @@ use std::str::FromStr; use clap::Parser; +use cosmwasm_std::Coin; use log::{debug, info}; use nym_ecash_contract_common::msg::InstantiateMsg; @@ -20,8 +21,8 @@ pub struct Args { #[clap(long)] pub holding_account: AccountId, - #[clap(long)] - pub mix_denom: Option, + #[clap(long, default_value = "75000000unym")] + pub deposit_amount: Coin, } pub async fn generate(args: Args) { @@ -43,15 +44,11 @@ pub async fn generate(args: Args) { .expect("Failed converting multisig address to AccountId") }); - let mix_denom = args.mix_denom.unwrap_or_else(|| { - std::env::var(nym_network_defaults::var_names::MIX_DENOM).expect("Mix denom has to be set") - }); - let instantiate_msg = InstantiateMsg { holding_account: args.holding_account.to_string(), group_addr: group_addr.to_string(), multisig_addr: multisig_addr.to_string(), - mix_denom, + deposit_amount: args.deposit_amount, }; debug!("instantiate_msg: {:?}", instantiate_msg); diff --git a/common/cosmwasm-smart-contracts/coconut-bandwidth-contract/src/events.rs b/common/cosmwasm-smart-contracts/coconut-bandwidth-contract/src/events.rs index 450ecd2543..5204f6a97e 100644 --- a/common/cosmwasm-smart-contracts/coconut-bandwidth-contract/src/events.rs +++ b/common/cosmwasm-smart-contracts/coconut-bandwidth-contract/src/events.rs @@ -4,9 +4,6 @@ // event types pub const DEPOSITED_FUNDS_EVENT_TYPE: &str = "deposited-funds"; -// a 'wasm-' prefix is added to all cosmwasm events -pub const COSMWASM_DEPOSITED_FUNDS_EVENT_TYPE: &str = "wasm-deposited-funds"; - // attributes that are used in multiple places pub const DEPOSIT_VALUE: &str = "deposit-value"; pub const DEPOSIT_INFO: &str = "deposit-info"; diff --git a/common/cosmwasm-smart-contracts/ecash-contract/src/counters.rs b/common/cosmwasm-smart-contracts/ecash-contract/src/counters.rs new file mode 100644 index 0000000000..2826afc002 --- /dev/null +++ b/common/cosmwasm-smart-contracts/ecash-contract/src/counters.rs @@ -0,0 +1,12 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use cosmwasm_schema::cw_serde; +use cosmwasm_std::Coin; + +#[cw_serde] +pub struct PoolCounters { + pub total_deposited: Coin, + pub total_redeemed_gateways: Coin, + pub total_redeemed_holding: Coin, +} diff --git a/common/cosmwasm-smart-contracts/ecash-contract/src/error.rs b/common/cosmwasm-smart-contracts/ecash-contract/src/error.rs index 3c7778d553..3aec9dd6e7 100644 --- a/common/cosmwasm-smart-contracts/ecash-contract/src/error.rs +++ b/common/cosmwasm-smart-contracts/ecash-contract/src/error.rs @@ -15,7 +15,7 @@ pub enum EcashContractError { InvalidDeposit(#[from] PaymentError), #[error("received wrong amount for deposit. got: {received}. required: {amount}")] - WrongAmount { received: u128, amount: u128 }, + WrongAmount { received: Coin, amount: Coin }, #[error("There aren't enough funds in the contract")] NotEnoughFunds, @@ -57,12 +57,12 @@ pub enum EcashContractError { #[error("the provided ed25519 identity was malformed")] MalformedEd25519Identity, - #[error("the required deposit amount has changed since the contract was created! This was not expected! It used to be {at_init} but it's {current} now! Please let the developers know ASAP!")] - DepositAmountChanged { at_init: Coin, current: Coin }, - - #[error("the e-cash ticket value has changed since the contract was created! This was not expected! It used to be {at_init} but it's {current} now! Please let the developers know ASAP!")] - TicketValueChanged { at_init: Coin, current: Coin }, + #[error("the ticket book size has changed since the contract was created! This was not expected! It used to be {at_init} but it's {current} now! Please let the developers know ASAP!")] + TicketBookSizeChanged { at_init: u64, current: u64 }, #[error("the provided tickets redemption commitment is malformed")] MalformedRedemptionCommitment, + + #[error("the account blacklisting hasn't been fully implemented yet")] + UnimplementedBlacklisting, } diff --git a/common/cosmwasm-smart-contracts/ecash-contract/src/events.rs b/common/cosmwasm-smart-contracts/ecash-contract/src/events.rs index 7217421cc5..a1f243b151 100644 --- a/common/cosmwasm-smart-contracts/ecash-contract/src/events.rs +++ b/common/cosmwasm-smart-contracts/ecash-contract/src/events.rs @@ -4,15 +4,6 @@ // event types pub const DEPOSITED_FUNDS_EVENT_TYPE: &str = "deposited-funds"; -// a 'wasm-' prefix is added to all cosmwasm events -pub const COSMWASM_DEPOSITED_FUNDS_EVENT_TYPE: &str = "wasm-deposited-funds"; - pub const DEPOSIT_ID: &str = "deposit-id"; - -pub const TICKET_BOOK_VALUE: u128 = 50_000_000; -// pub const TICKET_VALUE: u128 = 50_000; - pub const WASM_EVENT_NAME: &str = "wasm"; pub const PROPOSAL_ID_ATTRIBUTE_NAME: &str = "proposal_id"; -pub const BLACKLIST_PROPOSAL_REPLY_ID: u64 = 7759; -pub const REDEMPTION_PROPOSAL_REPLY_ID: u64 = 2137; diff --git a/common/cosmwasm-smart-contracts/ecash-contract/src/lib.rs b/common/cosmwasm-smart-contracts/ecash-contract/src/lib.rs index 10e07c1009..3c05433349 100644 --- a/common/cosmwasm-smart-contracts/ecash-contract/src/lib.rs +++ b/common/cosmwasm-smart-contracts/ecash-contract/src/lib.rs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 pub mod blacklist; +pub mod counters; pub mod deposit; pub mod error; pub mod event_attributes; diff --git a/common/cosmwasm-smart-contracts/ecash-contract/src/msg.rs b/common/cosmwasm-smart-contracts/ecash-contract/src/msg.rs index 4b5c965978..43bd4ca823 100644 --- a/common/cosmwasm-smart-contracts/ecash-contract/src/msg.rs +++ b/common/cosmwasm-smart-contracts/ecash-contract/src/msg.rs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 use cosmwasm_schema::cw_serde; +use cosmwasm_std::Coin; #[cfg(feature = "schema")] use crate::blacklist::{BlacklistedAccountResponse, PagedBlacklistedAccountResponse}; @@ -9,15 +10,13 @@ use crate::blacklist::{BlacklistedAccountResponse, PagedBlacklistedAccountRespon use crate::deposit::{DepositResponse, PagedDepositsResponse}; #[cfg(feature = "schema")] use cosmwasm_schema::QueryResponses; -#[cfg(feature = "schema")] -use cosmwasm_std::Coin; #[cw_serde] pub struct InstantiateMsg { pub holding_account: String, pub multisig_addr: String, pub group_addr: String, - pub mix_denom: String, + pub deposit_amount: Coin, } #[cw_serde] @@ -38,10 +37,16 @@ pub enum ExecuteMsg { n: u16, gw: String, }, - // SpendCredential { - // serial_number: String, - // gateway_cosmos_address: String, - // }, + + UpdateAdmin { + admin: String, + }, + + UpdateDepositValue { + new_deposit: Coin, + }, + + // TODO: properly implement ProposeToBlacklist { public_key: String, }, @@ -74,3 +79,6 @@ pub enum QueryMsg { start_after: Option, }, } + +#[cw_serde] +pub struct MigrateMsg {} diff --git a/common/credential-utils/src/utils.rs b/common/credential-utils/src/utils.rs index 7c77b077b5..9e8ad0e847 100644 --- a/common/credential-utils/src/utils.rs +++ b/common/credential-utils/src/utils.rs @@ -10,7 +10,7 @@ use nym_credential_storage::storage::Storage; use nym_ecash_time::ecash_default_expiration_date; use nym_validator_client::coconut::all_ecash_api_clients; use nym_validator_client::nyxd::contract_traits::{ - dkg_query_client::EpochState, DkgQueryClient, EcashSigningClient, + dkg_query_client::EpochState, DkgQueryClient, EcashQueryClient, EcashSigningClient, }; use std::path::PathBuf; use std::time::Duration; @@ -18,7 +18,7 @@ use time::OffsetDateTime; pub async fn issue_credential(client: &C, storage: &S, client_id: &[u8]) -> Result<()> where - C: DkgQueryClient + EcashSigningClient + Send + Sync, + C: DkgQueryClient + EcashSigningClient + EcashQueryClient + Send + Sync, S: Storage, ::StorageError: Send + Sync + 'static, { diff --git a/contracts/Cargo.lock b/contracts/Cargo.lock index e51183c7a7..3bdbdb9022 100644 --- a/contracts/Cargo.lock +++ b/contracts/Cargo.lock @@ -1233,6 +1233,7 @@ dependencies = [ "nym-crypto", "nym-ecash-contract-common", "nym-multisig-contract-common", + "nym-network-defaults", "rand_chacha", "schemars", "semver", @@ -1250,6 +1251,7 @@ dependencies = [ "cosmwasm-std", "cw-controllers", "cw-utils", + "cw2", "nym-multisig-contract-common", "thiserror", ] @@ -1320,6 +1322,10 @@ dependencies = [ "thiserror", ] +[[package]] +name = "nym-network-defaults" +version = "0.1.0" + [[package]] name = "nym-pemstore" version = "0.3.0" diff --git a/contracts/Makefile b/contracts/Makefile index 519bab061f..e9ff461be0 100644 --- a/contracts/Makefile +++ b/contracts/Makefile @@ -1,4 +1,4 @@ -schema: coconut-bandwidth-schema coconut-dkg-schema mixnet-schema vesting-schema multisig-schema group-schema +schema: coconut-bandwidth-schema coconut-dkg-schema mixnet-schema vesting-schema multisig-schema group-schema ecash-schema coconut-bandwidth-schema: $(MAKE) -C coconut-bandwidth generate-schema @@ -12,6 +12,9 @@ mixnet-schema: vesting-schema: $(MAKE) -C vesting generate-schema +ecash-schema: + $(MAKE) -C ecash generate-schema + multisig-schema: $(MAKE) -C multisig/cw3-flex-multisig generate-schema diff --git a/contracts/ecash/.cargo/config b/contracts/ecash/.cargo/config new file mode 100644 index 0000000000..2c25b8a87e --- /dev/null +++ b/contracts/ecash/.cargo/config @@ -0,0 +1,4 @@ +[alias] +wasm = "build --release --target wasm32-unknown-unknown" +unit-test = "test --lib" +schema = "run --bin schema --features=schema-gen" \ No newline at end of file diff --git a/contracts/ecash/Cargo.toml b/contracts/ecash/Cargo.toml index 7c63848966..51cc7a8d1b 100644 --- a/contracts/ecash/Cargo.toml +++ b/contracts/ecash/Cargo.toml @@ -1,10 +1,18 @@ [package] name = "nym-ecash" version = "0.1.0" -edition = "2021" +edition = { workspace = true } +authors = { workspace = true } +license = { workspace = true } +repository = { workspace = true } + +[[bin]] +name = "schema" +required-features = ["schema-gen"] [lib] -crate-type = ["cdylib"] +name = "ecash_contract" +crate-type = ["cdylib", "rlib"] [dependencies] bs58.workspace = true @@ -26,9 +34,12 @@ semver = { workspace = true, default-features = false } nym-ecash-contract-common = { path = "../../common/cosmwasm-smart-contracts/ecash-contract" } nym-contracts-common = { path = "../../common/cosmwasm-smart-contracts/contracts-common" } nym-multisig-contract-common = { path = "../../common/cosmwasm-smart-contracts/multisig-contract" } - +nym-network-defaults = { path = "../../common/network-defaults", default-features = false } [dev-dependencies] sylvia = { workspace = true, features = ["mt"] } nym-crypto = { path = "../../common/crypto", features = ["rand", "asymmetric"] } rand_chacha = "0.3" + +[features] +schema-gen = ["nym-ecash-contract-common/schema"] diff --git a/contracts/ecash/Makefile b/contracts/ecash/Makefile new file mode 100644 index 0000000000..8ca651ccec --- /dev/null +++ b/contracts/ecash/Makefile @@ -0,0 +1,5 @@ +wasm: + RUSTFLAGS='-C link-arg=-s' cargo build --release --target wasm32-unknown-unknown + +generate-schema: + cargo schema \ No newline at end of file diff --git a/contracts/ecash/schema/nym-ecash.json b/contracts/ecash/schema/nym-ecash.json new file mode 100644 index 0000000000..542c518df4 --- /dev/null +++ b/contracts/ecash/schema/nym-ecash.json @@ -0,0 +1,585 @@ +{ + "contract_name": "nym-ecash", + "contract_version": "0.1.0", + "idl_version": "1.0.0", + "instantiate": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "InstantiateMsg", + "type": "object", + "required": [ + "group_addr", + "holding_account", + "mix_denom", + "multisig_addr" + ], + "properties": { + "group_addr": { + "type": "string" + }, + "holding_account": { + "type": "string" + }, + "mix_denom": { + "type": "string" + }, + "multisig_addr": { + "type": "string" + } + }, + "additionalProperties": false + }, + "execute": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ExecuteMsg", + "oneOf": [ + { + "description": "Used by clients to request ticket books from the signers", + "type": "object", + "required": [ + "deposit_ticket_book_funds" + ], + "properties": { + "deposit_ticket_book_funds": { + "type": "object", + "required": [ + "identity_key" + ], + "properties": { + "identity_key": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Used by gateways to batch redeem tokens from the spent tickets", + "type": "object", + "required": [ + "request_redemption" + ], + "properties": { + "request_redemption": { + "type": "object", + "required": [ + "commitment_bs58", + "number_of_tickets" + ], + "properties": { + "commitment_bs58": { + "type": "string" + }, + "number_of_tickets": { + "type": "integer", + "format": "uint16", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "The actual message that gets executed, after multisig votes, that transfers the ticket tokens into gateway's (and the holding) account", + "type": "object", + "required": [ + "redeem_tickets" + ], + "properties": { + "redeem_tickets": { + "type": "object", + "required": [ + "gw", + "n" + ], + "properties": { + "gw": { + "type": "string" + }, + "n": { + "type": "integer", + "format": "uint16", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "update_admin" + ], + "properties": { + "update_admin": { + "type": "object", + "required": [ + "admin" + ], + "properties": { + "admin": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "update_deposit_value" + ], + "properties": { + "update_deposit_value": { + "type": "object", + "required": [ + "new_deposit" + ], + "properties": { + "new_deposit": { + "$ref": "#/definitions/Coin" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "propose_to_blacklist" + ], + "properties": { + "propose_to_blacklist": { + "type": "object", + "required": [ + "public_key" + ], + "properties": { + "public_key": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "add_to_blacklist" + ], + "properties": { + "add_to_blacklist": { + "type": "object", + "required": [ + "public_key" + ], + "properties": { + "public_key": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ], + "definitions": { + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } + }, + "query": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "QueryMsg", + "oneOf": [ + { + "type": "object", + "required": [ + "get_blacklisted_account" + ], + "properties": { + "get_blacklisted_account": { + "type": "object", + "required": [ + "public_key" + ], + "properties": { + "public_key": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "get_blacklist_paged" + ], + "properties": { + "get_blacklist_paged": { + "type": "object", + "properties": { + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "start_after": { + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "get_required_deposit_amount" + ], + "properties": { + "get_required_deposit_amount": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "get_deposit" + ], + "properties": { + "get_deposit": { + "type": "object", + "required": [ + "deposit_id" + ], + "properties": { + "deposit_id": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "get_deposits_paged" + ], + "properties": { + "get_deposits_paged": { + "type": "object", + "properties": { + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "start_after": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "migrate": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "MigrateMsg", + "type": "object", + "additionalProperties": false + }, + "sudo": null, + "responses": { + "get_blacklist_paged": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "PagedBlacklistedAccountResponse", + "type": "object", + "required": [ + "accounts", + "per_page" + ], + "properties": { + "accounts": { + "type": "array", + "items": { + "$ref": "#/definitions/BlacklistedAccount" + } + }, + "per_page": { + "type": "integer", + "format": "uint", + "minimum": 0.0 + }, + "start_next_after": { + "description": "Field indicating paging information for the following queries if the caller wishes to get further entries.", + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false, + "definitions": { + "BlacklistedAccount": { + "type": "object", + "required": [ + "info", + "public_key" + ], + "properties": { + "info": { + "$ref": "#/definitions/Blacklisting" + }, + "public_key": { + "type": "string" + } + }, + "additionalProperties": false + }, + "Blacklisting": { + "type": "object", + "required": [ + "proposal_id" + ], + "properties": { + "finalized_at_height": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + }, + "proposal_id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + } + }, + "get_blacklisted_account": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "BlacklistedAccountResponse", + "type": "object", + "properties": { + "account": { + "anyOf": [ + { + "$ref": "#/definitions/Blacklisting" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false, + "definitions": { + "Blacklisting": { + "type": "object", + "required": [ + "proposal_id" + ], + "properties": { + "finalized_at_height": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + }, + "proposal_id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + } + }, + "get_deposit": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "DepositResponse", + "type": "object", + "required": [ + "id" + ], + "properties": { + "deposit": { + "anyOf": [ + { + "$ref": "#/definitions/Deposit" + }, + { + "type": "null" + } + ] + }, + "id": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false, + "definitions": { + "Deposit": { + "type": "object", + "required": [ + "bs58_encoded_ed25519_pubkey" + ], + "properties": { + "bs58_encoded_ed25519_pubkey": { + "type": "string" + } + }, + "additionalProperties": false + } + } + }, + "get_deposits_paged": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "PagedDepositsResponse", + "type": "object", + "required": [ + "deposits" + ], + "properties": { + "deposits": { + "type": "array", + "items": { + "$ref": "#/definitions/DepositData" + } + }, + "start_next_after": { + "description": "Field indicating paging information for the following queries if the caller wishes to get further entries.", + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false, + "definitions": { + "Deposit": { + "type": "object", + "required": [ + "bs58_encoded_ed25519_pubkey" + ], + "properties": { + "bs58_encoded_ed25519_pubkey": { + "type": "string" + } + }, + "additionalProperties": false + }, + "DepositData": { + "type": "object", + "required": [ + "deposit", + "id" + ], + "properties": { + "deposit": { + "$ref": "#/definitions/Deposit" + }, + "id": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + } + }, + "get_required_deposit_amount": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Coin", + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + }, + "definitions": { + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } + } + } +} diff --git a/contracts/ecash/schema/raw/execute.json b/contracts/ecash/schema/raw/execute.json new file mode 100644 index 0000000000..9a7e3e054a --- /dev/null +++ b/contracts/ecash/schema/raw/execute.json @@ -0,0 +1,189 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ExecuteMsg", + "oneOf": [ + { + "description": "Used by clients to request ticket books from the signers", + "type": "object", + "required": [ + "deposit_ticket_book_funds" + ], + "properties": { + "deposit_ticket_book_funds": { + "type": "object", + "required": [ + "identity_key" + ], + "properties": { + "identity_key": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Used by gateways to batch redeem tokens from the spent tickets", + "type": "object", + "required": [ + "request_redemption" + ], + "properties": { + "request_redemption": { + "type": "object", + "required": [ + "commitment_bs58", + "number_of_tickets" + ], + "properties": { + "commitment_bs58": { + "type": "string" + }, + "number_of_tickets": { + "type": "integer", + "format": "uint16", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "The actual message that gets executed, after multisig votes, that transfers the ticket tokens into gateway's (and the holding) account", + "type": "object", + "required": [ + "redeem_tickets" + ], + "properties": { + "redeem_tickets": { + "type": "object", + "required": [ + "gw", + "n" + ], + "properties": { + "gw": { + "type": "string" + }, + "n": { + "type": "integer", + "format": "uint16", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "update_admin" + ], + "properties": { + "update_admin": { + "type": "object", + "required": [ + "admin" + ], + "properties": { + "admin": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "update_deposit_value" + ], + "properties": { + "update_deposit_value": { + "type": "object", + "required": [ + "new_deposit" + ], + "properties": { + "new_deposit": { + "$ref": "#/definitions/Coin" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "propose_to_blacklist" + ], + "properties": { + "propose_to_blacklist": { + "type": "object", + "required": [ + "public_key" + ], + "properties": { + "public_key": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "add_to_blacklist" + ], + "properties": { + "add_to_blacklist": { + "type": "object", + "required": [ + "public_key" + ], + "properties": { + "public_key": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ], + "definitions": { + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } +} diff --git a/contracts/ecash/schema/raw/instantiate.json b/contracts/ecash/schema/raw/instantiate.json new file mode 100644 index 0000000000..0b228df79c --- /dev/null +++ b/contracts/ecash/schema/raw/instantiate.json @@ -0,0 +1,26 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "InstantiateMsg", + "type": "object", + "required": [ + "group_addr", + "holding_account", + "mix_denom", + "multisig_addr" + ], + "properties": { + "group_addr": { + "type": "string" + }, + "holding_account": { + "type": "string" + }, + "mix_denom": { + "type": "string" + }, + "multisig_addr": { + "type": "string" + } + }, + "additionalProperties": false +} diff --git a/contracts/ecash/schema/raw/migrate.json b/contracts/ecash/schema/raw/migrate.json new file mode 100644 index 0000000000..7fbe8c5708 --- /dev/null +++ b/contracts/ecash/schema/raw/migrate.json @@ -0,0 +1,6 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "MigrateMsg", + "type": "object", + "additionalProperties": false +} diff --git a/contracts/ecash/schema/raw/query.json b/contracts/ecash/schema/raw/query.json new file mode 100644 index 0000000000..646ddfa84f --- /dev/null +++ b/contracts/ecash/schema/raw/query.json @@ -0,0 +1,123 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "QueryMsg", + "oneOf": [ + { + "type": "object", + "required": [ + "get_blacklisted_account" + ], + "properties": { + "get_blacklisted_account": { + "type": "object", + "required": [ + "public_key" + ], + "properties": { + "public_key": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "get_blacklist_paged" + ], + "properties": { + "get_blacklist_paged": { + "type": "object", + "properties": { + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "start_after": { + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "get_required_deposit_amount" + ], + "properties": { + "get_required_deposit_amount": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "get_deposit" + ], + "properties": { + "get_deposit": { + "type": "object", + "required": [ + "deposit_id" + ], + "properties": { + "deposit_id": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "get_deposits_paged" + ], + "properties": { + "get_deposits_paged": { + "type": "object", + "properties": { + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "start_after": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] +} diff --git a/contracts/ecash/schema/raw/response_to_get_blacklist_paged.json b/contracts/ecash/schema/raw/response_to_get_blacklist_paged.json new file mode 100644 index 0000000000..fd40f5ccef --- /dev/null +++ b/contracts/ecash/schema/raw/response_to_get_blacklist_paged.json @@ -0,0 +1,70 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "PagedBlacklistedAccountResponse", + "type": "object", + "required": [ + "accounts", + "per_page" + ], + "properties": { + "accounts": { + "type": "array", + "items": { + "$ref": "#/definitions/BlacklistedAccount" + } + }, + "per_page": { + "type": "integer", + "format": "uint", + "minimum": 0.0 + }, + "start_next_after": { + "description": "Field indicating paging information for the following queries if the caller wishes to get further entries.", + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false, + "definitions": { + "BlacklistedAccount": { + "type": "object", + "required": [ + "info", + "public_key" + ], + "properties": { + "info": { + "$ref": "#/definitions/Blacklisting" + }, + "public_key": { + "type": "string" + } + }, + "additionalProperties": false + }, + "Blacklisting": { + "type": "object", + "required": [ + "proposal_id" + ], + "properties": { + "finalized_at_height": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + }, + "proposal_id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + } +} diff --git a/contracts/ecash/schema/raw/response_to_get_blacklisted_account.json b/contracts/ecash/schema/raw/response_to_get_blacklisted_account.json new file mode 100644 index 0000000000..64873f42cd --- /dev/null +++ b/contracts/ecash/schema/raw/response_to_get_blacklisted_account.json @@ -0,0 +1,42 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "BlacklistedAccountResponse", + "type": "object", + "properties": { + "account": { + "anyOf": [ + { + "$ref": "#/definitions/Blacklisting" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false, + "definitions": { + "Blacklisting": { + "type": "object", + "required": [ + "proposal_id" + ], + "properties": { + "finalized_at_height": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + }, + "proposal_id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + } +} diff --git a/contracts/ecash/schema/raw/response_to_get_deposit.json b/contracts/ecash/schema/raw/response_to_get_deposit.json new file mode 100644 index 0000000000..a0f7ffbff8 --- /dev/null +++ b/contracts/ecash/schema/raw/response_to_get_deposit.json @@ -0,0 +1,40 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "DepositResponse", + "type": "object", + "required": [ + "id" + ], + "properties": { + "deposit": { + "anyOf": [ + { + "$ref": "#/definitions/Deposit" + }, + { + "type": "null" + } + ] + }, + "id": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false, + "definitions": { + "Deposit": { + "type": "object", + "required": [ + "bs58_encoded_ed25519_pubkey" + ], + "properties": { + "bs58_encoded_ed25519_pubkey": { + "type": "string" + } + }, + "additionalProperties": false + } + } +} diff --git a/contracts/ecash/schema/raw/response_to_get_deposits_paged.json b/contracts/ecash/schema/raw/response_to_get_deposits_paged.json new file mode 100644 index 0000000000..d93401a3ba --- /dev/null +++ b/contracts/ecash/schema/raw/response_to_get_deposits_paged.json @@ -0,0 +1,58 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "PagedDepositsResponse", + "type": "object", + "required": [ + "deposits" + ], + "properties": { + "deposits": { + "type": "array", + "items": { + "$ref": "#/definitions/DepositData" + } + }, + "start_next_after": { + "description": "Field indicating paging information for the following queries if the caller wishes to get further entries.", + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false, + "definitions": { + "Deposit": { + "type": "object", + "required": [ + "bs58_encoded_ed25519_pubkey" + ], + "properties": { + "bs58_encoded_ed25519_pubkey": { + "type": "string" + } + }, + "additionalProperties": false + }, + "DepositData": { + "type": "object", + "required": [ + "deposit", + "id" + ], + "properties": { + "deposit": { + "$ref": "#/definitions/Deposit" + }, + "id": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + } +} diff --git a/contracts/ecash/schema/raw/response_to_get_required_deposit_amount.json b/contracts/ecash/schema/raw/response_to_get_required_deposit_amount.json new file mode 100644 index 0000000000..6e18ef9a9b --- /dev/null +++ b/contracts/ecash/schema/raw/response_to_get_required_deposit_amount.json @@ -0,0 +1,23 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Coin", + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + }, + "definitions": { + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } +} diff --git a/contracts/ecash/src/bin/schema.rs b/contracts/ecash/src/bin/schema.rs new file mode 100644 index 0000000000..df9e0bffb5 --- /dev/null +++ b/contracts/ecash/src/bin/schema.rs @@ -0,0 +1,14 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use cosmwasm_schema::write_api; +use nym_ecash_contract_common::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg}; + +fn main() { + write_api! { + instantiate: InstantiateMsg, + query: QueryMsg, + execute: ExecuteMsg, + migrate: MigrateMsg, + } +} diff --git a/contracts/ecash/src/constants.rs b/contracts/ecash/src/constants.rs new file mode 100644 index 0000000000..ce5ff212fe --- /dev/null +++ b/contracts/ecash/src/constants.rs @@ -0,0 +1,5 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +pub const BLACKLIST_PROPOSAL_REPLY_ID: u64 = 7759; +pub const REDEMPTION_PROPOSAL_REPLY_ID: u64 = 2137; diff --git a/contracts/ecash/src/contract/helpers.rs b/contracts/ecash/src/contract/helpers.rs index 55f58d61f0..bab1a5472f 100644 --- a/contracts/ecash/src/contract/helpers.rs +++ b/contracts/ecash/src/contract/helpers.rs @@ -4,19 +4,33 @@ use crate::contract::NymEcashContract; use crate::helpers::{create_batch_redemption_proposal, create_blacklist_proposal, ProposalId}; use cosmwasm_schema::cw_serde; -use cosmwasm_std::{to_binary, Addr, Coin, Deps, SubMsg}; +use cosmwasm_std::{to_binary, Addr, Deps, Storage, SubMsg}; use cw3::ProposalResponse; use nym_ecash_contract_common::EcashContractError; use nym_multisig_contract_common::msg::QueryMsg as MultisigQueryMsg; +use nym_network_defaults::TICKETBOOK_SIZE; use sylvia::types::ExecCtx; #[cw_serde] pub(crate) struct Invariants { - pub(crate) ticket_book_value: Coin, - pub(crate) ticket_value: Coin, + pub(crate) ticket_book_size: u64, } impl NymEcashContract<'_> { + pub(crate) fn get_ticketbook_size( + &self, + storage: &dyn Storage, + ) -> Result { + let invariants = self.expected_invariants.load(storage)?; + if invariants.ticket_book_size != TICKETBOOK_SIZE { + return Err(EcashContractError::TicketBookSizeChanged { + at_init: invariants.ticket_book_size, + current: TICKETBOOK_SIZE, + }); + } + Ok(TICKETBOOK_SIZE) + } + fn must_get_multisig_addr(&self, deps: Deps) -> Result { // SAFETY: multisig admin MUST always be set on initialisation, // if the call fails, we're in some weird UB land @@ -45,6 +59,8 @@ impl NymEcashContract<'_> { .map_err(Into::into) } + // temporarily dead + #[allow(dead_code)] pub(crate) fn create_blacklist_proposal( &self, ctx: ExecCtx, diff --git a/contracts/ecash/src/contract/mod.rs b/contracts/ecash/src/contract/mod.rs index 66c5a60873..f394cf04d1 100644 --- a/contracts/ecash/src/contract/mod.rs +++ b/contracts/ecash/src/contract/mod.rs @@ -1,13 +1,16 @@ // Copyright 2024 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 +use crate::constants::{BLACKLIST_PROPOSAL_REPLY_ID, REDEMPTION_PROPOSAL_REPLY_ID}; use crate::contract::helpers::Invariants; use crate::deposit::DepositStorage; use crate::helpers::{ BlacklistKey, Config, MultisigReply, BLACKLIST_PAGE_DEFAULT_LIMIT, BLACKLIST_PAGE_MAX_LIMIT, CONTRACT_NAME, CONTRACT_VERSION, DEPOSITS_PAGE_DEFAULT_LIMIT, DEPOSITS_PAGE_MAX_LIMIT, }; -use cosmwasm_std::{BankMsg, Coin, Decimal, Event, Order, Reply, Response, StdResult, Uint128}; +use cosmwasm_std::{ + coin, BankMsg, Coin, Decimal, Event, Order, Reply, Response, StdResult, Uint128, +}; use cw4::Cw4Contract; use cw_controllers::Admin; use cw_storage_plus::{Bound, Item, Map}; @@ -15,12 +18,13 @@ use nym_contracts_common::set_build_information; use nym_ecash_contract_common::blacklist::{ BlacklistedAccount, BlacklistedAccountResponse, Blacklisting, PagedBlacklistedAccountResponse, }; +use nym_ecash_contract_common::counters::PoolCounters; use nym_ecash_contract_common::deposit::{DepositData, DepositResponse, PagedDepositsResponse}; use nym_ecash_contract_common::events::{ - BLACKLIST_PROPOSAL_REPLY_ID, DEPOSITED_FUNDS_EVENT_TYPE, DEPOSIT_ID, - PROPOSAL_ID_ATTRIBUTE_NAME, REDEMPTION_PROPOSAL_REPLY_ID, TICKET_BOOK_VALUE, TICKET_VALUE, + DEPOSITED_FUNDS_EVENT_TYPE, DEPOSIT_ID, PROPOSAL_ID_ATTRIBUTE_NAME, }; use nym_ecash_contract_common::EcashContractError; +use nym_network_defaults::TICKETBOOK_SIZE; use sylvia::types::{ExecCtx, InstantiateCtx, MigrateCtx, QueryCtx, ReplyCtx}; use sylvia::{contract, entry_points}; @@ -30,8 +34,10 @@ mod helpers; mod test; pub struct NymEcashContract<'a> { + pub(crate) contract_admin: Admin<'a>, pub(crate) multisig: Admin<'a>, pub(crate) config: Item<'a, Config>, + pub(crate) pool_counters: Item<'a, PoolCounters>, pub(crate) expected_invariants: Item<'a, Invariants>, pub(crate) blacklist: Map<'a, BlacklistKey, Blacklisting>, @@ -46,8 +52,10 @@ impl NymEcashContract<'_> { #[allow(clippy::new_without_default)] pub const fn new() -> Self { Self { + contract_admin: Admin::new("contract_admin"), multisig: Admin::new("multisig"), config: Item::new("config"), + pool_counters: Item::new("pool_counters"), expected_invariants: Item::new("expected_invariants"), blacklist: Map::new("blacklist"), deposits: DepositStorage::new(), @@ -61,7 +69,7 @@ impl NymEcashContract<'_> { holding_account: String, multisig_addr: String, group_addr: String, - mix_denom: String, + deposit_amount: Coin, ) -> Result { let multisig_addr = ctx.deps.api.addr_validate(&multisig_addr)?; let holding_account = ctx.deps.api.addr_validate(&holding_account)?; @@ -71,25 +79,37 @@ impl NymEcashContract<'_> { } })?); + // by default the sender becomes the admin + self.contract_admin + .set(ctx.deps.branch(), Some(ctx.info.sender))?; self.multisig .set(ctx.deps.branch(), Some(multisig_addr.clone()))?; self.expected_invariants.save( ctx.deps.storage, &Invariants { - ticket_book_value: Coin::new(TICKET_BOOK_VALUE, &mix_denom), - ticket_value: Coin::new(TICKET_VALUE, &mix_denom), + ticket_book_size: TICKETBOOK_SIZE, }, )?; - let cfg = Config { - group_addr, - mix_denom, - holding_account, + self.pool_counters.save( + ctx.deps.storage, + &PoolCounters { + total_deposited: coin(0, &deposit_amount.denom), + total_redeemed_gateways: coin(0, &deposit_amount.denom), + total_redeemed_holding: coin(0, &deposit_amount.denom), + }, + )?; - redemption_gateway_share: Decimal::percent(5), - }; - self.config.save(ctx.deps.storage, &cfg)?; + self.config.save( + ctx.deps.storage, + &Config { + group_addr, + holding_account, + redemption_gateway_share: Decimal::percent(5), + deposit_amount, + }, + )?; cw2::set_contract_version(ctx.deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; set_build_information!(ctx.deps.storage)?; @@ -142,21 +162,10 @@ impl NymEcashContract<'_> { } #[msg(query)] - pub fn get_required_deposit_amount(&self, ctx: QueryCtx) -> Result { - let mix_denom = self.config.load(ctx.deps.storage)?.mix_denom; - let expected_deposit = self - .expected_invariants - .load(ctx.deps.storage)? - .ticket_book_value; - let current = Coin::new(TICKET_BOOK_VALUE, mix_denom); - if expected_deposit != current { - return Err(EcashContractError::DepositAmountChanged { - at_init: expected_deposit, - current, - }); - } + pub fn get_required_deposit_amount(&self, ctx: QueryCtx) -> StdResult { + let deposit_amount = self.config.load(ctx.deps.storage)?.deposit_amount; - Ok(current) + Ok(deposit_amount) } #[msg(query)] @@ -209,28 +218,26 @@ impl NymEcashContract<'_> { ctx: ExecCtx, identity_key: String, ) -> Result { - let mix_denom = self.config.load(ctx.deps.storage)?.mix_denom; - let voucher_value = cw_utils::must_pay(&ctx.info, &mix_denom)?; - let amount = voucher_value.u128(); + let required_deposit = self.config.load(ctx.deps.storage)?.deposit_amount; - let expected_deposit = self - .expected_invariants - .load(ctx.deps.storage)? - .ticket_book_value; - if expected_deposit.amount.u128() != TICKET_BOOK_VALUE { - return Err(EcashContractError::DepositAmountChanged { - at_init: expected_deposit, - current: Coin::new(TICKET_BOOK_VALUE, mix_denom), - }); - } + let submitted = cw_utils::must_pay(&ctx.info, &required_deposit.denom)?; - if amount != TICKET_BOOK_VALUE { + if submitted != required_deposit.amount { + let mut funds = ctx.info.funds; return Err(EcashContractError::WrongAmount { - received: amount, - amount: TICKET_BOOK_VALUE, + // SAFETY: the call to `must_pay` ensured a single coin has been sent + #[allow(clippy::unwrap_used)] + received: funds.pop().unwrap(), + amount: required_deposit, }); } + self.pool_counters + .update(ctx.deps.storage, |mut counters| -> StdResult<_> { + counters.total_deposited.amount += submitted; + Ok(counters) + })?; + let deposit_id = self.deposits.save_deposit(ctx.deps.storage, identity_key)?; Ok(Response::new() @@ -272,66 +279,103 @@ impl NymEcashContract<'_> { .assert_admin(ctx.deps.as_ref(), &ctx.info.sender)?; let config = self.config.load(ctx.deps.storage)?; - let denom = &config.mix_denom; - - let expected_ticket = self - .expected_invariants - .load(ctx.deps.storage)? - .ticket_value; - let current = Coin::new(TICKET_VALUE, denom); - if expected_ticket != current { - return Err(EcashContractError::TicketValueChanged { - at_init: expected_ticket, - current, - }); - } // TODO: we need unit tests for this - let return_amount = Uint128::new(TICKET_VALUE * n as u128); + let deposit_amount = config.deposit_amount.amount; + let ticketbook_size = Uint128::new(self.get_ticketbook_size(ctx.deps.storage)? as u128); + let tickets = Uint128::new(n as u128); + + // how many tickets from a ticketbook you redeemed + let book_ratio = Decimal::from_ratio(tickets, ticketbook_size); + let return_amount = book_ratio * deposit_amount; + let gw_share = config.redemption_gateway_share * return_amount; let holding_share = return_amount - gw_share; + self.pool_counters + .update(ctx.deps.storage, |mut counters| -> StdResult<_> { + counters.total_redeemed_gateways.amount += gw_share; + counters.total_redeemed_holding.amount += holding_share; + Ok(counters) + })?; + Ok(Response::new() .add_message(BankMsg::Send { to_address: gw, amount: vec![Coin { - denom: denom.to_owned(), + denom: config.deposit_amount.denom.clone(), amount: gw_share, }], }) .add_message(BankMsg::Send { to_address: config.holding_account.to_string(), amount: vec![Coin { - denom: denom.to_owned(), + denom: config.deposit_amount.denom, amount: holding_share, }], })) } + #[msg(exec)] + pub fn update_admin( + &self, + ctx: ExecCtx, + admin: String, + ) -> Result { + let new_admin = ctx.deps.api.addr_validate(&admin)?; + + // note: the below performs validation to ensure the sender IS the current admin + Ok(self + .contract_admin + .execute_update_admin(ctx.deps, ctx.info, Some(new_admin))?) + } + + #[msg(exec)] + pub fn update_deposit_value( + &self, + ctx: ExecCtx, + new_deposit: Coin, + ) -> Result { + // only current admin can do that + self.contract_admin + .assert_admin(ctx.deps.as_ref(), &ctx.info.sender)?; + + let deposit_str = new_deposit.to_string(); + self.config + .update(ctx.deps.storage, |mut cfg| -> StdResult<_> { + cfg.deposit_amount = new_deposit; + Ok(cfg) + })?; + Ok(Response::new().add_attribute("updated_deposit", deposit_str)) + } + #[msg(exec)] pub fn propose_to_blacklist( &self, ctx: ExecCtx, public_key: String, ) -> Result { - let cfg = self.config.load(ctx.deps.storage)?; - cfg.group_addr - .is_voting_member(&ctx.deps.querier, &ctx.info.sender, ctx.env.block.height)? - .ok_or(EcashContractError::Unauthorized)?; - - if let Some(blacklisted) = self - .blacklist - .may_load(ctx.deps.storage, public_key.clone())? - { - // return existing proposal id - Ok(Response::new().add_attribute( - PROPOSAL_ID_ATTRIBUTE_NAME, - blacklisted.proposal_id.to_string(), - )) - } else { - let msg = self.create_blacklist_proposal(ctx, public_key)?; - Ok(Response::new().add_submessage(msg)) - } + let _ = ctx; + let _ = public_key; + Err(EcashContractError::UnimplementedBlacklisting) + // let cfg = self.config.load(ctx.deps.storage)?; + // cfg.group_addr + // .is_voting_member(&ctx.deps.querier, &ctx.info.sender, ctx.env.block.height)? + // .ok_or(EcashContractError::Unauthorized)?; + // + // if let Some(blacklisted) = self + // .blacklist + // .may_load(ctx.deps.storage, public_key.clone())? + // { + // // return existing proposal id + // Ok(Response::new().add_attribute( + // PROPOSAL_ID_ATTRIBUTE_NAME, + // blacklisted.proposal_id.to_string(), + // )) + // } else { + // let msg = self.create_blacklist_proposal(ctx, public_key)?; + // Ok(Response::new().add_submessage(msg)) + // } } #[msg(exec)] @@ -340,16 +384,19 @@ impl NymEcashContract<'_> { ctx: ExecCtx, public_key: String, ) -> Result { - //Only by multisig contract, actually add public key to blacklist - self.multisig - .assert_admin(ctx.deps.as_ref(), &ctx.info.sender)?; - - let mut blacklisting = self.blacklist.load(ctx.deps.storage, public_key.clone())?; - blacklisting.finalized_at_height = Some(ctx.env.block.height); - self.blacklist - .save(ctx.deps.storage, public_key.clone(), &blacklisting)?; - - Ok(Response::new()) + let _ = ctx; + let _ = public_key; + Err(EcashContractError::UnimplementedBlacklisting) + // //Only by multisig contract, actually add public key to blacklist + // self.multisig + // .assert_admin(ctx.deps.as_ref(), &ctx.info.sender)?; + // + // let mut blacklisting = self.blacklist.load(ctx.deps.storage, public_key.clone())?; + // blacklisting.finalized_at_height = Some(ctx.env.block.height); + // self.blacklist + // .save(ctx.deps.storage, public_key.clone(), &blacklisting)?; + // + // Ok(Response::new()) } /*===================== diff --git a/contracts/ecash/src/helpers.rs b/contracts/ecash/src/helpers.rs index 8a1b298dd5..650dc82f5c 100644 --- a/contracts/ecash/src/helpers.rs +++ b/contracts/ecash/src/helpers.rs @@ -1,18 +1,16 @@ // Copyright 2024 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 +use crate::constants::{BLACKLIST_PROPOSAL_REPLY_ID, REDEMPTION_PROPOSAL_REPLY_ID}; use cosmwasm_std::{ - to_binary, Addr, CosmosMsg, Decimal, Reply, StdError, StdResult, SubMsg, SubMsgResult, WasmMsg, + to_binary, Addr, Coin, CosmosMsg, Decimal, Reply, StdError, StdResult, SubMsg, SubMsgResult, + WasmMsg, }; use cw4::Cw4Contract; use nym_contracts_common::events::try_find_attribute; -use nym_ecash_contract_common::events::{ - PROPOSAL_ID_ATTRIBUTE_NAME, REDEMPTION_PROPOSAL_REPLY_ID, WASM_EVENT_NAME, -}; +use nym_ecash_contract_common::events::{PROPOSAL_ID_ATTRIBUTE_NAME, WASM_EVENT_NAME}; use nym_ecash_contract_common::redeem_credential::BATCH_REDEMPTION_PROPOSAL_TITLE; -use nym_ecash_contract_common::{ - events::BLACKLIST_PROPOSAL_REPLY_ID, msg::ExecuteMsg, EcashContractError, -}; +use nym_ecash_contract_common::{msg::ExecuteMsg, EcashContractError}; use nym_multisig_contract_common::msg::ExecuteMsg as MultisigExecuteMsg; use serde::{Deserialize, Serialize}; @@ -23,10 +21,10 @@ pub(crate) const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); #[derive(Serialize, Deserialize, Clone, Debug)] pub struct Config { pub group_addr: Cw4Contract, - pub mix_denom: String, pub holding_account: Addr, pub redemption_gateway_share: Decimal, + pub deposit_amount: Coin, } //type aliases for easier reasoning diff --git a/contracts/ecash/src/lib.rs b/contracts/ecash/src/lib.rs index e71f86f8cc..a66566e247 100644 --- a/contracts/ecash/src/lib.rs +++ b/contracts/ecash/src/lib.rs @@ -6,6 +6,7 @@ #![warn(clippy::todo)] #![warn(clippy::dbg_macro)] +mod constants; pub mod contract; mod deposit; mod helpers; diff --git a/contracts/name-service/Cargo.toml b/contracts/name-service/Cargo.toml deleted file mode 100644 index 5e60860761..0000000000 --- a/contracts/name-service/Cargo.toml +++ /dev/null @@ -1,36 +0,0 @@ -[package] -name = "nym-name-service" -version = "0.1.0" -edition = "2021" - -[[bin]] -name = "schema" -required-features = ["schema-gen"] - -[lib] -crate-type = ["cdylib", "rlib"] - -[dependencies] -bs58 = { workspace = true } -cosmwasm-schema = { workspace = true, optional = true } -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.5.0" } -nym-name-service-common = { path = "../../common/cosmwasm-smart-contracts/name-service" } -serde = { version = "1.0.155", default-features = false, features = ["derive"] } -thiserror = { workspace = true } - -[dev-dependencies] -anyhow = "1.0.40" -cw-multi-test = { workspace = true } -nym-crypto = { path = "../../common/crypto", features = ["asymmetric", "rand"] } -nym-sphinx-addressing = { path = "../../common/nymsphinx/addressing" } -rand = "0.8.5" -rand_chacha = "0.3" -rstest = "0.17.0" - -[features] -schema-gen = ["nym-name-service-common/schema", "cosmwasm-schema"] diff --git a/contracts/service-provider-directory/Cargo.toml b/contracts/service-provider-directory/Cargo.toml deleted file mode 100644 index 95c001f85b..0000000000 --- a/contracts/service-provider-directory/Cargo.toml +++ /dev/null @@ -1,34 +0,0 @@ -[package] -name = "nym-service-provider-directory" -version = "0.1.0" -edition = "2021" - -[[bin]] -name = "schema" -required-features = ["schema-gen"] - -[lib] -crate-type = ["cdylib", "rlib"] - -[dependencies] -bs58 = { workspace = true } -cosmwasm-schema = { workspace = true, optional = true } -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.5.0" } -nym-service-provider-directory-common = { path = "../../common/cosmwasm-smart-contracts/service-provider-directory" } -serde = { version = "1.0.155", default-features = false, features = ["derive"] } -thiserror = { workspace = true } - -[dev-dependencies] -anyhow = "1.0.40" -cw-multi-test = { workspace = true } -nym-crypto = { path = "../../common/crypto", features = ["asymmetric", "rand"] } -rand_chacha = "0.3" -rstest = "0.17.0" - -[features] -schema-gen = ["nym-service-provider-directory-common/schema", "cosmwasm-schema"] diff --git a/tools/internal/testnet-manager/src/manager/local_client.rs b/tools/internal/testnet-manager/src/manager/local_client.rs index 8e6a91805d..1a199f410f 100644 --- a/tools/internal/testnet-manager/src/manager/local_client.rs +++ b/tools/internal/testnet-manager/src/manager/local_client.rs @@ -220,7 +220,7 @@ impl NetworkManager { .kill_on_drop(true); if let Some(gateway) = &ctx.gateway { - cmd.args(["--gateway", &gateway]); + cmd.args(["--gateway", gateway]); } let mut child = cmd.spawn()?; diff --git a/tools/internal/testnet-manager/src/manager/network_init.rs b/tools/internal/testnet-manager/src/manager/network_init.rs index 24c3829ee2..a8d97fb658 100644 --- a/tools/internal/testnet-manager/src/manager/network_init.rs +++ b/tools/internal/testnet-manager/src/manager/network_init.rs @@ -199,7 +199,7 @@ impl NetworkManager { .to_string(), multisig_addr: ctx.network.contracts.cw3_multisig.address()?.to_string(), group_addr: ctx.network.contracts.cw4_group.address()?.to_string(), - mix_denom: ctx.admin.mix_coin(0).denom, + deposit_amount: ctx.admin.mix_coin(75_000_000).into(), }) }