diff --git a/package/plus/amm-v3-rebalancer/Cargo.toml b/package/plus/amm-v3-rebalancer/Cargo.toml index 1bfc511..59f3e6d 100644 --- a/package/plus/amm-v3-rebalancer/Cargo.toml +++ b/package/plus/amm-v3-rebalancer/Cargo.toml @@ -23,11 +23,16 @@ library = [] backtraces = ["cosmwasm-std/backtraces"] [dependencies] -cosmwasm-std = { version = "1.5" } +cosmwasm-std = { version = "1.5", features = ["stargate"] } cosmwasm-storage = { version = "1.5", features = ["iterator"] } cosmwasm-schema = { version = "1.5" } +cw20 = { version = "1.0.1" } schemars = "0.8" serde = { version = "1.0.204", default-features = false, features = ["derive"] } +oraiswap-v3-common = { git = "https://github.com/oraichain/oraiswap-v3.git", rev = "dc94a86" } +cosmos-sdk-proto = { version = "=0.19.0", default-features = false, features = [ + "cosmwasm", +] } thiserror = "1.0" cosmwasm-crypto = "0.14" sha3 = "0.10" diff --git a/package/plus/amm-v3-rebalancer/src/asset.rs b/package/plus/amm-v3-rebalancer/src/asset.rs new file mode 100644 index 0000000..4936135 --- /dev/null +++ b/package/plus/amm-v3-rebalancer/src/asset.rs @@ -0,0 +1,113 @@ +use cosmos_sdk_proto::{ + cosmos::{authz::v1beta1::MsgExec, bank::v1beta1::MsgSend, base::v1beta1::Coin as ProtoCoin}, + traits::{Message, MessageExt}, +}; +use cosmwasm_schema::cw_serde; +use cosmwasm_std::{to_json_binary, BankMsg, Binary, Coin, CosmosMsg, Env, Uint128, WasmMsg}; +use cw20::Cw20ExecuteMsg; +use oraiswap_v3_common::asset::AssetInfo; + +use crate::error::ContractError; + +#[cw_serde] +pub struct Asset { + pub info: AssetInfo, + pub amount: Uint128, +} + +impl Asset { + pub fn new(info: AssetInfo, amount: Uint128) -> Self { + Self { info, amount } + } + + pub fn transfer( + &self, + msgs: &mut Vec, + recipient: String, + ) -> Result<(), ContractError> { + if !self.amount.is_zero() { + match &self.info { + AssetInfo::Token { contract_addr } => { + msgs.push( + WasmMsg::Execute { + contract_addr: contract_addr.to_string(), + msg: to_json_binary(&Cw20ExecuteMsg::Transfer { + recipient, + amount: self.amount, + })?, + funds: vec![], + } + .into(), + ); + } + AssetInfo::NativeToken { denom } => msgs.push( + BankMsg::Send { + to_address: recipient, + amount: vec![Coin { + amount: self.amount, + denom: denom.to_string(), + }], + } + .into(), + ), + } + } + Ok(()) + } + + pub fn transfer_from( + &self, + env: &Env, + msgs: &mut Vec, + allower: String, + recipient: String, + ) -> Result<(), ContractError> { + if !self.amount.is_zero() { + match &self.info { + AssetInfo::Token { contract_addr } => { + msgs.push( + WasmMsg::Execute { + contract_addr: contract_addr.to_string(), + msg: to_json_binary(&Cw20ExecuteMsg::TransferFrom { + owner: allower.to_string(), + recipient, + amount: self.amount, + })?, + funds: vec![], + } + .into(), + ); + } + AssetInfo::NativeToken { denom } => { + let send = MsgSend { + from_address: allower.to_string(), + to_address: recipient.to_string(), + amount: vec![ProtoCoin { + denom: denom.clone(), + amount: self.amount.to_string(), + }], + }; + let send_any_result = send.to_any(); + if send_any_result.is_err() { + return Ok(()); + } + let stargate_value = Binary::from( + MsgExec { + grantee: env.contract.address.to_string(), + msgs: vec![send_any_result.unwrap()], + } + .encode_to_vec(), + ); + + let stargate = CosmosMsg::Stargate { + type_url: "/cosmos.authz.v1beta1.MsgExec".to_string(), + value: stargate_value, + }; + msgs.push(stargate) + } + } + } + + Ok(()) + } +} diff --git a/package/plus/amm-v3-rebalancer/src/contract.rs b/package/plus/amm-v3-rebalancer/src/contract.rs index 386585a..2c33687 100644 --- a/package/plus/amm-v3-rebalancer/src/contract.rs +++ b/package/plus/amm-v3-rebalancer/src/contract.rs @@ -1,7 +1,12 @@ #[cfg(not(feature = "library"))] use cosmwasm_std::{ - entry_point, to_json_binary, Addr, Binary, Deps, DepsMut, Env, MessageInfo, - Response, StdResult, + entry_point, to_json_binary, Addr, Binary, CosmosMsg, Deps, DepsMut, Env, MessageInfo, + Response, StdResult, WasmMsg, +}; +use cw20::{BalanceResponse as Cw20BalanceResponse, Cw20QueryMsg::Balance as Cw20Balance}; +use oraiswap_v3_common::{ + oraiswap_v3_msg::{ExecuteMsg as OraiswapV3ExecuteMsg, QueryMsg as OraiswapV3QueryMsg}, + storage::{Pool, PoolKey, Position}, }; use crate::error::ContractError; @@ -34,7 +39,7 @@ pub fn instantiate( #[cfg_attr(not(feature = "library"), entry_point)] pub fn execute( deps: DepsMut, - _env: Env, + env: Env, info: MessageInfo, msg: ExecuteMsg, ) -> Result { @@ -45,6 +50,8 @@ pub fn execute( wallet, amm_v3, } => update_config(deps, info, owner, executor, wallet, amm_v3), + ExecuteMsg::BurnPosition { token_id } => burn_position(deps, info, env, token_id), + ExecuteMsg::SendToken { denom } => send_token(deps, info, env, denom), } } @@ -78,6 +85,115 @@ fn update_config( Ok(Response::new().add_attributes(vec![("action", "update_config")])) } +fn burn_position( + deps: DepsMut, + info: MessageInfo, + env: Env, + token_id: u64, +) -> Result { + let config = CONFIG.load(deps.storage)?; + if info.sender != config.executor { + return Err(ContractError::Unauthorized {}); + } + let position_info: Position = deps.querier.query_wasm_smart( + config.amm_v3.to_string(), + &OraiswapV3QueryMsg::NftInfo { token_id }, + )?; + + let mut messages = vec![ + CosmosMsg::Wasm(WasmMsg::Execute { + contract_addr: config.amm_v3.to_string(), + msg: to_json_binary(&OraiswapV3ExecuteMsg::Burn { token_id })?, + funds: vec![], + }), + CosmosMsg::Wasm(WasmMsg::Execute { + contract_addr: env.contract.address.to_string(), + msg: to_json_binary(&ExecuteMsg::SendToken { + denom: position_info.clone().pool_key.token_x, + })?, + funds: vec![], + }), + CosmosMsg::Wasm(WasmMsg::Execute { + contract_addr: env.contract.address.to_string(), + msg: to_json_binary(&ExecuteMsg::SendToken { + denom: position_info.clone().pool_key.token_y, + })?, + funds: vec![], + }), + ]; + + let pool_info: Pool = deps.querier.query_wasm_smart( + config.amm_v3.to_string(), + &OraiswapV3QueryMsg::Pool { + token_0: position_info.clone().pool_key.token_x, + token_1: position_info.clone().pool_key.token_y, + fee_tier: position_info.clone().pool_key.fee_tier, + }, + )?; + + for incentive in pool_info.incentives.iter() { + messages.push( + CosmosMsg::Wasm(WasmMsg::Execute { + contract_addr: env.contract.address.to_string(), + msg: to_json_binary(&ExecuteMsg::SendToken { + denom: incentive.reward_token.denom(), + })?, + funds: vec![], + }) + ) + } + + Ok(Response::new() + .add_attributes(vec![ + ("action", "remove_position"), + ("token_id", &token_id.to_string()), + ]) + .add_messages(messages)) +} + +fn send_token( + deps: DepsMut, + info: MessageInfo, + env: Env, + denom: String, +) -> Result { + if info.sender != env.contract.address { + return Err(ContractError::Unauthorized {}); + } + + match deps.api.addr_validate(&denom) { + Ok(_) => { + let bal: Cw20BalanceResponse = deps.querier.query_wasm_smart( + denom, + &Cw20Balance { + address: env.contract.address.to_string(), + }, + )?; + if bal.balance.is_zero() { + return Ok(Response::default()); + } + + return Ok(Response::new().add_attributes(vec![ + ("action", "send_token"), + ("token", "denom"), + ("amount", &bal.balance.to_string()), + ])); + } + Err(_) => { + let bal = deps.querier.query_balance(env.contract.address, denom)?; + if bal.amount.is_zero() { + return Ok(Response::default()); + } + + return Ok(Response::new().add_attributes(vec![ + ("action", "send_token"), + ("token", "denom"), + ("amount", &bal.amount.to_string()), + ])); + } + } +} + #[cfg_attr(not(feature = "library"), entry_point)] pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { match msg { diff --git a/package/plus/amm-v3-rebalancer/src/error.rs b/package/plus/amm-v3-rebalancer/src/error.rs index dc19f10..6a3007b 100644 --- a/package/plus/amm-v3-rebalancer/src/error.rs +++ b/package/plus/amm-v3-rebalancer/src/error.rs @@ -1,11 +1,15 @@ use cosmwasm_std::StdError; use thiserror::Error; +use oraiswap_v3_common::error::ContractError as OraiswapV3Error; #[derive(Error, Debug)] pub enum ContractError { #[error("{0}")] Std(#[from] StdError), + #[error("{0}")] + V3Error(#[from] OraiswapV3Error), + #[error("Unauthorized")] Unauthorized {}, } diff --git a/package/plus/amm-v3-rebalancer/src/lib.rs b/package/plus/amm-v3-rebalancer/src/lib.rs index a5abdbb..1916833 100644 --- a/package/plus/amm-v3-rebalancer/src/lib.rs +++ b/package/plus/amm-v3-rebalancer/src/lib.rs @@ -2,3 +2,5 @@ pub mod contract; pub mod error; pub mod msg; pub mod state; + +pub mod asset; diff --git a/package/plus/amm-v3-rebalancer/src/msg.rs b/package/plus/amm-v3-rebalancer/src/msg.rs index b93430a..48f8733 100644 --- a/package/plus/amm-v3-rebalancer/src/msg.rs +++ b/package/plus/amm-v3-rebalancer/src/msg.rs @@ -18,14 +18,20 @@ pub enum ExecuteMsg { executor: Option, wallet: Option, amm_v3: Option, - } + }, + BurnPosition { + token_id: u64, + }, + SendToken { + denom: String, + }, } #[cw_serde] #[derive(QueryResponses)] pub enum QueryMsg { #[returns(Config)] - Config + Config, } #[cw_serde]