From 13f804f9c03d6a6e5d748d7adbb2eb308fb96e55 Mon Sep 17 00:00:00 2001 From: Kerber0x Date: Thu, 20 Jun 2024 15:08:58 +0100 Subject: [PATCH 1/2] feat: make adding fuel permissionless --- smart-contracts/Cargo.toml | 24 +++++------ smart-contracts/src/contract.rs | 75 ++++++++++++++++++++++++--------- smart-contracts/src/error.rs | 19 +++++++++ smart-contracts/src/execute.rs | 29 +++++++------ smart-contracts/src/helpers.rs | 9 +++- smart-contracts/src/msg.rs | 27 ++++++++---- smart-contracts/src/state.rs | 8 ++++ 7 files changed, 134 insertions(+), 57 deletions(-) diff --git a/smart-contracts/Cargo.toml b/smart-contracts/Cargo.toml index f5c7276..e0c18f4 100644 --- a/smart-contracts/Cargo.toml +++ b/smart-contracts/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "furnace" -version = "0.2.0" +version = "0.2.1" authors = ["Paul Stelzig ", "Nahem "] edition = "2021" @@ -18,22 +18,22 @@ crate-type = ["cdylib", "rlib"] backtraces = ["cosmwasm-std/backtraces"] [dependencies] -cosmwasm-schema = "2.0.0" -cosmwasm-std = { version = "1.1.3", features = ["stargate"] } -cosmwasm-storage = "1.1.3" -cw-storage-plus = "1.0.1" -cw2 = "1.0.1" -schemars = "0.8.10" +cosmwasm-schema = { version = "1.5.5" } +cosmwasm-std = { version = "1.5.5", features = ["stargate"] } +cw-storage-plus = { version = "1.2.0" } +cw2 = { version = "1.1.2" } +schemars = { version = "0.8.21" } serde = { version = "1.0.145", default-features = false, features = ["derive"] } thiserror = { version = "1.0.31" } -osmosis-std-derive = "0.15.3" +osmosis-std-derive = { version = "0.15.3" } prost = { version = "0.11.0", default-features = false, features = [ "prost-derive", ] } prost-types = { version = "0.11.1", default-features = false } -semver = "1.0.12" -cw20 = "1.0.1" -cw-utils = "1.0.1" +semver = { version = "1.0.12" } +cw20 = { version = "1.1.2" } +cw-utils = { version = "1.0.3" } +white-whale-std = { version = "1.1.5" } [dev-dependencies] -cw-multi-test = "0.20.0" +cw-multi-test = { version = "0.20.1" } diff --git a/smart-contracts/src/contract.rs b/smart-contracts/src/contract.rs index 4fb5202..9cba0a6 100644 --- a/smart-contracts/src/contract.rs +++ b/smart-contracts/src/contract.rs @@ -2,13 +2,20 @@ use crate::error::ContractError; use crate::execute::{ execute_add_fuel, execute_burn, execute_update_config, execute_update_fuel_config, }; +use crate::helpers::validate_fee_rate; use crate::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg}; use crate::query::{query_config, query_fuels, query_leaderboard}; use crate::state::{Config, CONFIG}; +use cosmwasm_schema::cw_serde; #[cfg(not(feature = "library"))] use cosmwasm_std::entry_point; -use cosmwasm_std::{to_json_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response}; -use cw2::set_contract_version; +use cosmwasm_std::{ + to_json_binary, Addr, Binary, Deps, DepsMut, Env, MessageInfo, Response, Uint128, +}; +use cw2::{get_contract_version, set_contract_version}; +use cw_storage_plus::Item; +use semver::Version; +use white_whale_std::migrate_guards::check_contract_name; // version info for migration info const CONTRACT_NAME: &str = "furnace"; @@ -30,6 +37,10 @@ pub fn instantiate( owner: deps.api.addr_validate(&msg.owner)?, mint_cost: msg.mint_cost, native_denom: msg.native_denom.to_string(), + default_fuel_fee_recipient: deps.api.addr_validate(&msg.default_fuel_fee_recipient)?, + default_fuel_fee_rate: validate_fee_rate(msg.default_fuel_fee_rate)?, + default_ash_fee_recipient: deps.api.addr_validate(&msg.default_ash_fee_recipient)?, + default_ash_fee_rate: validate_fee_rate(msg.default_ash_fee_rate)?, }; CONFIG.save(deps.storage, &config)?; @@ -48,24 +59,9 @@ pub fn execute( msg: ExecuteMsg, ) -> Result { match msg { - ExecuteMsg::AddFuel { - subdenom, - denom, - fuel_fee_recipient, - fuel_fee_rate, - ash_fee_recipient, - ash_fee_rate, - } => execute_add_fuel( - deps, - env, - info, - subdenom, - denom, - fuel_fee_recipient, - fuel_fee_rate, - ash_fee_recipient, - ash_fee_rate, - ), + ExecuteMsg::AddFuel { subdenom, denom } => { + execute_add_fuel(deps, env, info, subdenom, denom) + } ExecuteMsg::UpdateConfig { owner, mint_cost } => { execute_update_config(deps, info, owner, mint_cost) } @@ -117,6 +113,43 @@ pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> Result Result { +pub fn migrate(deps: DepsMut, _env: Env, msg: MigrateMsg) -> Result { + check_contract_name(deps.storage, CONTRACT_NAME.to_string())?; + let version: Version = CONTRACT_VERSION.parse()?; + let storage_version: Version = get_contract_version(deps.storage)?.version.parse()?; + + if storage_version >= version { + return Err(ContractError::MigrateInvalidVersion { + current_version: storage_version, + new_version: version, + }); + } + + if storage_version < Version::parse("0.2.1")? { + #[cw_serde] + struct OldConfig { + pub owner: Addr, + pub mint_cost: Uint128, + pub native_denom: String, + } + + const OLD_CONFIG: Item = Item::new("config"); + let old_config: OldConfig = OLD_CONFIG.load(deps.storage)?; + + let config = Config { + owner: old_config.owner, + mint_cost: old_config.mint_cost, + native_denom: old_config.native_denom, + default_fuel_fee_recipient: deps.api.addr_validate(&msg.default_fuel_fee_recipient)?, + default_fuel_fee_rate: validate_fee_rate(msg.default_fuel_fee_rate)?, + default_ash_fee_recipient: deps.api.addr_validate(&msg.default_ash_fee_recipient)?, + default_ash_fee_rate: validate_fee_rate(msg.default_ash_fee_rate)?, + }; + + CONFIG.save(deps.storage, &config)?; + } + + set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; + Ok(Response::default()) } diff --git a/smart-contracts/src/error.rs b/smart-contracts/src/error.rs index 30cfe36..3800524 100644 --- a/smart-contracts/src/error.rs +++ b/smart-contracts/src/error.rs @@ -1,5 +1,6 @@ use cosmwasm_std::{OverflowError, StdError, Uint128}; use cw_utils::PaymentError; +use semver::Version; use thiserror::Error; use crate::contract::MIN_AMOUNT_TO_BURN; @@ -39,4 +40,22 @@ pub enum ContractError { #[error("Insufficient funds")] InsufficientFunds {}, + + #[error("Attempt to migrate to version {new_version}, but contract is on a higher version {current_version}")] + MigrateInvalidVersion { + new_version: Version, + current_version: Version, + }, + + #[error("Semver parsing error: {0}")] + SemVer(String), + + #[error("Can't burn IBC tokens. Please add the fuel token on the native chain")] + InvalidIBCFuel {}, +} + +impl From for ContractError { + fn from(err: semver::Error) -> Self { + Self::SemVer(err.to_string()) + } } diff --git a/smart-contracts/src/execute.rs b/smart-contracts/src/execute.rs index efe48ff..b4ea4d9 100644 --- a/smart-contracts/src/execute.rs +++ b/smart-contracts/src/execute.rs @@ -43,10 +43,6 @@ pub fn execute_add_fuel( info: MessageInfo, subdenom: String, denom: String, - fuel_fee_recipient: String, - fuel_fee_rate: Decimal, - ash_fee_recipient: String, - ash_fee_rate: Decimal, ) -> Result { let config = CONFIG.load(deps.storage)?; @@ -59,7 +55,6 @@ pub fn execute_add_fuel( nonpayable(&info)?; } - assert_owner(deps.as_ref(), &info.sender)?; validate_subdenom(&subdenom)?; FUEL_CONFIG.update(deps.storage, denom.clone(), |old| match old { @@ -67,10 +62,10 @@ pub fn execute_add_fuel( None => Ok(FuelConfig { subdenom: subdenom.clone(), denom: denom.clone(), - fuel_fee_recipient: deps.api.addr_validate(&fuel_fee_recipient)?, - fuel_fee_rate: validate_fee_rate(fuel_fee_rate)?, - ash_fee_recipient: deps.api.addr_validate(&ash_fee_recipient)?, - ash_fee_rate: validate_fee_rate(ash_fee_rate)?, + fuel_fee_recipient: config.default_fuel_fee_recipient.clone(), + fuel_fee_rate: config.default_fuel_fee_rate.clone(), + ash_fee_recipient: config.default_ash_fee_recipient.clone(), + ash_fee_rate: config.default_ash_fee_rate.clone(), }), })?; @@ -84,13 +79,19 @@ pub fn execute_add_fuel( Ok(Response::default() .add_messages(messages) - .add_attribute("action", "add_fuel") + .add_attribute("action", "add_fuel".to_string()) .add_attribute("subdenom", subdenom) .add_attribute("denom", denom) - .add_attribute("fuel_fee_recipient", fuel_fee_recipient) - .add_attribute("fuel_fee_rate", fuel_fee_rate.to_string()) - .add_attribute("ash_fee_recipient", ash_fee_recipient) - .add_attribute("ash_fee_rate", ash_fee_rate.to_string())) + .add_attribute( + "fuel_fee_recipient", + config.default_fuel_fee_recipient.to_string(), + ) + .add_attribute("fuel_fee_rate", config.default_fuel_fee_rate.to_string()) + .add_attribute( + "ash_fee_recipient", + config.default_ash_fee_recipient.to_string(), + ) + .add_attribute("ash_fee_rate", config.default_ash_fee_rate.to_string())) } pub fn execute_update_fuel_config( diff --git a/smart-contracts/src/helpers.rs b/smart-contracts/src/helpers.rs index 45d9f75..1baa99c 100644 --- a/smart-contracts/src/helpers.rs +++ b/smart-contracts/src/helpers.rs @@ -1,6 +1,7 @@ use crate::error::ContractError; use crate::state::CONFIG; -use cosmwasm_std::{Addr, Decimal, Deps}; +use cosmwasm_std::{ensure, Addr, Decimal, Deps}; +use white_whale_std::pool_network::asset::is_ibc_token; /// This prevents the subdenom to be empty as all assets have a subdenom. pub const MIN_SUBDENOM_LENGTH: usize = 1; @@ -33,6 +34,8 @@ pub fn assert_owner(deps: Deps, caller: &Addr) -> Result<(), ContractError> { /// as per Osmosis token factory docs `Subdenoms can contain [a-zA-Z0-9./].`, and /// returns an error if invalid. pub fn validate_subdenom(subdenom: &str) -> Result<(), ContractError> { + ensure!(!is_ibc_token(&subdenom), ContractError::InvalidIBCFuel {}); + if subdenom.len() > MAX_SUBDENOM_LENGTH || subdenom.len() < MIN_SUBDENOM_LENGTH || !subdenom @@ -66,6 +69,10 @@ pub fn calc_range_start(start_after: Option) -> Option> { #[test] fn test_validate_subdenom() { + let _valid_subdenom_1 = validate_subdenom( + &"ibc/FA7112322CE7656DC84D441E49BAEAB9DC0AB3C7618A178A212CDE8B3F17C70B".to_string(), + ) + .unwrap_err(); let _valid_subdenom_1 = validate_subdenom(&"uwhale".to_string()).unwrap(); let _valid_subdenom_2 = validate_subdenom(&"....///".to_string()).unwrap(); let _valid_subdenom_3 = diff --git a/smart-contracts/src/msg.rs b/smart-contracts/src/msg.rs index 8a20d2f..c885510 100644 --- a/smart-contracts/src/msg.rs +++ b/smart-contracts/src/msg.rs @@ -10,6 +10,14 @@ pub struct InstantiateMsg { pub mint_cost: Uint128, /// Native denom pub native_denom: String, + /// The default address that will receive part of the burned token as fees. + pub default_fuel_fee_recipient: String, + /// The default fee rate that will be charged for burning the fuel token. + pub default_fuel_fee_rate: Decimal, + /// The default address that will receive the newly minted ash token fees. + pub default_ash_fee_recipient: String, + /// The default fee rate that will be charged for minting the ash token. + pub default_ash_fee_rate: Decimal, } #[cw_serde] @@ -31,14 +39,6 @@ pub enum ExecuteMsg { subdenom: String, /// The denom that will be accepted as fuel. denom: String, - /// The address that will receive part of the burned token as fees. - fuel_fee_recipient: String, - /// The fee rate that will be charged for burning the fuel token. - fuel_fee_rate: Decimal, - /// The address that will receive the newly minted ash token fees. - ash_fee_recipient: String, - /// The fee rate that will be charged for minting the ash token. - ash_fee_rate: Decimal, }, /// Updates the fuel config. Only the owner can update the fuel config. /// If a field is not sent in the message, it will not be updated. @@ -99,6 +99,15 @@ pub struct LeaderboardResponse { #[cw_serde] /// To be used in the future for migrating the contract. -pub struct MigrateMsg {} +pub struct MigrateMsg { + /// The default address that will receive part of the burned token as fees. + pub default_fuel_fee_recipient: String, + /// The default fee rate that will be charged for burning the fuel token. + pub default_fuel_fee_rate: Decimal, + /// The default address that will receive the newly minted ash token fees. + pub default_ash_fee_recipient: String, + /// The default fee rate that will be charged for minting the ash token. + pub default_ash_fee_rate: Decimal, +} pub type ConfigResponse = Config; diff --git a/smart-contracts/src/state.rs b/smart-contracts/src/state.rs index 803ac02..05ab1be 100644 --- a/smart-contracts/src/state.rs +++ b/smart-contracts/src/state.rs @@ -7,6 +7,14 @@ pub struct Config { pub owner: Addr, pub mint_cost: Uint128, pub native_denom: String, + /// The default address that will receive part of the burned token as fees. + pub default_fuel_fee_recipient: Addr, + /// The default fee rate that will be charged for burning the fuel token. + pub default_fuel_fee_rate: Decimal, + /// The default address that will receive the newly minted ash token fees. + pub default_ash_fee_recipient: Addr, + /// The default fee rate that will be charged for minting the ash token. + pub default_ash_fee_rate: Decimal, } #[cw_serde] From 4e3c38fc052c30dc29c7fb7a405295b81b50a6cc Mon Sep 17 00:00:00 2001 From: Kerber0x Date: Thu, 20 Jun 2024 15:46:07 +0100 Subject: [PATCH 2/2] test: fix tests --- Cargo.lock | 106 +++++++++++------- smart-contracts/src/execute.rs | 4 +- smart-contracts/src/helpers.rs | 5 +- smart-contracts/src/msg.rs | 4 +- smart-contracts/src/tests/helpers.rs | 6 +- smart-contracts/src/tests/test.rs | 155 ++++++++++++++------------- 6 files changed, 154 insertions(+), 126 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b0fe7e3..8e2decb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -120,20 +120,7 @@ version = "1.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7879036156092ad1c22fe0d7316efc5a5eceec2bc3906462a2560215f2a2f929" dependencies = [ - "cosmwasm-schema-derive 1.5.5", - "schemars", - "serde", - "serde_json", - "thiserror", -] - -[[package]] -name = "cosmwasm-schema" -version = "2.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "101d0739564bd34cba9b84bf73665f0822487ae3b29b2dd59930608ed3aafd43" -dependencies = [ - "cosmwasm-schema-derive 2.0.4", + "cosmwasm-schema-derive", "schemars", "serde", "serde_json", @@ -151,17 +138,6 @@ dependencies = [ "syn 1.0.109", ] -[[package]] -name = "cosmwasm-schema-derive" -version = "2.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf4be75f60158478da2c5d319ed59295bca1687ad50c18215a0485aa91a995ea" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - [[package]] name = "cosmwasm-std" version = "1.5.5" @@ -184,16 +160,6 @@ dependencies = [ "thiserror", ] -[[package]] -name = "cosmwasm-storage" -version = "1.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66de2ab9db04757bcedef2b5984fbe536903ada4a8a9766717a4a71197ef34f6" -dependencies = [ - "cosmwasm-std", - "serde", -] - [[package]] name = "cpufeatures" version = "0.2.12" @@ -203,6 +169,12 @@ dependencies = [ "libc", ] +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + [[package]] name = "crypto-bigint" version = "0.5.5" @@ -275,7 +247,7 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c4a657e5caacc3a0d00ee96ca8618745d050b8f757c709babafb81208d4239c" dependencies = [ - "cosmwasm-schema 1.5.5", + "cosmwasm-schema", "cosmwasm-std", "cw2", "schemars", @@ -290,7 +262,7 @@ version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c6c120b24fbbf5c3bedebb97f2cc85fbfa1c3287e09223428e7e597b5293c1fa" dependencies = [ - "cosmwasm-schema 1.5.5", + "cosmwasm-schema", "cosmwasm-std", "cw-storage-plus", "schemars", @@ -305,7 +277,7 @@ version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "526e39bb20534e25a1cd0386727f0038f4da294e5e535729ba3ef54055246abd" dependencies = [ - "cosmwasm-schema 1.5.5", + "cosmwasm-schema", "cosmwasm-std", "cw-utils", "schemars", @@ -432,11 +404,10 @@ checksum = "c8cbd1169bd7b4a0a20d92b9af7a7e0422888bd38a6f5ec29c1fd8c1558a272e" [[package]] name = "furnace" -version = "0.2.0" +version = "0.2.1" dependencies = [ - "cosmwasm-schema 2.0.4", + "cosmwasm-schema", "cosmwasm-std", - "cosmwasm-storage", "cw-multi-test", "cw-storage-plus", "cw-utils", @@ -449,6 +420,7 @@ dependencies = [ "semver", "serde", "thiserror", + "white-whale-std", ] [[package]] @@ -650,6 +622,27 @@ dependencies = [ "prost 0.11.9", ] +[[package]] +name = "protobuf" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b55bad9126f378a853655831eb7363b7b01b81d19f8cb1218861086ca4a1a61e" +dependencies = [ + "bytes", + "once_cell", + "protobuf-support", + "thiserror", +] + +[[package]] +name = "protobuf-support" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5d4d7b8601c814cfb36bcebb79f0e61e45e1e93640cf778837833bbed05c372" +dependencies = [ + "thiserror", +] + [[package]] name = "quote" version = "1.0.36" @@ -889,6 +882,18 @@ version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" +[[package]] +name = "uint" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76f64bba2c53b04fcab63c01a7d7427eadc821e3bc48c34dc9ba29c501164b52" +dependencies = [ + "byteorder", + "crunchy", + "hex", + "static_assertions", +] + [[package]] name = "unicode-ident" version = "1.0.12" @@ -907,6 +912,25 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "white-whale-std" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3015d5c0c46b6e1cf2fb02c036e8d1481174508a32572d62e6f240a4dec4077" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw2", + "cw20", + "osmosis-std-derive", + "prost 0.11.9", + "prost-types", + "protobuf", + "schemars", + "serde", + "uint", +] + [[package]] name = "zeroize" version = "1.8.1" diff --git a/smart-contracts/src/execute.rs b/smart-contracts/src/execute.rs index b4ea4d9..4eff3c1 100644 --- a/smart-contracts/src/execute.rs +++ b/smart-contracts/src/execute.rs @@ -6,9 +6,10 @@ use crate::{ state::{Config, FuelConfig, CONFIG, FUEL_CONFIG, LEADERBOARD}, }; use cosmwasm_std::{ - coins, BankMsg, CosmosMsg, Decimal, DepsMut, Env, MessageInfo, Response, Uint128, + coins, ensure, BankMsg, CosmosMsg, Decimal, DepsMut, Env, MessageInfo, Response, Uint128, }; use cw_utils::{must_pay, nonpayable, one_coin}; +use white_whale_std::pool_network::asset::is_ibc_token; pub fn execute_update_config( deps: DepsMut, @@ -55,6 +56,7 @@ pub fn execute_add_fuel( nonpayable(&info)?; } + ensure!(!is_ibc_token(&denom), ContractError::InvalidIBCFuel {}); validate_subdenom(&subdenom)?; FUEL_CONFIG.update(deps.storage, denom.clone(), |old| match old { diff --git a/smart-contracts/src/helpers.rs b/smart-contracts/src/helpers.rs index 1baa99c..302fbc4 100644 --- a/smart-contracts/src/helpers.rs +++ b/smart-contracts/src/helpers.rs @@ -1,7 +1,6 @@ use crate::error::ContractError; use crate::state::CONFIG; -use cosmwasm_std::{ensure, Addr, Decimal, Deps}; -use white_whale_std::pool_network::asset::is_ibc_token; +use cosmwasm_std::{Addr, Decimal, Deps}; /// This prevents the subdenom to be empty as all assets have a subdenom. pub const MIN_SUBDENOM_LENGTH: usize = 1; @@ -34,8 +33,6 @@ pub fn assert_owner(deps: Deps, caller: &Addr) -> Result<(), ContractError> { /// as per Osmosis token factory docs `Subdenoms can contain [a-zA-Z0-9./].`, and /// returns an error if invalid. pub fn validate_subdenom(subdenom: &str) -> Result<(), ContractError> { - ensure!(!is_ibc_token(&subdenom), ContractError::InvalidIBCFuel {}); - if subdenom.len() > MAX_SUBDENOM_LENGTH || subdenom.len() < MIN_SUBDENOM_LENGTH || !subdenom diff --git a/smart-contracts/src/msg.rs b/smart-contracts/src/msg.rs index c885510..3484a8f 100644 --- a/smart-contracts/src/msg.rs +++ b/smart-contracts/src/msg.rs @@ -35,9 +35,9 @@ pub enum ExecuteMsg { /// Adds a new denom to the contract with the given fuel and ash fee rates, and fee recipients. /// The contract will then be able to accept this denom as fuel. Only the owner can add a new fuel. AddFuel { - /// Human readable name of the fuel. i.e. `uwhale` + /// Human readable name of the fuel, used to create the ash token. i.e. `uwhale` subdenom: String, - /// The denom that will be accepted as fuel. + /// The denom that will be accepted as fuel, i.e. the token that will be burned. denom: String, }, /// Updates the fuel config. Only the owner can update the fuel config. diff --git a/smart-contracts/src/tests/helpers.rs b/smart-contracts/src/tests/helpers.rs index f2aad01..cc07c92 100644 --- a/smart-contracts/src/tests/helpers.rs +++ b/smart-contracts/src/tests/helpers.rs @@ -1,7 +1,7 @@ use cosmwasm_std::{ attr, coins, testing::{mock_dependencies, mock_env, mock_info, MockApi, MockQuerier}, - Env, MemoryStorage, OwnedDeps, Uint128, + Decimal, Env, MemoryStorage, OwnedDeps, Uint128, }; use crate::tests::test::NATIVE_DENOM; @@ -15,6 +15,10 @@ pub fn init() -> (OwnedDeps, Env) { owner: "owner".to_string(), mint_cost: Uint128::new(50_000_000), native_denom: NATIVE_DENOM.to_string(), + default_fuel_fee_recipient: "bob".to_string(), + default_fuel_fee_rate: Decimal::percent(10), + default_ash_fee_recipient: "marley".to_string(), + default_ash_fee_rate: Decimal::percent(5), }; let res = instantiate(deps.as_mut(), env.clone(), info, msg).unwrap(); diff --git a/smart-contracts/src/tests/test.rs b/smart-contracts/src/tests/test.rs index 583ef0b..b6e5d4b 100644 --- a/smart-contracts/src/tests/test.rs +++ b/smart-contracts/src/tests/test.rs @@ -15,6 +15,7 @@ use cw_utils::PaymentError; use prost::Message; pub const NATIVE_DENOM: &str = "uwhale"; +pub const IBC_DENOM: &str = "ibc/FA7112322CE7656DC84D441E49BAEAB9DC0AB3C7618A178A212CDE8B3F17C70B"; pub const DENOM: &str = "factory/migaloo1erul6xyq0gk6ws98ncj7lnq9l4jn4gnnu9we73gdz78yyl2lr7qqrvcgup/ash"; pub const SUBDENOM: &str = "inj"; @@ -106,10 +107,6 @@ fn add_fuel_works() { let msg = ExecuteMsg::AddFuel { subdenom: SUBDENOM.to_string(), denom: DENOM.to_string(), - fuel_fee_recipient: "bob".to_string(), - fuel_fee_rate: "0.1".parse().unwrap(), - ash_fee_recipient: "marley".to_string(), - ash_fee_rate: "0.05".parse().unwrap(), }; // check that the message is executed successfully and attributes are correct @@ -149,23 +146,44 @@ fn add_fuel_works() { }] ); + // add a second fuel + let info = mock_info("another_user", &coins(50_000_000, NATIVE_DENOM)); + let msg = ExecuteMsg::AddFuel { + subdenom: "uatom".to_string(), + denom: "uatom".to_string(), + }; + + execute(deps.as_mut(), env.clone(), info, msg).unwrap(); + // query fuels should return the newly added fuel let query_msg = QueryMsg::Fuels { start_after: None, limit: None, fuel_denom: None, }; - let res: FuelsResponse = from_json(query(deps.as_ref(), env, query_msg).unwrap()).unwrap(); + + let res: FuelsResponse = + from_json(query(deps.as_ref(), env.clone(), query_msg).unwrap()).unwrap(); assert_eq!( res.fuels, - vec![FuelConfig { - subdenom: SUBDENOM.to_string(), - denom: DENOM.to_string(), - fuel_fee_recipient: Addr::unchecked("bob"), - fuel_fee_rate: "0.1".parse().unwrap(), - ash_fee_recipient: Addr::unchecked("marley"), - ash_fee_rate: "0.05".parse().unwrap(), - }] + vec![ + FuelConfig { + subdenom: SUBDENOM.to_string(), + denom: DENOM.to_string(), + fuel_fee_recipient: Addr::unchecked("bob"), + fuel_fee_rate: "0.1".parse().unwrap(), + ash_fee_recipient: Addr::unchecked("marley"), + ash_fee_rate: "0.05".parse().unwrap(), + }, + FuelConfig { + subdenom: "uatom".to_string(), + denom: "uatom".to_string(), + fuel_fee_recipient: Addr::unchecked("bob"), + fuel_fee_rate: "0.1".parse().unwrap(), + ash_fee_recipient: Addr::unchecked("marley"), + ash_fee_rate: "0.05".parse().unwrap(), + } + ] ); } @@ -178,64 +196,72 @@ fn add_fuel_should_fail() { let msg = ExecuteMsg::AddFuel { subdenom: SUBDENOM.to_string(), denom: DENOM.to_string(), - fuel_fee_recipient: "bob".to_string(), - fuel_fee_rate: "0.1".parse().unwrap(), - ash_fee_recipient: "marley".to_string(), - ash_fee_rate: "0.05".parse().unwrap(), }; - assert!(execute(deps.as_mut(), env.clone(), info, msg.clone()).is_err()); + let res = execute(deps.as_mut(), env.clone(), info, msg.clone()); + match res { + Ok(_) => panic!("should throw an error"), + Err(error) => assert_eq!( + error, + ContractError::PaymentError(PaymentError::MissingDenom { + 0: "uwhale".to_string() + }) + ), + } - // if caller is not owner - info = mock_info("not_owner", &coins(50_000_000, NATIVE_DENOM)); - assert!(execute(deps.as_mut(), env.clone(), info, msg.clone()).is_err()); + info = mock_info( + "anyone", + &[coin(50_000_000, IBC_DENOM), coin(50_000_000, NATIVE_DENOM)], + ); + let res = execute(deps.as_mut(), env.clone(), info, msg.clone()); + + match res { + Ok(_) => panic!("should throw an error"), + Err(error) => assert_eq!( + error, + ContractError::PaymentError { + 0: PaymentError::MultipleDenoms {} + } + ), + } + + info = mock_info("anyone", &[coin(50_000_000, NATIVE_DENOM)]); + let res = execute( + deps.as_mut(), + env.clone(), + info, + ExecuteMsg::AddFuel { + subdenom: SUBDENOM.to_string(), + denom: IBC_DENOM.to_string(), + }, + ); + + match res { + Ok(_) => panic!("should throw an error"), + Err(error) => assert_eq!(error, ContractError::InvalidIBCFuel {}), + } // if subdenom is invalid info = mock_info("owner", &coins(50_000_000, NATIVE_DENOM)); let msg = ExecuteMsg::AddFuel { subdenom: "{invalid_subdenom!".to_string(), denom: DENOM.to_string(), - fuel_fee_recipient: "bob".to_string(), - fuel_fee_rate: "0.1".parse().unwrap(), - ash_fee_recipient: "marley".to_string(), - ash_fee_rate: "0.05".parse().unwrap(), }; assert!(execute(deps.as_mut(), env.clone(), info.clone(), msg).is_err()); - // if fuel fee rate is invalid - let msg = ExecuteMsg::AddFuel { - subdenom: SUBDENOM.to_string(), - denom: DENOM.to_string(), - fuel_fee_recipient: "bob".to_string(), - fuel_fee_rate: "1.1".parse().unwrap(), - ash_fee_recipient: "marley".to_string(), - ash_fee_rate: "0.05".parse().unwrap(), - }; - assert!(execute(deps.as_mut(), env.clone(), info.clone(), msg).is_err()); - - // if ash fee rate is invalid - let msg = ExecuteMsg::AddFuel { - subdenom: SUBDENOM.to_string(), - denom: DENOM.to_string(), - fuel_fee_recipient: "bob".to_string(), - fuel_fee_rate: "0.1".parse().unwrap(), - ash_fee_recipient: "marley".to_string(), - ash_fee_rate: "1.1".parse().unwrap(), - }; - assert!(execute(deps.as_mut(), env.clone(), info, msg).is_err()); - // if denom already exists let info = mock_info("owner", &coins(50_000_000, NATIVE_DENOM)); let msg = ExecuteMsg::AddFuel { subdenom: SUBDENOM.to_string(), denom: DENOM.to_string(), - fuel_fee_recipient: "bob".to_string(), - fuel_fee_rate: "0.1".parse().unwrap(), - ash_fee_recipient: "marley".to_string(), - ash_fee_rate: "0.05".parse().unwrap(), }; assert!(execute(deps.as_mut(), env.clone(), info.clone(), msg.clone()).is_ok()); - assert!(execute(deps.as_mut(), env, info, msg).is_err()); + + let res = execute(deps.as_mut(), env, info, msg); + match res { + Ok(_) => panic!("should throw an error"), + Err(error) => assert_eq!(error, ContractError::FuelAlreadyExists {}), + } } //test burn message @@ -259,10 +285,6 @@ fn test_burn_works() { let msg = ExecuteMsg::AddFuel { subdenom: SUBDENOM.to_string(), denom: DENOM.to_string(), - fuel_fee_recipient: "bob".to_string(), - fuel_fee_rate: "0.1".parse().unwrap(), - ash_fee_recipient: "marley".to_string(), - ash_fee_rate: "0.05".parse().unwrap(), }; execute(deps.as_mut(), env.clone(), info.clone(), msg.clone()).unwrap(); @@ -390,10 +412,6 @@ fn test_burn_should_fail() { let msg = ExecuteMsg::AddFuel { subdenom: SUBDENOM.to_string(), denom: DENOM.to_string(), - fuel_fee_recipient: "bob".to_string(), - fuel_fee_rate: "0.1".parse().unwrap(), - ash_fee_recipient: "marley".to_string(), - ash_fee_rate: "0.05".parse().unwrap(), }; execute(deps.as_mut(), env.clone(), info.clone(), msg.clone()).unwrap(); @@ -501,10 +519,6 @@ fn test_update_fuel_config_works() { let msg = ExecuteMsg::AddFuel { subdenom: SUBDENOM.to_string(), denom: DENOM.to_string(), - fuel_fee_recipient: "bob".to_string(), - fuel_fee_rate: "0.1".parse().unwrap(), - ash_fee_recipient: "marley".to_string(), - ash_fee_rate: "0.05".parse().unwrap(), }; let _ = execute(deps.as_mut(), env.clone(), info.clone(), msg.clone()).unwrap(); @@ -583,10 +597,6 @@ fn test_update_fuel_config_should_fail() { let msg = ExecuteMsg::AddFuel { subdenom: SUBDENOM.to_string(), denom: DENOM.to_string(), - fuel_fee_recipient: "bob".to_string(), - fuel_fee_rate: "0.1".parse().unwrap(), - ash_fee_recipient: "marley".to_string(), - ash_fee_rate: "0.05".parse().unwrap(), }; let _ = execute(deps.as_mut(), env.clone(), info.clone(), msg.clone()).unwrap(); @@ -677,7 +687,6 @@ fn test_update_fuel_config_should_fail() { ] ); } - #[test] fn test_burn_10_coins() { let (mut deps, env) = init(); @@ -686,10 +695,6 @@ fn test_burn_10_coins() { let msg = ExecuteMsg::AddFuel { subdenom: SUBDENOM.to_string(), denom: DENOM.to_string(), - fuel_fee_recipient: "bob".to_string(), - fuel_fee_rate: "0.1".parse().unwrap(), - ash_fee_recipient: "marley".to_string(), - ash_fee_rate: "0.05".parse().unwrap(), }; let _ = execute(deps.as_mut(), env.clone(), info.clone(), msg.clone()).unwrap(); @@ -770,10 +775,6 @@ fn test_add_fuel_when_mint_cost_is_zero() { let msg = ExecuteMsg::AddFuel { subdenom: SUBDENOM.to_string(), denom: DENOM.to_string(), - fuel_fee_recipient: "bob".to_string(), - fuel_fee_rate: "0.1".parse().unwrap(), - ash_fee_recipient: "marley".to_string(), - ash_fee_rate: "0.05".parse().unwrap(), }; // should not let add fuel when mint cost is zero and funds are sent