From 307d56a1939f0992a99ed47e5569ed0153221054 Mon Sep 17 00:00:00 2001 From: Joe Monem Date: Wed, 29 May 2024 16:05:36 +0300 Subject: [PATCH 1/2] feature: allow users to set timeout for their functions, resolves issue #1 --- contracts/liquidity/factory/src/contract.rs | 12 +++++++++--- contracts/liquidity/factory/src/execute.rs | 16 +++++++++++++--- contracts/liquidity/factory/src/ibc.rs | 2 +- contracts/liquidity/pool/src/contract.rs | 4 ++++ contracts/liquidity/pool/src/execute.rs | 15 +++++++++++---- packages/euclid/src/error.rs | 3 +++ packages/euclid/src/lib.rs | 1 + packages/euclid/src/msgs/factory.rs | 3 +++ packages/euclid/src/msgs/pool.rs | 3 +++ packages/euclid/src/timeout.rs | 18 ++++++++++++++++++ 10 files changed, 66 insertions(+), 11 deletions(-) create mode 100644 packages/euclid/src/timeout.rs diff --git a/contracts/liquidity/factory/src/contract.rs b/contracts/liquidity/factory/src/contract.rs index 4ec13448..0bd540e1 100644 --- a/contracts/liquidity/factory/src/contract.rs +++ b/contracts/liquidity/factory/src/contract.rs @@ -47,15 +47,18 @@ pub fn execute( msg: ExecuteMsg, ) -> Result { match msg { - ExecuteMsg::RequestPoolCreation { pair_info, channel } => { - execute::execute_request_pool_creation(deps, env, info, pair_info, channel) - } + ExecuteMsg::RequestPoolCreation { + pair_info, + channel, + timeout, + } => execute::execute_request_pool_creation(deps, env, info, pair_info, channel, timeout), ExecuteMsg::ExecuteSwap { asset, asset_amount, min_amount_out, channel, swap_id, + timeout, } => execute::execute_swap( deps, env, @@ -65,6 +68,7 @@ pub fn execute( min_amount_out, channel, swap_id, + timeout, ), ExecuteMsg::AddLiquidity { token_1_liquidity, @@ -72,6 +76,7 @@ pub fn execute( slippage_tolerance, channel, liquidity_id, + timeout, } => execute::execute_add_liquidity( deps, env, @@ -81,6 +86,7 @@ pub fn execute( slippage_tolerance, channel, liquidity_id, + timeout, ), } } diff --git a/contracts/liquidity/factory/src/execute.rs b/contracts/liquidity/factory/src/execute.rs index 0929999a..65497e84 100644 --- a/contracts/liquidity/factory/src/execute.rs +++ b/contracts/liquidity/factory/src/execute.rs @@ -3,6 +3,7 @@ use cosmwasm_std::{ }; use euclid::{ error::ContractError, + timeout::get_timeout, token::{PairInfo, Token}, }; use euclid_ibc::msg::IbcExecuteMsg; @@ -16,6 +17,7 @@ pub fn execute_request_pool_creation( info: MessageInfo, pair_info: PairInfo, channel: String, + timeout: Option, ) -> Result { // Load the state let state = STATE.load(deps.storage)?; @@ -25,6 +27,8 @@ pub fn execute_request_pool_creation( // Create a Request in state let pool_request = generate_pool_req(deps, &info.sender, env.block.chain_id, channel.clone())?; + let timeout = get_timeout(timeout)?; + // Create IBC packet to send to Router let ibc_packet = IbcMsg::SendPacket { channel_id: channel.clone(), @@ -34,7 +38,7 @@ pub fn execute_request_pool_creation( pair_info, })?, - timeout: IbcTimeout::with_timestamp(env.block.time.plus_seconds(60)), + timeout: IbcTimeout::with_timestamp(env.block.time.plus_seconds(timeout)), }; msgs.push(ibc_packet.into()); @@ -54,12 +58,15 @@ pub fn execute_swap( min_amount_out: Uint128, channel: String, swap_id: String, + timeout: Option, ) -> Result { // Load the state let state = STATE.load(deps.storage)?; let pool_address = info.sender; + let timeout = get_timeout(timeout)?; + // Create IBC packet to send to Router let ibc_packet = IbcMsg::SendPacket { channel_id: channel.clone(), @@ -72,7 +79,7 @@ pub fn execute_swap( swap_id, pool_address, })?, - timeout: IbcTimeout::with_timestamp(env.block.time.plus_seconds(60)), + timeout: IbcTimeout::with_timestamp(env.block.time.plus_seconds(timeout)), }; let msg = CosmosMsg::Ibc(ibc_packet); @@ -92,12 +99,15 @@ pub fn execute_add_liquidity( slippage_tolerance: u64, channel: String, liquidity_id: String, + timeout: Option, ) -> Result { // Load the state let state = STATE.load(deps.storage)?; let pool_address = info.sender.clone(); + let timeout = get_timeout(timeout)?; + // Create IBC packet to send to Router let ibc_packet = IbcMsg::SendPacket { channel_id: channel.clone(), @@ -109,7 +119,7 @@ pub fn execute_add_liquidity( liquidity_id, pool_address: pool_address.clone().to_string(), })?, - timeout: IbcTimeout::with_timestamp(env.block.time.plus_seconds(60)), + timeout: IbcTimeout::with_timestamp(env.block.time.plus_seconds(timeout)), }; let msg = CosmosMsg::Ibc(ibc_packet); diff --git a/contracts/liquidity/factory/src/ibc.rs b/contracts/liquidity/factory/src/ibc.rs index b9d7b6f4..2159d26f 100644 --- a/contracts/liquidity/factory/src/ibc.rs +++ b/contracts/liquidity/factory/src/ibc.rs @@ -10,7 +10,7 @@ use cosmwasm_std::{ }; use euclid::msgs::pool::ExecuteMsg as PoolExecuteMsg; use euclid::{ - error::{ContractError, Never}, + error::ContractError, msgs::pool::CallbackExecuteMsg, pool::{LiquidityResponse, Pool, PoolCreationResponse}, swap::SwapResponse, diff --git a/contracts/liquidity/pool/src/contract.rs b/contracts/liquidity/pool/src/contract.rs index 9b5a55ef..259c9dc3 100644 --- a/contracts/liquidity/pool/src/contract.rs +++ b/contracts/liquidity/pool/src/contract.rs @@ -57,6 +57,7 @@ pub fn execute( asset_amount, min_amount_out, channel, + timeout, } => execute::execute_swap_request( deps, info, @@ -66,12 +67,14 @@ pub fn execute( min_amount_out, channel, None, + timeout, ), ExecuteMsg::AddLiquidity { token_1_liquidity, token_2_liquidity, slippage_tolerance, channel, + timeout, } => execute::add_liquidity_request( deps, info, @@ -81,6 +84,7 @@ pub fn execute( slippage_tolerance, channel, None, + timeout, ), ExecuteMsg::Receive(msg) => execute::receive_cw20(deps, env, info, msg), ExecuteMsg::Callback(callback_msg) => { diff --git a/contracts/liquidity/pool/src/execute.rs b/contracts/liquidity/pool/src/execute.rs index edede367..f92a2460 100644 --- a/contracts/liquidity/pool/src/execute.rs +++ b/contracts/liquidity/pool/src/execute.rs @@ -1,6 +1,6 @@ use cosmwasm_std::{ - ensure, from_json, to_json_binary, CosmosMsg, DepsMut, Env, IbcMsg, IbcTimeout, MessageInfo, - Response, Uint128, WasmMsg, + ensure, from_json, to_json_binary, CosmosMsg, DepsMut, Env, IbcTimeout, MessageInfo, Response, + Uint128, WasmMsg, }; use cw20::Cw20ReceiveMsg; use euclid::{ @@ -9,9 +9,9 @@ use euclid::{ msgs::{factory, pool::Cw20HookMsg}, pool::LiquidityResponse, swap::{self, SwapResponse}, + timeout::get_timeout, token::TokenInfo, }; -use euclid_ibc::msg::IbcExecuteMsg; use crate::state::{ generate_liquidity_req, generate_swap_req, PENDING_LIQUIDITY, PENDING_SWAPS, STATE, @@ -28,6 +28,7 @@ pub fn execute_swap_request( min_amount_out: Uint128, channel: String, msg_sender: Option, + timeout: Option, ) -> Result { let state = STATE.load(deps.storage)?; @@ -82,7 +83,8 @@ pub fn execute_swap_request( // Get alternative token let asset_out: TokenInfo = state.pair_info.get_other_token(asset.clone()); - let timeout = IbcTimeout::with_timestamp(env.block.time.plus_seconds(60)); + let timeout_duration = get_timeout(timeout)?; + let timeout = IbcTimeout::with_timestamp(env.block.time.plus_seconds(timeout_duration)); let swap_info = generate_swap_req(deps, sender, asset, asset_out, asset_amount, timeout)?; let msg = FactoryExecuteMsg::ExecuteSwap { @@ -91,6 +93,7 @@ pub fn execute_swap_request( min_amount_out, channel, swap_id: swap_info.swap_id, + timeout: Some(timeout_duration), }; let msg = WasmMsg::Execute { @@ -204,6 +207,7 @@ pub fn receive_cw20( asset, min_amount_out, channel, + timeout, } => { let contract_adr = info.sender.clone(); @@ -224,6 +228,7 @@ pub fn receive_cw20( min_amount_out, channel, Some(cw20_msg.sender), + timeout, ) } } @@ -239,6 +244,7 @@ pub fn add_liquidity_request( slippage_tolerance: u64, channel: String, msg_sender: Option, + timeout: Option, ) -> Result { let state = STATE.load(deps.storage)?; @@ -319,6 +325,7 @@ pub fn add_liquidity_request( slippage_tolerance, channel, liquidity_id: liquidity_info.liquidity_id, + timeout, }; let msg = WasmMsg::Execute { diff --git a/packages/euclid/src/error.rs b/packages/euclid/src/error.rs index 8f69dd38..b4656330 100644 --- a/packages/euclid/src/error.rs +++ b/packages/euclid/src/error.rs @@ -57,6 +57,9 @@ pub enum ContractError { #[error("Invalid Liquidity Ratio")] InvalidLiquidityRatio {}, + #[error("Invalid Timeout")] + InvalidTimeout {}, + #[error("Slippage Tolerance must be between 0 and 100")] InvalidSlippageTolerance {}, diff --git a/packages/euclid/src/lib.rs b/packages/euclid/src/lib.rs index 99f5a0c9..2f9603ba 100644 --- a/packages/euclid/src/lib.rs +++ b/packages/euclid/src/lib.rs @@ -5,4 +5,5 @@ pub mod liquidity; pub mod msgs; pub mod pool; pub mod swap; +pub mod timeout; pub mod token; diff --git a/packages/euclid/src/msgs/factory.rs b/packages/euclid/src/msgs/factory.rs index 86877959..ac6a7a34 100644 --- a/packages/euclid/src/msgs/factory.rs +++ b/packages/euclid/src/msgs/factory.rs @@ -17,6 +17,7 @@ pub enum ExecuteMsg { RequestPoolCreation { pair_info: PairInfo, channel: String, + timeout: Option, }, ExecuteSwap { asset: Token, @@ -24,6 +25,7 @@ pub enum ExecuteMsg { min_amount_out: Uint128, channel: String, swap_id: String, + timeout: Option, }, // Add Liquidity Request to the VLP AddLiquidity { @@ -32,6 +34,7 @@ pub enum ExecuteMsg { slippage_tolerance: u64, channel: String, liquidity_id: String, + timeout: Option, }, } diff --git a/packages/euclid/src/msgs/pool.rs b/packages/euclid/src/msgs/pool.rs index 3cdd7441..f579f305 100644 --- a/packages/euclid/src/msgs/pool.rs +++ b/packages/euclid/src/msgs/pool.rs @@ -24,6 +24,7 @@ pub enum ExecuteMsg { asset_amount: Uint128, min_amount_out: Uint128, channel: String, + timeout: Option, }, // Add Liquidity Request to the VLP @@ -32,6 +33,7 @@ pub enum ExecuteMsg { token_2_liquidity: Uint128, slippage_tolerance: u64, channel: String, + timeout: Option, }, // Recieve CW20 TOKENS structure @@ -94,6 +96,7 @@ pub enum Cw20HookMsg { asset: TokenInfo, min_amount_out: Uint128, channel: String, + timeout: Option, }, } diff --git a/packages/euclid/src/timeout.rs b/packages/euclid/src/timeout.rs new file mode 100644 index 00000000..e067c3eb --- /dev/null +++ b/packages/euclid/src/timeout.rs @@ -0,0 +1,18 @@ +use cosmwasm_std::ensure; + +use crate::error::ContractError; + +/// Ensures that the timeout is between 30 and 240 seconds, and if not provided, defaults the timeout to 60 seconds +pub fn get_timeout(timeout: Option) -> Result { + if let Some(timeout) = timeout { + // Validate that the timeout is between 30 and 240 seconds inclusive + ensure!( + timeout.ge(&30) && timeout.le(&240), + ContractError::InvalidTimeout {} + ); + Ok(timeout) + } else { + // Default timeout to 60 seconds if not provided + Ok(60) + } +} From 929b4db7ba3e1c7e2131cce9ebac34d2169138ff Mon Sep 17 00:00:00 2001 From: Joe Monem Date: Wed, 29 May 2024 18:06:13 +0300 Subject: [PATCH 2/2] test: test_get_timeout --- packages/euclid/src/error.rs | 2 +- packages/euclid/src/timeout.rs | 65 ++++++++++++++++++++++++++++++++++ 2 files changed, 66 insertions(+), 1 deletion(-) diff --git a/packages/euclid/src/error.rs b/packages/euclid/src/error.rs index b4656330..dd61d278 100644 --- a/packages/euclid/src/error.rs +++ b/packages/euclid/src/error.rs @@ -7,7 +7,7 @@ use thiserror::Error; use crate::{liquidity::LiquidityTxInfo, pool::PoolRequest, swap::SwapInfo}; #[derive(Error, Debug)] pub enum Never {} -#[derive(Error, Debug)] +#[derive(Error, Debug, PartialEq)] pub enum ContractError { #[error("{0}")] Std(#[from] StdError), diff --git a/packages/euclid/src/timeout.rs b/packages/euclid/src/timeout.rs index e067c3eb..d5000ac6 100644 --- a/packages/euclid/src/timeout.rs +++ b/packages/euclid/src/timeout.rs @@ -16,3 +16,68 @@ pub fn get_timeout(timeout: Option) -> Result { Ok(60) } } + +#[cfg(test)] +mod tests { + use super::*; + + struct TestGetTimeout { + name: &'static str, + timeout: Option, + expected_error: Option, + expected_result: Option, + } + + #[test] + fn test_get_timeout() { + let test_cases = vec![ + TestGetTimeout { + name: "Empty timeout", + timeout: None, + expected_error: None, + expected_result: Some(60), + }, + TestGetTimeout { + name: "Timeout below 30", + timeout: Some(29), + expected_error: Some(ContractError::InvalidTimeout {}), + expected_result: None, + }, + TestGetTimeout { + name: "Timeout above 240", + timeout: Some(241), + expected_error: Some(ContractError::InvalidTimeout {}), + expected_result: None, + }, + TestGetTimeout { + name: "Timeout at 240", + timeout: Some(240), + expected_error: None, + expected_result: Some(240), + }, + TestGetTimeout { + name: "Timeout at 30", + timeout: Some(30), + expected_error: None, + expected_result: Some(30), + }, + TestGetTimeout { + name: "Timeout between 30 and 240", + timeout: Some(80), + expected_error: None, + expected_result: Some(80), + }, + ]; + + for test in test_cases { + let res = get_timeout(test.timeout); + + if let Some(err) = test.expected_error { + assert_eq!(res.unwrap_err(), err, "{}", test.name); + continue; + } else { + assert_eq!(res.unwrap(), test.expected_result.unwrap()) + } + } + } +}