diff --git a/.cargo/audit.toml b/.cargo/audit.toml new file mode 100644 index 000000000..80dec5a81 --- /dev/null +++ b/.cargo/audit.toml @@ -0,0 +1,6 @@ +# Reference: https://github.com/rustsec/rustsec/blob/main/cargo-audit/audit.toml.example + +[advisories] +# Ignore the following advisory IDs. +# RUSTSEC-2022-0093 is reported for test-tube which is only used for testing. +ignore = ["RUSTSEC-2022-0093"] \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 5fb78db7f..ce2082c52 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -815,9 +815,9 @@ dependencies = [ [[package]] name = "cw-it" -version = "0.1.0" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e3120d46b30b900c4b9ab0f996ce527c1187095c75ffa61d9ea5aa6d1e93b41" +checksum = "6c7ad91046afef49ea3b922c36395298254802844cd360911cf2e1b96825c9df" dependencies = [ "anyhow", "apollo-cw-multi-test", @@ -1830,6 +1830,7 @@ dependencies = [ "cw2 1.1.0", "mars-owner", "mars-red-bank-types", + "mars-utils", "pyth-sdk-cw", "schemars", "serde", diff --git a/Cargo.toml b/Cargo.toml index 8df05ee17..71a9d92e2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -59,7 +59,7 @@ thiserror = "1.0.44" # dev-dependencies cw-multi-test = "0.16.5" -cw-it = "0.1.0" +cw-it = "0.1.1" osmosis-test-tube = "=16.1.1" proptest = "1.2.0" test-case = "3.1.0" diff --git a/contracts/oracle/base/Cargo.toml b/contracts/oracle/base/Cargo.toml index 5afd4108f..d7c357996 100644 --- a/contracts/oracle/base/Cargo.toml +++ b/contracts/oracle/base/Cargo.toml @@ -22,6 +22,7 @@ cosmwasm-std = { workspace = true } cw2 = { workspace = true } cw-storage-plus = { workspace = true } mars-owner = { workspace = true } +mars-utils = { workspace = true } mars-red-bank-types = { workspace = true } pyth-sdk-cw = { workspace = true, optional = true } schemars = { workspace = true } diff --git a/contracts/oracle/base/src/contract.rs b/contracts/oracle/base/src/contract.rs index e0268d8d0..b402c913b 100644 --- a/contracts/oracle/base/src/contract.rs +++ b/contracts/oracle/base/src/contract.rs @@ -10,11 +10,9 @@ use mars_red_bank_types::oracle::msg::{ ActionKind, Config, ConfigResponse, ExecuteMsg, InstantiateMsg, PriceResponse, PriceSourceResponse, QueryMsg, }; +use mars_utils::helpers::validate_native_denom; -use crate::{ - error::ContractResult, utils::validate_native_denom, ContractError, PriceSourceChecked, - PriceSourceUnchecked, -}; +use crate::{error::ContractResult, ContractError, PriceSourceChecked, PriceSourceUnchecked}; const DEFAULT_LIMIT: u32 = 10; const MAX_LIMIT: u32 = 30; diff --git a/contracts/oracle/base/src/error.rs b/contracts/oracle/base/src/error.rs index 8b71779a8..06cf2c50e 100644 --- a/contracts/oracle/base/src/error.rs +++ b/contracts/oracle/base/src/error.rs @@ -3,6 +3,7 @@ use cosmwasm_std::{ DecimalRangeExceeded, DivideByZeroError, OverflowError, StdError, }; use mars_owner::OwnerError; +use mars_utils::error::ValidationError; use thiserror::Error; #[derive(Error, Debug, PartialEq)] @@ -10,12 +11,8 @@ pub enum ContractError { #[error("{0}")] Std(#[from] StdError), - // #[error("{0}")] - // Mars(#[from] MarsError), - #[error("Invalid denom: {reason}")] - InvalidDenom { - reason: String, - }, + #[error("{0}")] + Validation(#[from] ValidationError), #[error("{0}")] Version(#[from] cw2::VersionError), diff --git a/contracts/oracle/base/src/lib.rs b/contracts/oracle/base/src/lib.rs index 48deeef95..0756db31a 100644 --- a/contracts/oracle/base/src/lib.rs +++ b/contracts/oracle/base/src/lib.rs @@ -1,7 +1,6 @@ mod contract; mod error; mod traits; -mod utils; #[cfg(feature = "pyth")] pub mod pyth; @@ -9,4 +8,3 @@ pub mod pyth; pub use contract::*; pub use error::*; pub use traits::*; -pub use utils::*; diff --git a/contracts/oracle/base/src/utils.rs b/contracts/oracle/base/src/utils.rs deleted file mode 100644 index 8b5cb83b5..000000000 --- a/contracts/oracle/base/src/utils.rs +++ /dev/null @@ -1,32 +0,0 @@ -use crate::ContractError; - -/// follows cosmos SDK validation logic where denoms can be 3 - 128 characters long -/// and starts with a letter, followed but either a letter, number, or separator ( ‘/' , ‘:' , ‘.’ , ‘_’ , or '-') -/// reference: https://github.com/cosmos/cosmos-sdk/blob/7728516abfab950dc7a9120caad4870f1f962df5/types/coin.go#L865-L867 -pub fn validate_native_denom(denom: &str) -> Result<(), ContractError> { - if denom.len() < 3 || denom.len() > 128 { - return Err(ContractError::InvalidDenom { - reason: "Invalid denom length".to_string(), - }); - } - - let mut chars = denom.chars(); - let first = chars.next().unwrap(); - if !first.is_ascii_alphabetic() { - return Err(ContractError::InvalidDenom { - reason: "First character is not ASCII alphabetic".to_string(), - }); - } - - let set = ['/', ':', '.', '_', '-']; - for c in chars { - if !(c.is_ascii_alphanumeric() || set.contains(&c)) { - return Err(ContractError::InvalidDenom { - reason: "Not all characters are ASCII alphanumeric or one of: / : . _ -" - .to_string(), - }); - } - } - - Ok(()) -} diff --git a/contracts/oracle/osmosis/Cargo.toml b/contracts/oracle/osmosis/Cargo.toml index 47f0829b9..2a6e48688 100644 --- a/contracts/oracle/osmosis/Cargo.toml +++ b/contracts/oracle/osmosis/Cargo.toml @@ -26,6 +26,7 @@ cw-storage-plus = { workspace = true } mars-oracle-base = { workspace = true, features = ["pyth"] } mars-osmosis = { workspace = true } mars-owner = { workspace = true } +mars-utils = { workspace = true } mars-red-bank-types = { workspace = true } osmosis-std = { workspace = true } pyth-sdk-cw = { workspace = true } diff --git a/contracts/oracle/osmosis/src/helpers.rs b/contracts/oracle/osmosis/src/helpers.rs index 4ffc2a1ec..c0937bb4c 100644 --- a/contracts/oracle/osmosis/src/helpers.rs +++ b/contracts/oracle/osmosis/src/helpers.rs @@ -49,6 +49,12 @@ fn assert_osmosis_pool_contains_two_assets( let pool_id = pool.get_pool_id(); let pool_denoms = pool.get_pool_denoms(); + if denom == base_denom { + return Err(ContractError::InvalidPriceSource { + reason: "denom and base denom can't be the same".to_string(), + }); + } + if !pool_denoms.contains(&base_denom.to_string()) { return Err(ContractError::InvalidPriceSource { reason: format!("pool {} does not contain the base denom {}", pool_id, base_denom), diff --git a/contracts/oracle/osmosis/src/price_source.rs b/contracts/oracle/osmosis/src/price_source.rs index 22f0eba1a..adb229297 100644 --- a/contracts/oracle/osmosis/src/price_source.rs +++ b/contracts/oracle/osmosis/src/price_source.rs @@ -11,6 +11,7 @@ use mars_osmosis::helpers::{ recovered_since_downtime_of_length, Pool, }; use mars_red_bank_types::oracle::{ActionKind, Config}; +use mars_utils::helpers::validate_native_denom; use pyth_sdk_cw::PriceIdentifier; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; @@ -381,9 +382,12 @@ impl PriceSourceUnchecked for OsmosisPriceSour window_size, downtime_detector, } => { + validate_native_denom(transitive_denom)?; + let pool = query_pool(&deps.querier, *pool_id)?; helpers::assert_osmosis_pool_assets(&pool, denom, transitive_denom)?; helpers::assert_osmosis_twap(*window_size, downtime_detector)?; + Ok(OsmosisPriceSourceChecked::StakedGeometricTwap { transitive_denom: transitive_denom.to_string(), pool_id: *pool_id, @@ -415,12 +419,15 @@ impl PriceSourceUnchecked for OsmosisPriceSour geometric_twap, redemption_rate, } => { + validate_native_denom(transitive_denom)?; + let pool = query_pool(&deps.querier, geometric_twap.pool_id)?; helpers::assert_osmosis_pool_assets(&pool, denom, transitive_denom)?; helpers::assert_osmosis_twap( geometric_twap.window_size, &geometric_twap.downtime_detector, )?; + Ok(OsmosisPriceSourceChecked::Lsd { transitive_denom: transitive_denom.to_string(), geometric_twap: geometric_twap.clone(), diff --git a/contracts/oracle/osmosis/tests/test_admin.rs b/contracts/oracle/osmosis/tests/test_admin.rs index b996215d3..5812bc5e8 100644 --- a/contracts/oracle/osmosis/tests/test_admin.rs +++ b/contracts/oracle/osmosis/tests/test_admin.rs @@ -4,6 +4,7 @@ use mars_oracle_osmosis::{contract::entry, msg::ExecuteMsg}; use mars_owner::OwnerError::NotOwner; use mars_red_bank_types::oracle::{ConfigResponse, InstantiateMsg, QueryMsg}; use mars_testing::{mock_dependencies, mock_info}; +use mars_utils::error::ValidationError; mod helpers; @@ -35,9 +36,9 @@ fn instantiating_incorrect_denom() { ); assert_eq!( res, - Err(ContractError::InvalidDenom { + Err(ContractError::Validation(ValidationError::InvalidDenom { reason: "First character is not ASCII alphabetic".to_string() - }) + })) ); let res = entry::instantiate( @@ -52,10 +53,10 @@ fn instantiating_incorrect_denom() { ); assert_eq!( res, - Err(ContractError::InvalidDenom { + Err(ContractError::Validation(ValidationError::InvalidDenom { reason: "Not all characters are ASCII alphanumeric or one of: / : . _ -" .to_string() - }) + })) ); let res = entry::instantiate( @@ -70,9 +71,9 @@ fn instantiating_incorrect_denom() { ); assert_eq!( res, - Err(ContractError::InvalidDenom { + Err(ContractError::Validation(ValidationError::InvalidDenom { reason: "Invalid denom length".to_string() - }) + })) ); } @@ -99,9 +100,9 @@ fn update_config_with_invalid_base_denom() { let res_err = entry::execute(deps.as_mut(), mock_env(), info, msg).unwrap_err(); assert_eq!( res_err, - ContractError::InvalidDenom { + ContractError::Validation(ValidationError::InvalidDenom { reason: "First character is not ASCII alphabetic".to_string() - } + }) ); } diff --git a/contracts/oracle/osmosis/tests/test_set_price_source.rs b/contracts/oracle/osmosis/tests/test_set_price_source.rs index d56fa5c91..4cd0c60f2 100644 --- a/contracts/oracle/osmosis/tests/test_set_price_source.rs +++ b/contracts/oracle/osmosis/tests/test_set_price_source.rs @@ -11,6 +11,7 @@ use mars_oracle_osmosis::{ use mars_owner::OwnerError::NotOwner; use mars_red_bank_types::oracle::msg::QueryMsg; use mars_testing::mock_info; +use mars_utils::error::ValidationError; use pyth_sdk_cw::PriceIdentifier; mod helpers; @@ -83,9 +84,9 @@ fn setting_price_source_incorrect_denom() { ); assert_eq!( res, - Err(ContractError::InvalidDenom { + Err(ContractError::Validation(ValidationError::InvalidDenom { reason: "First character is not ASCII alphabetic".to_string() - }) + })) ); let res_two = execute( @@ -101,10 +102,10 @@ fn setting_price_source_incorrect_denom() { ); assert_eq!( res_two, - Err(ContractError::InvalidDenom { + Err(ContractError::Validation(ValidationError::InvalidDenom { reason: "Not all characters are ASCII alphanumeric or one of: / : . _ -" .to_string() - }) + })) ); let res_three = execute( @@ -120,9 +121,9 @@ fn setting_price_source_incorrect_denom() { ); assert_eq!( res_three, - Err(ContractError::InvalidDenom { + Err(ContractError::Validation(ValidationError::InvalidDenom { reason: "Invalid denom length".to_string() - }) + })) ); } @@ -144,6 +145,15 @@ fn setting_price_source_spot() { ) }; + // attempting to set price source for base denom; should fail + let err = set_price_source_spot("uosmo", 1).unwrap_err(); + assert_eq!( + err, + ContractError::InvalidPriceSource { + reason: "denom and base denom can't be the same".to_string() + } + ); + // attempting to use a pool that does not contain the denom of interest; should fail let err = set_price_source_spot("umars", 1).unwrap_err(); assert_eq!( @@ -222,6 +232,15 @@ fn setting_price_source_arithmetic_twap_with_invalid_params() { ) }; + // attempting to set price source for base denom; should fail + let err = set_price_source_twap("uosmo", 1, 86400, None).unwrap_err(); + assert_eq!( + err, + ContractError::InvalidPriceSource { + reason: "denom and base denom can't be the same".to_string() + } + ); + // attempting to use a pool that does not contain the denom of interest; should fail let err = set_price_source_twap("umars", 1, 86400, None).unwrap_err(); assert_eq!( @@ -385,6 +404,15 @@ fn setting_price_source_geometric_twap_with_invalid_params() { ) }; + // attempting to set price source for base denom; should fail + let err = set_price_source_twap("uosmo", 1, 86400, None).unwrap_err(); + assert_eq!( + err, + ContractError::InvalidPriceSource { + reason: "denom and base denom can't be the same".to_string() + } + ); + // attempting to use a pool that does not contain the denom of interest; should fail let err = set_price_source_twap("umars", 1, 86400, None).unwrap_err(); assert_eq!( @@ -550,6 +578,24 @@ fn setting_price_source_staked_geometric_twap_with_invalid_params() { ) }; + // attempting to set price source for base denom; should fail + let err = set_price_source_twap("uosmo", "uosmo", 1, 86400, None).unwrap_err(); + assert_eq!( + err, + ContractError::InvalidPriceSource { + reason: "denom and base denom can't be the same".to_string() + } + ); + + // attempting to set price source with invalid transitive denom; should fail + let err = set_price_source_twap("ustatom", "!*jadfaefc", 803, 86400, None).unwrap_err(); + assert_eq!( + err, + ContractError::Validation(ValidationError::InvalidDenom { + reason: "First character is not ASCII alphabetic".to_string() + }) + ); + // attempting to use a pool that does not contain the denom of interest; should fail let err = set_price_source_twap("ustatom", "umars", 803, 86400, None).unwrap_err(); assert_eq!( @@ -724,6 +770,24 @@ fn setting_price_source_lsd_with_invalid_params() { ) }; + // attempting to set price source for base denom; should fail + let err = set_price_source_twap("uosmo", "uosmo", 1, 86400, None).unwrap_err(); + assert_eq!( + err, + ContractError::InvalidPriceSource { + reason: "denom and base denom can't be the same".to_string() + } + ); + + // attempting to set price source with invalid transitive denom; should fail + let err = set_price_source_twap("ustatom", "!*jadfaefc", 3333, 86400, None).unwrap_err(); + assert_eq!( + err, + ContractError::Validation(ValidationError::InvalidDenom { + reason: "First character is not ASCII alphabetic".to_string() + }) + ); + // attempting to use a pool that does not contain the denom of interest; should fail let err = set_price_source_twap("ustatom", "umars", 803, 86400, None).unwrap_err(); assert_eq!(