Skip to content

Commit 9c1a082

Browse files
authored
Merge pull request #203 from osmosis-labs/trinity/on-channel-close
feat: on_close_channel logic
2 parents 7cf179e + 21b38e9 commit 9c1a082

File tree

10 files changed

+162
-18
lines changed

10 files changed

+162
-18
lines changed

contracts/consumer/converter/src/contract.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ impl ConverterContract<'_> {
7575
virtual_staking_code_id: u64,
7676
tombstoned_unbond_enable: bool,
7777
admin: Option<String>,
78-
max_retrieve: u16,
78+
max_retrieve: u32,
7979
) -> Result<custom::Response, ContractError> {
8080
nonpayable(&ctx.info)?;
8181
// validate args

contracts/consumer/converter/src/error.rs

+6
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,12 @@ pub enum ContractError {
2626
#[error("You must start the channel handshake on this side, it doesn't support OpenTry")]
2727
IbcOpenTryDisallowed,
2828

29+
#[error("IBC channels not match")]
30+
IbcChannelNotMatch,
31+
32+
#[error("You must start the channel close on provider side")]
33+
IbcChannelCloseInitDisallowed,
34+
2935
#[error("Sent wrong denom over IBC: {sent}, expected {expected}")]
3036
WrongDenom { sent: String, expected: String },
3137

contracts/consumer/converter/src/ibc.rs

+19-5
Original file line numberDiff line numberDiff line change
@@ -188,11 +188,18 @@ pub(crate) fn valset_update_msg(
188188
/// On closed channel, we take all tokens from reflect contract to this contract.
189189
/// We also delete the channel entry from accounts.
190190
pub fn ibc_channel_close(
191-
_deps: DepsMut,
191+
deps: DepsMut,
192192
_env: Env,
193193
_msg: IbcChannelCloseMsg,
194194
) -> Result<IbcBasicResponse, ContractError> {
195-
todo!();
195+
let contract = ConverterContract::new();
196+
let msg = virtual_staking_api::sv::ExecMsg::HandleCloseChannel {};
197+
let msg = WasmMsg::Execute {
198+
contract_addr: contract.virtual_stake.load(deps.storage)?.into(),
199+
msg: to_json_binary(&msg)?,
200+
funds: vec![],
201+
};
202+
Ok(IbcBasicResponse::new().add_message(msg))
196203
}
197204

198205
#[cfg_attr(not(feature = "library"), entry_point)]
@@ -228,7 +235,7 @@ pub fn ibc_packet_receive(
228235
tx_id: _,
229236
} => {
230237
let response = contract.unstake(deps, delegator, validator, unstake)?;
231-
let ack = ack_success(&UnstakeAck {})?;
238+
let ack: cosmwasm_std::Binary = ack_success(&UnstakeAck {})?;
232239
IbcReceiveResponse::new()
233240
.set_ack(ack)
234241
.add_submessages(response.messages)
@@ -247,9 +254,16 @@ pub fn ibc_packet_receive(
247254
ProviderPacket::TransferRewards {
248255
rewards, recipient, ..
249256
} => {
250-
let msg = contract.transfer_rewards(deps.as_ref(), recipient, rewards)?;
257+
let msg =
258+
contract.transfer_rewards(deps.as_ref(), recipient.clone(), rewards.clone())?;
259+
let event = Event::new("mesh-transfer-rewards")
260+
.add_attribute("recipient", &recipient)
261+
.add_attribute("rewards", &rewards.amount.to_string());
251262
let ack = ack_success(&TransferRewardsAck {})?;
252-
IbcReceiveResponse::new().set_ack(ack).add_message(msg)
263+
IbcReceiveResponse::new()
264+
.set_ack(ack)
265+
.add_message(msg)
266+
.add_event(event)
253267
}
254268
};
255269
Ok(res)

contracts/consumer/virtual-staking/src/contract.rs

+48-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use std::cmp::Ordering;
22
use std::collections::{BTreeMap, HashMap, HashSet};
3+
use std::u32;
34

45
use cosmwasm_std::{
56
coin, ensure_eq, to_json_binary, Coin, CosmosMsg, CustomQuery, DepsMut, DistributionMsg, Env,
@@ -74,7 +75,7 @@ impl VirtualStakingContract<'_> {
7475
pub fn instantiate(
7576
&self,
7677
ctx: InstantiateCtx<VirtualStakeCustomQuery>,
77-
max_retrieve: u16,
78+
max_retrieve: u32,
7879
tombstoned_unbond_enable: bool,
7980
) -> Result<Response<VirtualStakeCustomMsg>, ContractError> {
8081
nonpayable(&ctx.info)?;
@@ -622,6 +623,52 @@ impl VirtualStakingApi for VirtualStakingContract<'_> {
622623
Ok(Response::new().add_messages(msgs))
623624
}
624625

626+
fn handle_close_channel(
627+
&self,
628+
ctx: ExecCtx<Self::QueryC>,
629+
) -> Result<Response<VirtualStakeCustomMsg>, Self::Error> {
630+
nonpayable(&ctx.info)?;
631+
let ExecCtx { deps, env, info } = ctx;
632+
let config = self.config.load(deps.storage)?;
633+
ensure_eq!(info.sender, config.converter, ContractError::Unauthorized); // only the converter can call this
634+
635+
let all_delegations = TokenQuerier::new(&deps.querier)
636+
.all_delegations(env.contract.address.to_string(), u32::MAX)?;
637+
638+
let mut msgs = vec![VirtualStakeMsg::DeleteAllScheduledTasks {}];
639+
for delegation in all_delegations.delegations.iter() {
640+
let amount = Coin {
641+
denom: config.denom.clone(),
642+
amount: delegation.amount,
643+
};
644+
msgs.push(VirtualStakeMsg::UpdateDelegation {
645+
amount: amount.clone(),
646+
is_deduct: true,
647+
delegator: delegation.delegator.clone(),
648+
validator: delegation.validator.clone(),
649+
});
650+
msgs.push(VirtualStakeMsg::Unbond {
651+
amount,
652+
validator: delegation.validator.clone(),
653+
});
654+
self.bond_requests
655+
.save(deps.storage, &delegation.validator, &Uint128::zero())?;
656+
}
657+
658+
let requests: Vec<(String, Uint128)> = self
659+
.bond_requests
660+
.range(
661+
deps.as_ref().storage,
662+
None,
663+
None,
664+
cosmwasm_std::Order::Ascending,
665+
)
666+
.collect::<Result<_, _>>()?;
667+
self.bonded.save(deps.storage, &requests)?;
668+
669+
Ok(Response::new().add_messages(msgs))
670+
}
671+
625672
// FIXME: need to handle custom message types and queries
626673
/**
627674
* This is called once per epoch to withdraw all rewards and rebalance the bonded tokens.

contracts/consumer/virtual-staking/src/state.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@ pub struct Config {
99
/// The address of the converter contract (that is authorized to bond/unbond and will receive rewards)
1010
pub converter: Addr,
1111

12+
/// Maximum delegations per query
13+
pub max_retrieve: u32,
14+
1215
/// If it enable, tombstoned validators will be unbond automatically
1316
pub tombstoned_unbond_enable: bool,
14-
15-
/// Maximum delegations per query
16-
pub max_retrieve: u16,
1717
}

contracts/provider/external-staking/src/contract.rs

+51-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use cosmwasm_std::{
2-
coin, ensure, ensure_eq, to_json_binary, Coin, Decimal, DepsMut, Env, Event, IbcMsg, Order,
3-
Response, StdResult, Storage, Uint128, Uint256, WasmMsg,
2+
coin, ensure, ensure_eq, to_json_binary, Addr, Coin, Decimal, DepsMut, Env, Event, IbcMsg,
3+
Order, Response, StdResult, Storage, Uint128, Uint256, WasmMsg,
44
};
55
use cw2::set_contract_version;
66
use cw_storage_plus::{Bounder, Item, Map};
@@ -497,6 +497,55 @@ impl ExternalStakingContract<'_> {
497497
Ok(event)
498498
}
499499

500+
pub(crate) fn handle_close_channel(
501+
&self,
502+
deps: DepsMut,
503+
env: Env,
504+
) -> Result<(), ContractError> {
505+
let stakes: Vec<((Addr, String), Stake)> = self
506+
.stakes
507+
.stake
508+
.range(
509+
deps.as_ref().storage,
510+
None,
511+
None,
512+
cosmwasm_std::Order::Ascending,
513+
)
514+
.collect::<Result<_, _>>()?;
515+
516+
for ((user, validator), stake) in stakes.iter() {
517+
let mut new_stake = stake.clone();
518+
let amount = new_stake.stake.low().clone();
519+
let unbond = PendingUnbond {
520+
amount,
521+
release_at: env.block.time,
522+
};
523+
new_stake.pending_unbonds.push(unbond);
524+
new_stake.stake = ValueRange::new_val(Uint128::zero());
525+
526+
let mut distribution = self
527+
.distribution
528+
.may_load(deps.storage, &validator)?
529+
.unwrap_or_default();
530+
new_stake
531+
.points_alignment
532+
.stake_decreased(amount, distribution.points_per_stake);
533+
534+
distribution.total_stake -= amount;
535+
536+
// Save stake
537+
self.stakes
538+
.stake
539+
.save(deps.storage, (user, validator), &new_stake)?;
540+
541+
// Save distribution
542+
self.distribution
543+
.save(deps.storage, validator, &distribution)?;
544+
}
545+
546+
Ok(())
547+
}
548+
500549
/// In non-test code, this is called from `ibc_packet_ack`
501550
#[allow(clippy::too_many_arguments)]
502551
pub(crate) fn valset_update(

contracts/provider/external-staking/src/error.rs

+6
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,12 @@ pub enum ContractError {
4545
#[error("You must start the channel handshake on the other side, it doesn't support OpenInit")]
4646
IbcOpenInitDisallowed,
4747

48+
#[error("IBC channels not match")]
49+
IbcChannelNotMatch,
50+
51+
#[error("You must start the channel close on this side")]
52+
IbcChannelCloseConfirmDisallowed,
53+
4854
#[error("Invalid authorized endpoint: {0}")]
4955
InvalidEndpoint(String),
5056

contracts/provider/external-staking/src/ibc.rs

+18-4
Original file line numberDiff line numberDiff line change
@@ -104,11 +104,25 @@ pub fn ibc_channel_connect(
104104

105105
#[cfg_attr(not(feature = "library"), entry_point)]
106106
pub fn ibc_channel_close(
107-
_deps: DepsMut,
108-
_env: Env,
109-
_msg: IbcChannelCloseMsg,
107+
deps: DepsMut,
108+
env: Env,
109+
msg: IbcChannelCloseMsg,
110110
) -> Result<IbcBasicResponse, ContractError> {
111-
todo!();
111+
match msg {
112+
IbcChannelCloseMsg::CloseInit { channel } => {
113+
if channel.ne(&IBC_CHANNEL.load(deps.storage)?) {
114+
return Err(ContractError::IbcChannelNotMatch);
115+
}
116+
}
117+
IbcChannelCloseMsg::CloseConfirm { .. } => {
118+
return Err(ContractError::IbcChannelCloseConfirmDisallowed)
119+
}
120+
};
121+
122+
let contract = ExternalStakingContract::new();
123+
contract.handle_close_channel(deps, env)?;
124+
125+
Ok(IbcBasicResponse::new())
112126
}
113127

114128
#[cfg_attr(not(feature = "library"), entry_point)]

packages/apis/src/virtual_staking_api.rs

+8
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,14 @@ pub trait VirtualStakingApi {
6565
amount: Coin,
6666
) -> Result<Response<Self::ExecC>, Self::Error>;
6767

68+
/// Handle the close channel process.
69+
/// Unbond all tokens from contract and delete scheduled tasks.
70+
#[sv::msg(exec)]
71+
fn handle_close_channel(
72+
&self,
73+
ctx: ExecCtx<Self::QueryC>,
74+
) -> Result<Response<Self::ExecC>, Self::Error>;
75+
6876
/// SudoMsg::HandleEpoch{} should be called once per epoch by the sdk (in EndBlock).
6977
/// It allows the virtual staking contract to bond or unbond any pending requests, as well
7078
/// as to perform a rebalance if needed (over the max cap).

packages/bindings/src/query.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ pub enum VirtualStakeQuery {
2828

2929
/// Returns a max retrieve amount of delegations for the given contract
3030
#[returns(AllDelegationsResponse)]
31-
AllDelegations { contract: String, max_retrieve: u16 },
31+
AllDelegations { contract: String, max_retrieve: u32 },
3232
}
3333

3434
/// Bookkeeping info in the virtual staking sdk module
@@ -108,7 +108,7 @@ impl<'a> TokenQuerier<'a> {
108108
pub fn all_delegations(
109109
&self,
110110
contract: String,
111-
max_retrieve: u16,
111+
max_retrieve: u32,
112112
) -> StdResult<AllDelegationsResponse> {
113113
let all_delegations_query = VirtualStakeQuery::AllDelegations {
114114
contract,

0 commit comments

Comments
 (0)