diff --git a/Cargo.lock b/Cargo.lock index caa2fef..713abc7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -13,15 +13,6 @@ dependencies = [ "version_check", ] -[[package]] -name = "aho-corasick" -version = "0.7.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4f55bd91a0978cbfd91c457a164bab8b4001c833b7f323132c0a4e1922dd44e" -dependencies = [ - "memchr", -] - [[package]] name = "base16ct" version = "0.1.1" @@ -306,7 +297,6 @@ dependencies = [ "cw2", "cw20", "injective-cosmwasm", - "regex", "serde", "thiserror", ] @@ -611,12 +601,6 @@ version = "0.2.137" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc7fcc620a3bff7cdd7a365be3376c97191aeaccc2a603e600951e452615bf89" -[[package]] -name = "memchr" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" - [[package]] name = "once_cell" version = "1.16.0" @@ -700,23 +684,6 @@ dependencies = [ "getrandom", ] -[[package]] -name = "regex" -version = "1.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e076559ef8e241f2ae3479e36f97bd5741c0330689e217ad51ce2c76808b868a" -dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", -] - -[[package]] -name = "regex-syntax" -version = "0.6.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" - [[package]] name = "rfc6979" version = "0.3.1" diff --git a/contracts/cw20-adapter/Cargo.toml b/contracts/cw20-adapter/Cargo.toml index 0010baf..27e60da 100644 --- a/contracts/cw20-adapter/Cargo.toml +++ b/contracts/cw20-adapter/Cargo.toml @@ -28,6 +28,5 @@ cw20 = { workspace = true } cw-storage-plus = { workspace = true } thiserror = { workspace = true } cw-item-set = { workspace = true } -regex = { workspace = true } injective-cosmwasm = { workspace = true } serde = { workspace = true } \ No newline at end of file diff --git a/contracts/cw20-adapter/src/common.rs b/contracts/cw20-adapter/src/common.rs index 4156480..e15d88e 100644 --- a/contracts/cw20-adapter/src/common.rs +++ b/contracts/cw20-adapter/src/common.rs @@ -1,23 +1,78 @@ -use crate::error::ContractError; -use crate::state::CW20_CONTRACTS; -use cosmwasm_std::{Addr, Coin, CosmosMsg, DepsMut, Env, QuerierWrapper, StdResult}; +use cosmwasm_std::{Addr, Coin, CosmosMsg, DepsMut, Env, QuerierWrapper, StdResult, Uint128}; use cw20::{Cw20QueryMsg, TokenInfoResponse}; + use injective_cosmwasm::{create_new_denom_msg, InjectiveMsgWrapper, InjectiveQuerier, InjectiveQueryWrapper}; -use regex::Regex; +use serde::{Deserialize, Serialize}; + +use crate::error::ContractError; +use crate::state::CW20_CONTRACTS; + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct AdapterDenom { + pub adapter_addr: String, + pub cw20_addr: String, +} + +const FACTORY_PREFIX: &str = "factory/"; + +impl AdapterDenom { + pub fn new(denom: S) -> Result + where + S: Into, + { + let denom = denom.into(); + + if denom.len() != 93 { + return Err(ContractError::NotCw20Address); + } + if !denom.starts_with(FACTORY_PREFIX) { + return Err(ContractError::NotCw20Address); + } + // adapter address starts from index 8 and ends at 50 since "factory/" is 8 characters + // and inj addresses have 42 characters + let adapter_part = &denom[8..50]; + // remaining portion is the cw20 address + let cw20_part = &denom[51..]; + Ok::, ContractError>(AdapterDenom::from_components(adapter_part, cw20_part))? + } + + pub fn from_components(adapter_addr: S, cw20_addr: S) -> Result + where + S: Into, + { + let adapter_addr = adapter_addr.into(); + let cw20_addr = cw20_addr.into(); + if !adapter_addr.chars().all(char::is_alphanumeric) { + return Err(ContractError::NotCw20Address); + } + if !cw20_addr.chars().all(char::is_alphanumeric) { + return Err(ContractError::NotCw20Address); + } + Ok(AdapterDenom { adapter_addr, cw20_addr }) + } + + pub fn as_string(&self) -> String { + get_denom_from_str(&self.adapter_addr, &self.cw20_addr) + } +} -// 42 since the string length of inj addresses is 42, e.g. "inj1mtl2fykeceen4c74ddldrtdkq6fvnz79n8c592" -pub fn denom_parser() -> Regex { - Regex::new(r"factory/(\w{42})/(\w{42})").unwrap() +pub struct AdapterCoin { + pub amount: Uint128, + pub denom: AdapterDenom, } -pub fn get_cw20_address_from_denom(parser: &Regex, denom: &str) -> Option { - let captures = parser.captures(denom)?; - let cw20addr = captures.get(2)?; - Some(cw20addr.as_str().to_string()) +impl AdapterCoin { + pub fn as_coin(&self) -> Coin { + Coin::new(self.amount.u128(), self.denom.as_string()) + } } pub fn get_denom(adapter_address: &Addr, cw20addr: &Addr) -> String { - format!("factory/{}/{}", adapter_address, cw20addr) + get_denom_from_str(adapter_address.as_str(), cw20addr.as_str()) +} + +fn get_denom_from_str(adapter_address: &str, cw20addr: &str) -> String { + format!("{}{}/{}", FACTORY_PREFIX, adapter_address, cw20addr) } pub fn query_denom_creation_fee(querier_wrapper: &QuerierWrapper) -> StdResult> { @@ -74,52 +129,59 @@ mod tests { #[test] fn it_returns_true_on_correct_token_factory_denom() { - assert!( - denom_parser().is_match("factory/inj1pvrwmjuusn9wh34j7y520g8gumuy9xtlt6xtzw/inj1n0qvel0zfmsxu3q8q23xzjvuwfxn0ydlhgyh7h"), - "input was not treated as token factory denom" - ) + AdapterDenom::new("factory/inj1pvrwmjuusn9wh34j7y520g8gumuy9xtlt6xtzw/inj1n0qvel0zfmsxu3q8q23xzjvuwfxn0ydlhgyh7h") + .expect("input was not treated as token factory denom"); } #[test] fn it_returns_false_for_non_token_factory_denom() { - assert!( - !denom_parser().is_match(".factory/inj1pvrwmjuusn9wh34j7y520g8gumuy9xtlt6xtzw/inj1n0qvel0zfmsxu3q8q23xzjvuwfxn0ydlhgyh7"), - "input was treated as token factory denom" - ) + AdapterDenom::new(".factory/inj1pvrwmjuusn9wh34j7y520g8gumuy9xtlt6xtzw/inj1n0qvel0zfmsxu3q8q23xzjvuwfxn0ydlhgyh7") + .expect_err("input was treated as token factory denom"); + } + + #[test] + fn it_returns_false_for_non_token_factory_denom_2() { + AdapterDenom::new("factory/inj1pvrwmjuusn9wh34j7y520g8gumuy9xtlt6xtz/winj1n0qvel0zfmsxu3q8q23xzjvuwfxn0ydlhgyh7h") + .expect_err("input was treated as token factory denom"); + } + + #[test] + fn it_returns_false_for_non_token_factory_denom_3() { + AdapterDenom::new("factory/inj1pvrwm_uusn9wh34j7y520g8gumuy9xtlt6xtzw/inj1n0qvel0zfmsxu3q8q23xzjvuwfxn0ydlhgyh7") + .expect_err("input was treated as token factory denom"); + } + + #[test] + fn it_returns_false_for_non_token_factory_denom_4() { + AdapterDenom::new("factory/inj1pvrwmuusn9wh34j7y520g8gumuy9xtlt6xtzw/inj1n0qvel0zfmsxu3q8q23xzjvuwfxn0ydlhgyh7/sddsjsdk") + .expect_err("input was treated as token factory denom"); } #[test] fn it_returns_cw_20_address_for_token_factory_denom() { + let denom = AdapterDenom::new("factory/inj1pvrwmjuusn9wh34j7y520g8gumuy9xtlt6xtzw/inj1n0qvel0zfmsxu3q8q23xzjvuwfxn0ydlhgyh7h").unwrap(); + assert_eq!( + denom.adapter_addr, "inj1pvrwmjuusn9wh34j7y520g8gumuy9xtlt6xtzw", + "wrong cw20 address returned" + ); assert_eq!( - get_cw20_address_from_denom( - &denom_parser(), - "factory/inj1pvrwmjuusn9wh34j7y520g8gumuy9xtlt6xtzw/inj1n0qvel0zfmsxu3q8q23xzjvuwfxn0ydlhgyh7h", - ) - .unwrap(), - "inj1n0qvel0zfmsxu3q8q23xzjvuwfxn0ydlhgyh7h", + denom.cw20_addr, "inj1n0qvel0zfmsxu3q8q23xzjvuwfxn0ydlhgyh7h", "wrong cw20 address returned" ) } #[test] fn it_returns_none_cw_20_address_for_non_token_factory_denom() { - assert!( - get_cw20_address_from_denom( - &denom_parser(), - "factory/inj1pvrwmjuusn9wh34j7y520g8gumuy9xtlt6xtzw/inj1n0qvel0zfmsxu3q8q23xzjvuwfxn0ydlhgyh7", - ) - .is_none(), - "cw20 address returned" - ) + let denom = AdapterDenom::new("factory/inj1pvrwmjuusn9wh34j7y520g8gumuy9xtlt6xtzw/inj1n0qvel0zfmsxu3q8q23xzjvuwfxn0ydlhgyh7").unwrap_err(); + assert_eq!(denom, ContractError::NotCw20Address, "cw20 address returned") } #[test] fn it_returns_denom() { + let denom = + AdapterDenom::from_components("inj1pvrwmjuusn9wh34j7y520g8gumuy9xtlt6xtzw", "inj1n0qvel0zfmsxu3q8q23xzjvuwfxn0ydlhgyh7h").unwrap(); assert_eq!( - get_denom( - &Addr::unchecked("inj1pvrwmjuusn9wh34j7y520g8gumuy9xtlt6xtzw".to_string()), - &Addr::unchecked("inj1n0qvel0zfmsxu3q8q23xzjvuwfxn0ydlhgyh7h".to_string()), - ), + denom.as_string(), "factory/inj1pvrwmjuusn9wh34j7y520g8gumuy9xtlt6xtzw/inj1n0qvel0zfmsxu3q8q23xzjvuwfxn0ydlhgyh7h", "wrong denom returned" ) diff --git a/contracts/cw20-adapter/src/execute_redeem.rs b/contracts/cw20-adapter/src/execute_redeem.rs index 9e6d42e..a6161f9 100644 --- a/contracts/cw20-adapter/src/execute_redeem.rs +++ b/contracts/cw20-adapter/src/execute_redeem.rs @@ -1,8 +1,8 @@ -use cosmwasm_std::{to_binary, Binary, Coin, DepsMut, Env, MessageInfo, Response, WasmMsg}; +use cosmwasm_std::{to_binary, Binary, DepsMut, Env, MessageInfo, Response, WasmMsg}; use cw20::Cw20ExecuteMsg; use injective_cosmwasm::{create_burn_tokens_msg, InjectiveMsgWrapper, InjectiveQueryWrapper}; -use crate::common::{denom_parser, get_cw20_address_from_denom}; +use crate::common::{AdapterCoin, AdapterDenom}; use crate::error::ContractError; use crate::state::CW20_CONTRACTS; @@ -18,26 +18,30 @@ pub fn handle_redeem_msg( if info.funds.len() > 1 { return Err(ContractError::SuperfluousFundsProvided); } - let denom_parser = denom_parser(); let tokens_to_exchange = info .funds .iter() - .find_map(|c| -> Option { - if denom_parser.is_match(&c.denom) { - Some(c.clone()) - } else { - None + .find_map(|c| -> Option { + match AdapterDenom::new(&c.denom) { + Ok(denom) => Some(AdapterCoin { amount: c.amount, denom }), + Err(_) => None, } + // if denom_parser.is_match(&c.denom) { + // Some(c.clone()) + // } else { + // None + // } }) .ok_or(ContractError::NoRegisteredTokensProvided)?; - let cw20_addr = get_cw20_address_from_denom(&denom_parser, &tokens_to_exchange.denom).ok_or(ContractError::NoRegisteredTokensProvided)?; - let is_contract_registered = CW20_CONTRACTS.contains(deps.storage, cw20_addr.as_str()); + let cw20_addr = tokens_to_exchange.denom.cw20_addr.clone(); + // let cw20_addr = get_cw20_address_from_denom(&denom_parser, &tokens_to_exchange.denom).ok_or(ContractError::NoRegisteredTokensProvided)?; + let is_contract_registered = CW20_CONTRACTS.contains(deps.storage, &tokens_to_exchange.denom.cw20_addr); if !is_contract_registered { return Err(ContractError::NoRegisteredTokensProvided); } - let burn_tf_tokens_message = create_burn_tokens_msg(env.contract.address, tokens_to_exchange.clone()); + let burn_tf_tokens_message = create_burn_tokens_msg(env.contract.address, tokens_to_exchange.as_coin()); let cw20_message: WasmMsg = match submessage { None => WasmMsg::Execute { diff --git a/contracts/cw20-adapter/src/execute_register.rs b/contracts/cw20-adapter/src/execute_register.rs index 824c56d..c1c8b18 100644 --- a/contracts/cw20-adapter/src/execute_register.rs +++ b/contracts/cw20-adapter/src/execute_register.rs @@ -19,7 +19,8 @@ pub fn handle_register_msg( } let mut provided_funds = info.funds.iter(); - for required_coin in required_funds { + + for required_coin in &required_funds { let pf = provided_funds .find(|c| -> bool { c.denom == required_coin.denom }) .ok_or(ContractError::NotEnoughBalanceToPayDenomCreationFee)?;