From 6384c75bf70809c36da1f14699025bf2bb595f12 Mon Sep 17 00:00:00 2001 From: trung2891 Date: Fri, 4 Oct 2024 15:20:33 +0700 Subject: [PATCH 1/3] feat: support withdraw all protocol fee --- contracts/oraiswap-v3/src/contract.rs | 1 + .../oraiswap-v3/src/entrypoints/execute.rs | 66 ++++++++++++++++- contracts/oraiswap-v3/src/tests/helper.rs | 20 ++++++ .../oraiswap-v3/src/tests/protocol_fee.rs | 71 +++++++++++++++++++ .../oraiswap-v3-common/src/oraiswap_v3_msg.rs | 18 +++-- 5 files changed, 169 insertions(+), 7 deletions(-) diff --git a/contracts/oraiswap-v3/src/contract.rs b/contracts/oraiswap-v3/src/contract.rs index 5732e21..e3ada99 100644 --- a/contracts/oraiswap-v3/src/contract.rs +++ b/contracts/oraiswap-v3/src/contract.rs @@ -45,6 +45,7 @@ pub fn execute( match msg { ExecuteMsg::ChangeAdmin { new_admin } => change_admin(deps, info, new_admin), ExecuteMsg::WithdrawProtocolFee { pool_key } => withdraw_protocol_fee(deps, info, pool_key), + ExecuteMsg::WithdrawAllProtocolFee {} => withdraw_all_protocol_fee(deps, info), ExecuteMsg::ChangeProtocolFee { protocol_fee } => { change_protocol_fee(deps, info, protocol_fee) } diff --git a/contracts/oraiswap-v3/src/entrypoints/execute.rs b/contracts/oraiswap-v3/src/entrypoints/execute.rs index 732ab31..9907c5e 100644 --- a/contracts/oraiswap-v3/src/entrypoints/execute.rs +++ b/contracts/oraiswap-v3/src/entrypoints/execute.rs @@ -2,7 +2,9 @@ use crate::state::{self, CONFIG, POOLS}; use oraiswap_v3_common::asset::{Asset, AssetInfo}; use oraiswap_v3_common::error::ContractError; use oraiswap_v3_common::incentives_fund_manager; -use oraiswap_v3_common::interface::{CalculateSwapResult, Cw721ReceiveMsg, SwapHop}; +use oraiswap_v3_common::interface::{ + CalculateSwapResult, Cw721ReceiveMsg, PoolWithPoolKey, SwapHop, +}; use oraiswap_v3_common::math::fee_growth::FeeGrowth; use oraiswap_v3_common::math::liquidity::Liquidity; use oraiswap_v3_common::math::percentage::Percentage; @@ -17,7 +19,8 @@ use super::{ transfer_nft, update_approvals, TimeStampExt, }; use cosmwasm_std::{ - attr, wasm_execute, Addr, Attribute, Binary, DepsMut, Env, MessageInfo, Response, + attr, wasm_execute, Addr, Attribute, Binary, DepsMut, Env, MessageInfo, Order, Response, + StdResult, }; use cw20::Expiration; use decimal::Decimal; @@ -54,6 +57,65 @@ pub fn change_admin( Ok(Response::new().add_attributes(event_attributes)) } +/// Allows an fee receiver to withdraw collected fees. +/// +/// +/// # Errors +/// - Reverts the call when the caller is an unauthorized receiver. +pub fn withdraw_all_protocol_fee( + deps: DepsMut, + info: MessageInfo, +) -> Result { + let pools: Vec = POOLS + .range_raw(deps.storage, None, None, Order::Ascending) + .map(|item| { + let (raw_key, pool) = item?; + Ok(PoolWithPoolKey { + pool_key: PoolKey::from_bytes(&raw_key)?, + pool, + }) + }) + .collect::>()?; + let mut attrs: Vec = vec![ + attr("action", "withdraw_protocol_fee"), + attr("receiver", info.sender.as_str()), + ]; + let mut msgs = vec![]; + + for mut pool_info in pools { + if pool_info.pool.fee_receiver != info.sender { + continue; + } + let pool_key_db = pool_info.pool_key.key(); + let (fee_protocol_token_x, fee_protocol_token_y) = pool_info.pool.withdraw_protocol_fee(); + POOLS.save(deps.storage, &pool_key_db, &pool_info.pool)?; + + let asset_0 = Asset { + info: AssetInfo::from_denom(deps.api, pool_info.pool_key.token_x.as_str()), + amount: fee_protocol_token_x.into(), + }; + + let asset_1 = Asset { + info: AssetInfo::from_denom(deps.api, pool_info.pool_key.token_y.as_str()), + amount: fee_protocol_token_y.into(), + }; + + asset_0.transfer(&mut msgs, &info)?; + asset_1.transfer(&mut msgs, &info)?; + + let mut event_attributes = vec![ + attr("action", "withdraw_protocol_fee"), + attr("pool_key", pool_info.pool_key.to_string()), + attr("token_x", fee_protocol_token_x.to_string()), + attr("token_y", fee_protocol_token_y.to_string()), + ]; + + attrs.append(&mut event_attributes); + } + + Ok(Response::new().add_messages(msgs).add_attributes(attrs)) +} + /// Allows an fee receiver to withdraw collected fees. /// /// # Parameters diff --git a/contracts/oraiswap-v3/src/tests/helper.rs b/contracts/oraiswap-v3/src/tests/helper.rs index 7a3e946..1ddc08b 100644 --- a/contracts/oraiswap-v3/src/tests/helper.rs +++ b/contracts/oraiswap-v3/src/tests/helper.rs @@ -184,6 +184,19 @@ impl MockApp { ) } + pub fn withdraw_all_protocol_fee( + &mut self, + sender: &str, + dex: &str, + ) -> MockResult { + self.execute( + Addr::unchecked(sender), + Addr::unchecked(dex), + &oraiswap_v3_msg::ExecuteMsg::WithdrawAllProtocolFee {}, + &[], + ) + } + pub fn change_fee_receiver( &mut self, sender: &str, @@ -1194,6 +1207,13 @@ pub mod macros { } pub(crate) use withdraw_protocol_fee; + macro_rules! withdraw_all_protocol_fee { + ($app:ident, $dex_address:expr, $caller:tt) => {{ + $app.withdraw_all_protocol_fee($caller, $dex_address.as_str()) + }}; + } + pub(crate) use withdraw_all_protocol_fee; + macro_rules! change_fee_receiver { ($app:ident, $dex_address:expr, $pool_key:expr, $fee_receiver:tt, $caller:tt) => {{ $app.change_fee_receiver($caller, $dex_address.as_str(), &$pool_key, $fee_receiver) diff --git a/contracts/oraiswap-v3/src/tests/protocol_fee.rs b/contracts/oraiswap-v3/src/tests/protocol_fee.rs index fcfa156..92b998c 100644 --- a/contracts/oraiswap-v3/src/tests/protocol_fee.rs +++ b/contracts/oraiswap-v3/src/tests/protocol_fee.rs @@ -48,6 +48,45 @@ fn test_protocol_fee() { ); } +#[test] +fn test_withdraw_all_protocol_fee() { + let (mut app, accounts) = MockApp::new(&[ + ("alice", &coins(100_000_000_000, FEE_DENOM)), + ("bob", &coins(100_000_000_000, FEE_DENOM)), + ]); + let alice = &accounts[0]; + let bob = &accounts[1]; + + let (dex, token_x, token_y) = init_dex_and_tokens!(app, alice); + init_basic_pool!(app, dex, token_x, token_y, alice); + init_basic_position!(app, dex, token_x, token_y, alice); + init_basic_swap!(app, dex, token_x, token_y, alice, bob); + + let fee_tier = FeeTier::new(Percentage::from_scale(6, 3), 10).unwrap(); + + withdraw_all_protocol_fee!(app, dex, alice).unwrap(); + + let amount_x = balance_of!(app, token_x, alice); + let amount_y = balance_of!(app, token_y, alice); + assert_eq!(amount_x, 9999999501); + assert_eq!(amount_y, 9999999000); + + let amount_x = balance_of!(app, token_x, dex); + let amount_y = balance_of!(app, token_y, dex); + assert_eq!(amount_x, 1499); + assert_eq!(amount_y, 7); + + let pool_after_withdraw = get_pool!(app, dex, token_x, token_y, fee_tier).unwrap(); + assert_eq!( + pool_after_withdraw.fee_protocol_token_x, + TokenAmount::new(0) + ); + assert_eq!( + pool_after_withdraw.fee_protocol_token_y, + TokenAmount::new(0) + ); +} + #[test] fn test_protocol_fee_not_admin() { let (mut app, accounts) = MockApp::new(&[ @@ -78,6 +117,38 @@ fn test_protocol_fee_not_admin() { .contains(&ContractError::Unauthorized {}.to_string())); } +#[test] +fn test_withdraw_all_protocol_fee_not_admin() { + let (mut app, accounts) = MockApp::new(&[ + ("alice", &coins(100_000_000_000, FEE_DENOM)), + ("bob", &coins(100_000_000_000, FEE_DENOM)), + ]); + let alice = &accounts[0]; + let bob = &accounts[1]; + let (dex, token_x, token_y) = init_dex_and_tokens!(app, alice); + init_basic_pool!(app, dex, token_x, token_y, alice); + init_basic_position!(app, dex, token_x, token_y, alice); + init_basic_swap!(app, dex, token_x, token_y, alice, bob); + let fee_tier = FeeTier::new(Percentage::from_scale(6, 3), 10).unwrap(); + + withdraw_all_protocol_fee!(app, dex, bob).unwrap(); + + let amount_x = balance_of!(app, token_x, alice); + let amount_y = balance_of!(app, token_y, alice); + assert_eq!(amount_x, 9999999500); + assert_eq!(amount_y, 9999999000); + + let pool_after_withdraw = get_pool!(app, dex, token_x, token_y, fee_tier).unwrap(); + assert_eq!( + pool_after_withdraw.fee_protocol_token_x, + TokenAmount::new(1) + ); + assert_eq!( + pool_after_withdraw.fee_protocol_token_y, + TokenAmount::new(0) + ); +} + #[test] fn test_withdraw_fee_not_deployer() { let (mut app, accounts) = MockApp::new(&[ diff --git a/packages/oraiswap-v3-common/src/oraiswap_v3_msg.rs b/packages/oraiswap-v3-common/src/oraiswap_v3_msg.rs index 33e72a2..4ebf821 100644 --- a/packages/oraiswap-v3-common/src/oraiswap_v3_msg.rs +++ b/packages/oraiswap-v3-common/src/oraiswap_v3_msg.rs @@ -1,14 +1,21 @@ #![allow(unused_imports)] +use crate::asset::{Asset, AssetInfo}; use cosmwasm_schema::{cw_serde, QueryResponses}; use cosmwasm_std::{Addr, Binary, Uint64}; use cw20::Expiration; -use crate::asset::{Asset, AssetInfo}; -use crate::{interface::{ - AllNftInfoResponse, ApprovedForAllResponse, NftInfoResponse, NumTokensResponse, OwnerOfResponse, PoolWithPoolKey, PositionTick, QuoteResult, SwapHop, TokensResponse -}, math::{liquidity::Liquidity, percentage::Percentage, sqrt_price::SqrtPrice, token_amount::TokenAmount}, storage::{FeeTier, LiquidityTick, Pool, PoolKey, Position, Tick}}; +use crate::{ + interface::{ + AllNftInfoResponse, ApprovedForAllResponse, NftInfoResponse, NumTokensResponse, + OwnerOfResponse, PoolWithPoolKey, PositionTick, QuoteResult, SwapHop, TokensResponse, + }, + math::{ + liquidity::Liquidity, percentage::Percentage, sqrt_price::SqrtPrice, + token_amount::TokenAmount, + }, + storage::{FeeTier, LiquidityTick, Pool, PoolKey, Position, Tick}, +}; #[allow(unused_imports)] - #[cw_serde] pub struct InstantiateMsg { pub protocol_fee: Percentage, @@ -33,6 +40,7 @@ pub enum ExecuteMsg { WithdrawProtocolFee { pool_key: PoolKey, }, + WithdrawAllProtocolFee {}, ChangeProtocolFee { protocol_fee: Percentage, }, From 4ff5c11592d12ad730abc4ca25d44f74905dc44a Mon Sep 17 00:00:00 2001 From: trung2891 Date: Fri, 4 Oct 2024 15:21:20 +0700 Subject: [PATCH 2/3] refactor --- contracts/oraiswap-v3/src/entrypoints/execute.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/contracts/oraiswap-v3/src/entrypoints/execute.rs b/contracts/oraiswap-v3/src/entrypoints/execute.rs index 9907c5e..5d7c5a9 100644 --- a/contracts/oraiswap-v3/src/entrypoints/execute.rs +++ b/contracts/oraiswap-v3/src/entrypoints/execute.rs @@ -104,7 +104,6 @@ pub fn withdraw_all_protocol_fee( asset_1.transfer(&mut msgs, &info)?; let mut event_attributes = vec![ - attr("action", "withdraw_protocol_fee"), attr("pool_key", pool_info.pool_key.to_string()), attr("token_x", fee_protocol_token_x.to_string()), attr("token_y", fee_protocol_token_y.to_string()), From 54af5935f2ed6a71bcf7772e4e124ee430949feb Mon Sep 17 00:00:00 2001 From: trung2891 Date: Fri, 4 Oct 2024 15:33:47 +0700 Subject: [PATCH 3/3] chore: add fee receiver on withdraw fee --- contracts/oraiswap-v3/src/contract.rs | 4 +- .../oraiswap-v3/src/entrypoints/execute.rs | 9 +++- contracts/oraiswap-v3/src/tests/helper.rs | 7 +-- .../oraiswap-v3/src/tests/protocol_fee.rs | 47 +++++++++++++++++-- .../oraiswap-v3-common/src/oraiswap_v3_msg.rs | 4 +- 5 files changed, 61 insertions(+), 10 deletions(-) diff --git a/contracts/oraiswap-v3/src/contract.rs b/contracts/oraiswap-v3/src/contract.rs index e3ada99..52fff9c 100644 --- a/contracts/oraiswap-v3/src/contract.rs +++ b/contracts/oraiswap-v3/src/contract.rs @@ -45,7 +45,9 @@ pub fn execute( match msg { ExecuteMsg::ChangeAdmin { new_admin } => change_admin(deps, info, new_admin), ExecuteMsg::WithdrawProtocolFee { pool_key } => withdraw_protocol_fee(deps, info, pool_key), - ExecuteMsg::WithdrawAllProtocolFee {} => withdraw_all_protocol_fee(deps, info), + ExecuteMsg::WithdrawAllProtocolFee { receiver } => { + withdraw_all_protocol_fee(deps, info, receiver) + } ExecuteMsg::ChangeProtocolFee { protocol_fee } => { change_protocol_fee(deps, info, protocol_fee) } diff --git a/contracts/oraiswap-v3/src/entrypoints/execute.rs b/contracts/oraiswap-v3/src/entrypoints/execute.rs index 5d7c5a9..5eeb907 100644 --- a/contracts/oraiswap-v3/src/entrypoints/execute.rs +++ b/contracts/oraiswap-v3/src/entrypoints/execute.rs @@ -64,7 +64,8 @@ pub fn change_admin( /// - Reverts the call when the caller is an unauthorized receiver. pub fn withdraw_all_protocol_fee( deps: DepsMut, - info: MessageInfo, + mut info: MessageInfo, + receiver: Option, ) -> Result { let pools: Vec = POOLS .range_raw(deps.storage, None, None, Order::Ascending) @@ -81,9 +82,13 @@ pub fn withdraw_all_protocol_fee( attr("receiver", info.sender.as_str()), ]; let mut msgs = vec![]; + let sender = info.sender.clone(); + if let Some(receiver) = receiver { + info.sender = receiver; + } for mut pool_info in pools { - if pool_info.pool.fee_receiver != info.sender { + if pool_info.pool.fee_receiver != sender { continue; } let pool_key_db = pool_info.pool_key.key(); diff --git a/contracts/oraiswap-v3/src/tests/helper.rs b/contracts/oraiswap-v3/src/tests/helper.rs index 1ddc08b..a938111 100644 --- a/contracts/oraiswap-v3/src/tests/helper.rs +++ b/contracts/oraiswap-v3/src/tests/helper.rs @@ -188,11 +188,12 @@ impl MockApp { &mut self, sender: &str, dex: &str, + receiver: Option, ) -> MockResult { self.execute( Addr::unchecked(sender), Addr::unchecked(dex), - &oraiswap_v3_msg::ExecuteMsg::WithdrawAllProtocolFee {}, + &oraiswap_v3_msg::ExecuteMsg::WithdrawAllProtocolFee { receiver }, &[], ) } @@ -1208,8 +1209,8 @@ pub mod macros { pub(crate) use withdraw_protocol_fee; macro_rules! withdraw_all_protocol_fee { - ($app:ident, $dex_address:expr, $caller:tt) => {{ - $app.withdraw_all_protocol_fee($caller, $dex_address.as_str()) + ($app:ident, $dex_address:expr,$receiver:expr, $caller:tt) => {{ + $app.withdraw_all_protocol_fee($caller, $dex_address.as_str(), $receiver) }}; } pub(crate) use withdraw_all_protocol_fee; diff --git a/contracts/oraiswap-v3/src/tests/protocol_fee.rs b/contracts/oraiswap-v3/src/tests/protocol_fee.rs index 92b998c..a664d33 100644 --- a/contracts/oraiswap-v3/src/tests/protocol_fee.rs +++ b/contracts/oraiswap-v3/src/tests/protocol_fee.rs @@ -1,4 +1,4 @@ -use cosmwasm_std::coins; +use cosmwasm_std::{coins, Addr}; use decimal::*; use crate::tests::helper::{macros::*, MockApp, FEE_DENOM}; @@ -64,7 +64,7 @@ fn test_withdraw_all_protocol_fee() { let fee_tier = FeeTier::new(Percentage::from_scale(6, 3), 10).unwrap(); - withdraw_all_protocol_fee!(app, dex, alice).unwrap(); + withdraw_all_protocol_fee!(app, dex, None, alice).unwrap(); let amount_x = balance_of!(app, token_x, alice); let amount_y = balance_of!(app, token_y, alice); @@ -87,6 +87,47 @@ fn test_withdraw_all_protocol_fee() { ); } +#[test] +fn test_withdraw_all_protocol_fee_with_receiver() { + let (mut app, accounts) = MockApp::new(&[ + ("alice", &coins(100_000_000_000, FEE_DENOM)), + ("bob", &coins(100_000_000_000, FEE_DENOM)), + ("charlie", &coins(100_000_000_000, FEE_DENOM)), + ]); + let alice = &accounts[0]; + let bob = &accounts[1]; + let charlie = &accounts[2]; + + let (dex, token_x, token_y) = init_dex_and_tokens!(app, alice); + init_basic_pool!(app, dex, token_x, token_y, alice); + init_basic_position!(app, dex, token_x, token_y, alice); + init_basic_swap!(app, dex, token_x, token_y, alice, bob); + + let fee_tier = FeeTier::new(Percentage::from_scale(6, 3), 10).unwrap(); + + withdraw_all_protocol_fee!(app, dex, Some(Addr::unchecked(charlie)), alice).unwrap(); + + let amount_x = balance_of!(app, token_x, charlie); + let amount_y = balance_of!(app, token_y, charlie); + assert_eq!(amount_x, 1); + assert_eq!(amount_y, 0); + + let amount_x = balance_of!(app, token_x, dex); + let amount_y = balance_of!(app, token_y, dex); + assert_eq!(amount_x, 1499); + assert_eq!(amount_y, 7); + + let pool_after_withdraw = get_pool!(app, dex, token_x, token_y, fee_tier).unwrap(); + assert_eq!( + pool_after_withdraw.fee_protocol_token_x, + TokenAmount::new(0) + ); + assert_eq!( + pool_after_withdraw.fee_protocol_token_y, + TokenAmount::new(0) + ); +} + #[test] fn test_protocol_fee_not_admin() { let (mut app, accounts) = MockApp::new(&[ @@ -131,7 +172,7 @@ fn test_withdraw_all_protocol_fee_not_admin() { init_basic_swap!(app, dex, token_x, token_y, alice, bob); let fee_tier = FeeTier::new(Percentage::from_scale(6, 3), 10).unwrap(); - withdraw_all_protocol_fee!(app, dex, bob).unwrap(); + withdraw_all_protocol_fee!(app, dex, Some(Addr::unchecked(alice)), bob).unwrap(); let amount_x = balance_of!(app, token_x, alice); let amount_y = balance_of!(app, token_y, alice); diff --git a/packages/oraiswap-v3-common/src/oraiswap_v3_msg.rs b/packages/oraiswap-v3-common/src/oraiswap_v3_msg.rs index 4ebf821..e866890 100644 --- a/packages/oraiswap-v3-common/src/oraiswap_v3_msg.rs +++ b/packages/oraiswap-v3-common/src/oraiswap_v3_msg.rs @@ -40,7 +40,9 @@ pub enum ExecuteMsg { WithdrawProtocolFee { pool_key: PoolKey, }, - WithdrawAllProtocolFee {}, + WithdrawAllProtocolFee { + receiver: Option, + }, ChangeProtocolFee { protocol_fee: Percentage, },