From a42acbe91f25d046b7db96dfaeea8db9d4a408d4 Mon Sep 17 00:00:00 2001 From: Trinity Date: Fri, 6 Sep 2024 02:24:18 +0700 Subject: [PATCH 1/6] Add on close channel logic --- contracts/consumer/converter/src/contract.rs | 2 +- contracts/consumer/converter/src/error.rs | 6 +++ contracts/consumer/converter/src/ibc.rs | 24 +++++++-- .../consumer/virtual-staking/src/contract.rs | 49 ++++++++++++++++- .../consumer/virtual-staking/src/state.rs | 2 +- .../provider/external-staking/src/contract.rs | 53 ++++++++++++++++++- .../provider/external-staking/src/error.rs | 6 +++ .../provider/external-staking/src/ibc.rs | 22 ++++++-- packages/apis/src/virtual_staking_api.rs | 8 +++ packages/bindings/src/query.rs | 4 +- 10 files changed, 162 insertions(+), 14 deletions(-) diff --git a/contracts/consumer/converter/src/contract.rs b/contracts/consumer/converter/src/contract.rs index 98eb878c..7032f061 100644 --- a/contracts/consumer/converter/src/contract.rs +++ b/contracts/consumer/converter/src/contract.rs @@ -74,7 +74,7 @@ impl ConverterContract<'_> { remote_denom: String, virtual_staking_code_id: u64, admin: Option, - max_retrieve: u16, + max_retrieve: u32, ) -> Result { nonpayable(&ctx.info)?; // validate args diff --git a/contracts/consumer/converter/src/error.rs b/contracts/consumer/converter/src/error.rs index fb2cba7a..7a697920 100644 --- a/contracts/consumer/converter/src/error.rs +++ b/contracts/consumer/converter/src/error.rs @@ -26,6 +26,12 @@ pub enum ContractError { #[error("You must start the channel handshake on this side, it doesn't support OpenTry")] IbcOpenTryDisallowed, + #[error("IBC channels not match")] + IbcChannelNotMatch, + + #[error("You must start the channel close on provider side")] + IbcChannelCloseInitDisallowed, + #[error("Sent wrong denom over IBC: {sent}, expected {expected}")] WrongDenom { sent: String, expected: String }, diff --git a/contracts/consumer/converter/src/ibc.rs b/contracts/consumer/converter/src/ibc.rs index 90865843..a36d6daf 100644 --- a/contracts/consumer/converter/src/ibc.rs +++ b/contracts/consumer/converter/src/ibc.rs @@ -188,11 +188,29 @@ pub(crate) fn valset_update_msg( /// On closed channel, we take all tokens from reflect contract to this contract. /// We also delete the channel entry from accounts. pub fn ibc_channel_close( - _deps: DepsMut, + deps: DepsMut, _env: Env, - _msg: IbcChannelCloseMsg, + msg: IbcChannelCloseMsg, ) -> Result { - todo!(); + match msg { + IbcChannelCloseMsg::CloseInit { .. } => { + return Err(ContractError::IbcChannelCloseInitDisallowed) + } + IbcChannelCloseMsg::CloseConfirm { channel } => { + if channel.ne(&IBC_CHANNEL.load(deps.storage)?) { + return Err(ContractError::IbcChannelNotMatch); + } + } + }; + + let contract = ConverterContract::new(); + let msg = virtual_staking_api::sv::ExecMsg::HandleCloseChannel {}; + let msg = WasmMsg::Execute { + contract_addr: contract.virtual_stake.load(deps.storage)?.into(), + msg: to_json_binary(&msg)?, + funds: vec![], + }; + Ok(IbcBasicResponse::new().add_message(msg)) } #[cfg_attr(not(feature = "library"), entry_point)] diff --git a/contracts/consumer/virtual-staking/src/contract.rs b/contracts/consumer/virtual-staking/src/contract.rs index fe240257..2458a524 100644 --- a/contracts/consumer/virtual-staking/src/contract.rs +++ b/contracts/consumer/virtual-staking/src/contract.rs @@ -1,5 +1,6 @@ use std::cmp::Ordering; use std::collections::{BTreeMap, HashMap, HashSet}; +use std::u32; use cosmwasm_std::{ coin, ensure_eq, to_json_binary, Coin, CosmosMsg, CustomQuery, DepsMut, DistributionMsg, Env, @@ -74,7 +75,7 @@ impl VirtualStakingContract<'_> { pub fn instantiate( &self, ctx: InstantiateCtx, - max_retrieve: u16, + max_retrieve: u32, ) -> Result, ContractError> { nonpayable(&ctx.info)?; let denom = ctx.deps.querier.query_bonded_denom()?; @@ -612,6 +613,52 @@ impl VirtualStakingApi for VirtualStakingContract<'_> { Ok(Response::new().add_messages(msgs)) } + fn handle_close_channel( + &self, + ctx: ExecCtx, + ) -> Result, Self::Error> { + nonpayable(&ctx.info)?; + let ExecCtx { deps, env, info } = ctx; + let config = self.config.load(deps.storage)?; + ensure_eq!(info.sender, config.converter, ContractError::Unauthorized); // only the converter can call this + + let all_delegations = TokenQuerier::new(&deps.querier) + .all_delegations(env.contract.address.to_string(), u32::MAX)?; + + let mut msgs = vec![VirtualStakeMsg::DeleteAllScheduledTasks {}]; + for delegation in all_delegations.delegations.iter() { + let amount = Coin { + denom: config.denom.clone(), + amount: delegation.amount, + }; + msgs.push(VirtualStakeMsg::UpdateDelegation { + amount: amount.clone(), + is_deduct: true, + delegator: delegation.delegator.clone(), + validator: delegation.validator.clone(), + }); + msgs.push(VirtualStakeMsg::Unbond { + amount, + validator: delegation.validator.clone(), + }); + self.bond_requests + .save(deps.storage, &delegation.validator, &Uint128::zero())?; + } + + let requests: Vec<(String, Uint128)> = self + .bond_requests + .range( + deps.as_ref().storage, + None, + None, + cosmwasm_std::Order::Ascending, + ) + .collect::>()?; + self.bonded.save(deps.storage, &requests)?; + + Ok(Response::new().add_messages(msgs)) + } + // FIXME: need to handle custom message types and queries /** * This is called once per epoch to withdraw all rewards and rebalance the bonded tokens. diff --git a/contracts/consumer/virtual-staking/src/state.rs b/contracts/consumer/virtual-staking/src/state.rs index 76a6cb25..2563851d 100644 --- a/contracts/consumer/virtual-staking/src/state.rs +++ b/contracts/consumer/virtual-staking/src/state.rs @@ -11,5 +11,5 @@ pub struct Config { /// Maximum /// - pub max_retrieve: u16, + pub max_retrieve: u32, } diff --git a/contracts/provider/external-staking/src/contract.rs b/contracts/provider/external-staking/src/contract.rs index 115d1bef..292993f6 100644 --- a/contracts/provider/external-staking/src/contract.rs +++ b/contracts/provider/external-staking/src/contract.rs @@ -1,6 +1,6 @@ use cosmwasm_std::{ - coin, ensure, ensure_eq, to_json_binary, Coin, Decimal, DepsMut, Env, Event, IbcMsg, Order, - Response, StdResult, Storage, Uint128, Uint256, WasmMsg, + coin, ensure, ensure_eq, to_json_binary, Addr, Coin, Decimal, DepsMut, Env, Event, IbcMsg, + Order, Response, StdResult, Storage, Uint128, Uint256, WasmMsg, }; use cw2::set_contract_version; use cw_storage_plus::{Bounder, Item, Map}; @@ -497,6 +497,55 @@ impl ExternalStakingContract<'_> { Ok(event) } + pub(crate) fn handle_close_channel( + &self, + deps: DepsMut, + env: Env, + ) -> Result<(), ContractError> { + let stakes: Vec<((Addr, String), Stake)> = self + .stakes + .stake + .range( + deps.as_ref().storage, + None, + None, + cosmwasm_std::Order::Ascending, + ) + .collect::>()?; + + for ((user, validator), stake) in stakes.iter() { + let mut new_stake = stake.clone(); + let amount = new_stake.stake.low().clone(); + let unbond = PendingUnbond { + amount, + release_at: env.block.time, + }; + new_stake.pending_unbonds.push(unbond); + new_stake.stake = ValueRange::new_val(Uint128::zero()); + + let mut distribution = self + .distribution + .may_load(deps.storage, &validator)? + .unwrap_or_default(); + new_stake + .points_alignment + .stake_decreased(amount, distribution.points_per_stake); + + distribution.total_stake -= amount; + + // Save stake + self.stakes + .stake + .save(deps.storage, (user, validator), &new_stake)?; + + // Save distribution + self.distribution + .save(deps.storage, validator, &distribution)?; + } + + Ok(()) + } + /// In non-test code, this is called from `ibc_packet_ack` #[allow(clippy::too_many_arguments)] pub(crate) fn valset_update( diff --git a/contracts/provider/external-staking/src/error.rs b/contracts/provider/external-staking/src/error.rs index 05a337d0..092c1571 100644 --- a/contracts/provider/external-staking/src/error.rs +++ b/contracts/provider/external-staking/src/error.rs @@ -45,6 +45,12 @@ pub enum ContractError { #[error("You must start the channel handshake on the other side, it doesn't support OpenInit")] IbcOpenInitDisallowed, + #[error("IBC channels not match")] + IbcChannelNotMatch, + + #[error("You must start the channel close on this side")] + IbcChannelCloseConfirmDisallowed, + #[error("Invalid authorized endpoint: {0}")] InvalidEndpoint(String), diff --git a/contracts/provider/external-staking/src/ibc.rs b/contracts/provider/external-staking/src/ibc.rs index c8080f70..0e9c7c6b 100644 --- a/contracts/provider/external-staking/src/ibc.rs +++ b/contracts/provider/external-staking/src/ibc.rs @@ -104,11 +104,25 @@ pub fn ibc_channel_connect( #[cfg_attr(not(feature = "library"), entry_point)] pub fn ibc_channel_close( - _deps: DepsMut, - _env: Env, - _msg: IbcChannelCloseMsg, + deps: DepsMut, + env: Env, + msg: IbcChannelCloseMsg, ) -> Result { - todo!(); + match msg { + IbcChannelCloseMsg::CloseInit { channel } => { + if channel.ne(&IBC_CHANNEL.load(deps.storage)?) { + return Err(ContractError::IbcChannelNotMatch); + } + } + IbcChannelCloseMsg::CloseConfirm { .. } => { + return Err(ContractError::IbcChannelCloseConfirmDisallowed) + } + }; + + let contract = ExternalStakingContract::new(); + contract.handle_close_channel(deps, env)?; + + Ok(IbcBasicResponse::new()) } #[cfg_attr(not(feature = "library"), entry_point)] diff --git a/packages/apis/src/virtual_staking_api.rs b/packages/apis/src/virtual_staking_api.rs index 9377eee1..c541e875 100644 --- a/packages/apis/src/virtual_staking_api.rs +++ b/packages/apis/src/virtual_staking_api.rs @@ -65,6 +65,14 @@ pub trait VirtualStakingApi { amount: Coin, ) -> Result, Self::Error>; + /// Handle the close channel process. + /// Unbond all tokens from contract and delete scheduled tasks. + #[sv::msg(exec)] + fn handle_close_channel( + &self, + ctx: ExecCtx, + ) -> Result, Self::Error>; + /// SudoMsg::HandleEpoch{} should be called once per epoch by the sdk (in EndBlock). /// It allows the virtual staking contract to bond or unbond any pending requests, as well /// as to perform a rebalance if needed (over the max cap). diff --git a/packages/bindings/src/query.rs b/packages/bindings/src/query.rs index 72b3bf14..f3d70dad 100644 --- a/packages/bindings/src/query.rs +++ b/packages/bindings/src/query.rs @@ -24,7 +24,7 @@ pub enum VirtualStakeQuery { /// Returns a max retrieve amount of delegations for the given contract #[returns(AllDelegationsResponse)] - AllDelegations { contract: String, max_retrieve: u16 }, + AllDelegations { contract: String, max_retrieve: u32 }, } /// Bookkeeping info in the virtual staking sdk module @@ -89,7 +89,7 @@ impl<'a> TokenQuerier<'a> { pub fn all_delegations( &self, contract: String, - max_retrieve: u16, + max_retrieve: u32, ) -> StdResult { let all_delegations_query = VirtualStakeQuery::AllDelegations { contract, From ab73677575e9047e6c15fd3b0c6cdb779a3d81cd Mon Sep 17 00:00:00 2001 From: vuong177 Date: Fri, 6 Sep 2024 14:45:29 +0700 Subject: [PATCH 2/6] add events in TransferRewards --- contracts/consumer/converter/src/ibc.rs | 22 ++++++++-------------- 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/contracts/consumer/converter/src/ibc.rs b/contracts/consumer/converter/src/ibc.rs index a36d6daf..c560237b 100644 --- a/contracts/consumer/converter/src/ibc.rs +++ b/contracts/consumer/converter/src/ibc.rs @@ -192,19 +192,8 @@ pub fn ibc_channel_close( _env: Env, msg: IbcChannelCloseMsg, ) -> Result { - match msg { - IbcChannelCloseMsg::CloseInit { .. } => { - return Err(ContractError::IbcChannelCloseInitDisallowed) - } - IbcChannelCloseMsg::CloseConfirm { channel } => { - if channel.ne(&IBC_CHANNEL.load(deps.storage)?) { - return Err(ContractError::IbcChannelNotMatch); - } - } - }; - let contract = ConverterContract::new(); - let msg = virtual_staking_api::sv::ExecMsg::HandleCloseChannel {}; + let msg = virtual_staking_api::sv::ExecMsg::HandleCloseChannel{}; let msg = WasmMsg::Execute { contract_addr: contract.virtual_stake.load(deps.storage)?.into(), msg: to_json_binary(&msg)?, @@ -246,7 +235,7 @@ pub fn ibc_packet_receive( tx_id: _, } => { let response = contract.unstake(deps, delegator, validator, unstake)?; - let ack = ack_success(&UnstakeAck {})?; + let ack: cosmwasm_std::Binary = ack_success(&UnstakeAck {})?; IbcReceiveResponse::new() .set_ack(ack) .add_submessages(response.messages) @@ -266,8 +255,13 @@ pub fn ibc_packet_receive( rewards, recipient, .. } => { let msg = contract.transfer_rewards(deps.as_ref(), recipient, rewards)?; + let event = Event::new("mesh-transfer-rewards") + .add_attribute("recipient", &recipient) + .add_attribute("rewards", &rewards.amount.to_string()); let ack = ack_success(&TransferRewardsAck {})?; - IbcReceiveResponse::new().set_ack(ack).add_message(msg) + IbcReceiveResponse::new() + .set_ack(ack).add_message(msg) + .add_event(event) } }; Ok(res) From 2e35cb96214f62b713a6b5b98e560e3219044aed Mon Sep 17 00:00:00 2001 From: vuong177 Date: Fri, 6 Sep 2024 14:50:36 +0700 Subject: [PATCH 3/6] nits --- contracts/consumer/converter/src/ibc.rs | 7 ++++--- contracts/consumer/virtual-staking/src/state.rs | 4 +--- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/contracts/consumer/converter/src/ibc.rs b/contracts/consumer/converter/src/ibc.rs index c560237b..e0e38284 100644 --- a/contracts/consumer/converter/src/ibc.rs +++ b/contracts/consumer/converter/src/ibc.rs @@ -193,7 +193,7 @@ pub fn ibc_channel_close( msg: IbcChannelCloseMsg, ) -> Result { let contract = ConverterContract::new(); - let msg = virtual_staking_api::sv::ExecMsg::HandleCloseChannel{}; + let msg = virtual_staking_api::sv::ExecMsg::HandleCloseChannel {}; let msg = WasmMsg::Execute { contract_addr: contract.virtual_stake.load(deps.storage)?.into(), msg: to_json_binary(&msg)?, @@ -260,8 +260,9 @@ pub fn ibc_packet_receive( .add_attribute("rewards", &rewards.amount.to_string()); let ack = ack_success(&TransferRewardsAck {})?; IbcReceiveResponse::new() - .set_ack(ack).add_message(msg) - .add_event(event) + .set_ack(ack) + .add_message(msg) + .add_event(event) } }; Ok(res) diff --git a/contracts/consumer/virtual-staking/src/state.rs b/contracts/consumer/virtual-staking/src/state.rs index 411a2092..dd2373d0 100644 --- a/contracts/consumer/virtual-staking/src/state.rs +++ b/contracts/consumer/virtual-staking/src/state.rs @@ -9,12 +9,10 @@ pub struct Config { /// The address of the converter contract (that is authorized to bond/unbond and will receive rewards) pub converter: Addr, - /// + /// Maximum delegations per query pub max_retrieve: u32, /// If it enable, tombstoned validators will be unbond automatically pub tombstoned_unbond_enable: bool, - /// Maximum delegations per query - pub max_retrieve: u16, } From cc78c6247372be6d8d732aa1e93360c4502dcfed Mon Sep 17 00:00:00 2001 From: vuong177 Date: Fri, 6 Sep 2024 14:51:40 +0700 Subject: [PATCH 4/6] fmt --- contracts/consumer/virtual-staking/src/state.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/contracts/consumer/virtual-staking/src/state.rs b/contracts/consumer/virtual-staking/src/state.rs index dd2373d0..2af7e3ba 100644 --- a/contracts/consumer/virtual-staking/src/state.rs +++ b/contracts/consumer/virtual-staking/src/state.rs @@ -8,11 +8,10 @@ pub struct Config { /// The address of the converter contract (that is authorized to bond/unbond and will receive rewards) pub converter: Addr, - + /// Maximum delegations per query pub max_retrieve: u32, - + /// If it enable, tombstoned validators will be unbond automatically pub tombstoned_unbond_enable: bool, - } From 2302a65ee75476330b7dfac021cd01b4b9ce4d5d Mon Sep 17 00:00:00 2001 From: Trinity Date: Fri, 6 Sep 2024 20:32:35 +0700 Subject: [PATCH 5/6] fix compile --- contracts/consumer/converter/src/ibc.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contracts/consumer/converter/src/ibc.rs b/contracts/consumer/converter/src/ibc.rs index e0e38284..399dec6b 100644 --- a/contracts/consumer/converter/src/ibc.rs +++ b/contracts/consumer/converter/src/ibc.rs @@ -190,7 +190,7 @@ pub(crate) fn valset_update_msg( pub fn ibc_channel_close( deps: DepsMut, _env: Env, - msg: IbcChannelCloseMsg, + _msg: IbcChannelCloseMsg, ) -> Result { let contract = ConverterContract::new(); let msg = virtual_staking_api::sv::ExecMsg::HandleCloseChannel {}; @@ -254,7 +254,7 @@ pub fn ibc_packet_receive( ProviderPacket::TransferRewards { rewards, recipient, .. } => { - let msg = contract.transfer_rewards(deps.as_ref(), recipient, rewards)?; + let msg = contract.transfer_rewards(deps.as_ref(), recipient.clone(), rewards.clone())?; let event = Event::new("mesh-transfer-rewards") .add_attribute("recipient", &recipient) .add_attribute("rewards", &rewards.amount.to_string()); From c3b260dbddbb71e7cbe533770a8121388f8bc037 Mon Sep 17 00:00:00 2001 From: Trinity Date: Fri, 6 Sep 2024 20:52:47 +0700 Subject: [PATCH 6/6] lint --- contracts/consumer/converter/src/ibc.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/contracts/consumer/converter/src/ibc.rs b/contracts/consumer/converter/src/ibc.rs index 399dec6b..3f4209e8 100644 --- a/contracts/consumer/converter/src/ibc.rs +++ b/contracts/consumer/converter/src/ibc.rs @@ -254,7 +254,8 @@ pub fn ibc_packet_receive( ProviderPacket::TransferRewards { rewards, recipient, .. } => { - let msg = contract.transfer_rewards(deps.as_ref(), recipient.clone(), rewards.clone())?; + let msg = + contract.transfer_rewards(deps.as_ref(), recipient.clone(), rewards.clone())?; let event = Event::new("mesh-transfer-rewards") .add_attribute("recipient", &recipient) .add_attribute("rewards", &rewards.amount.to_string());