Skip to content

Commit

Permalink
Merge pull request #192 from decentrio/trinity/zero-max-cap
Browse files Browse the repository at this point in the history
feat: Handle zero max cap
  • Loading branch information
vuong177 authored Sep 5, 2024
2 parents c6a34ff + 8b6f4ee commit 84d6b2a
Show file tree
Hide file tree
Showing 18 changed files with 470 additions and 71 deletions.
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion contracts/consumer/converter/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ fake-custom = [ "mesh-simple-price-feed/fake-custom" ]
[dependencies]
mesh-apis = { workspace = true }
mesh-bindings = { workspace = true }
mesh-sync = { workspace = true }
mesh-virtual-staking = { workspace = true }

sylvia = { workspace = true }
cosmwasm-schema = { workspace = true }
Expand All @@ -38,7 +40,7 @@ thiserror = { workspace = true }
[dev-dependencies]
mesh-burn = { workspace = true }
mesh-simple-price-feed = { workspace = true, features = ["mt"] }

mesh-virtual-staking = { workspace = true, features = ["mt"] }
cw-multi-test = { workspace = true }
test-case = { workspace = true }
derivative = { workspace = true }
Expand Down
73 changes: 63 additions & 10 deletions contracts/consumer/converter/src/contract.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use cosmwasm_std::{
ensure_eq, to_json_binary, Addr, BankMsg, Coin, CosmosMsg, Decimal, Deps, DepsMut, Event,
Fraction, MessageInfo, Reply, Response, StdError, SubMsg, SubMsgResponse, Uint128, Validator,
WasmMsg,
Fraction, IbcMsg, MessageInfo, Reply, Response, StdError, SubMsg, SubMsgResponse, Uint128,
Validator, WasmMsg,
};
use cw2::set_contract_version;
use cw_storage_plus::Item;
Expand All @@ -15,7 +15,9 @@ use mesh_apis::price_feed_api;
use mesh_apis::virtual_staking_api;

use crate::error::ContractError;
use crate::ibc::{make_ibc_packet, valset_update_msg, IBC_CHANNEL};
use crate::ibc::{
make_ibc_packet, packet_timeout_internal_unstake, valset_update_msg, IBC_CHANNEL,
};
use crate::msg::ConfigResponse;
use crate::state::Config;

Expand Down Expand Up @@ -72,6 +74,7 @@ impl ConverterContract<'_> {
remote_denom: String,
virtual_staking_code_id: u64,
admin: Option<String>,
max_retrieve: u16,
) -> Result<custom::Response, ContractError> {
nonpayable(&ctx.info)?;
// validate args
Expand All @@ -95,11 +98,13 @@ impl ConverterContract<'_> {
ctx.deps.api.addr_validate(admin)?;
}

let msg =
to_json_binary(&mesh_virtual_staking::contract::sv::InstantiateMsg { max_retrieve })?;
// Instantiate virtual staking contract
let init_msg = WasmMsg::Instantiate {
admin,
code_id: virtual_staking_code_id,
msg: b"{}".into(),
msg,
funds: vec![],
label: format!("Virtual Staking: {}", &config.remote_denom),
};
Expand Down Expand Up @@ -138,17 +143,18 @@ impl ConverterContract<'_> {
fn test_stake(
&self,
ctx: ExecCtx<custom::ConverterQuery>,
delegator: String,
validator: String,
stake: Coin,
) -> Result<custom::Response, ContractError> {
#[cfg(any(test, feature = "mt"))]
{
// This can only ever be called in tests
self.stake(ctx.deps, validator, stake)
self.stake(ctx.deps, delegator, validator, stake)
}
#[cfg(not(any(test, feature = "mt")))]
{
let _ = (ctx, validator, stake);
let _ = (ctx, delegator, validator, stake);
Err(ContractError::Unauthorized)
}
}
Expand All @@ -159,17 +165,18 @@ impl ConverterContract<'_> {
fn test_unstake(
&self,
ctx: ExecCtx<custom::ConverterQuery>,
delegator: String,
validator: String,
unstake: Coin,
) -> Result<custom::Response, ContractError> {
#[cfg(any(test, feature = "mt"))]
{
// This can only ever be called in tests
self.unstake(ctx.deps, validator, unstake)
self.unstake(ctx.deps, delegator, validator, unstake)
}
#[cfg(not(any(test, feature = "mt")))]
{
let _ = (ctx, validator, unstake);
let _ = (ctx, delegator, validator, unstake);
Err(ContractError::Unauthorized)
}
}
Expand Down Expand Up @@ -214,6 +221,7 @@ impl ConverterContract<'_> {
pub(crate) fn stake(
&self,
deps: DepsMut<custom::ConverterQuery>,
delegator: String,
validator: String,
stake: Coin,
) -> Result<custom::Response, ContractError> {
Expand All @@ -223,7 +231,11 @@ impl ConverterContract<'_> {
.add_attribute("validator", &validator)
.add_attribute("amount", amount.amount.to_string());

let msg = virtual_staking_api::sv::ExecMsg::Bond { validator, amount };
let msg = virtual_staking_api::sv::ExecMsg::Bond {
delegator,
validator,
amount,
};
let msg = WasmMsg::Execute {
contract_addr: self.virtual_stake.load(deps.storage)?.into(),
msg: to_json_binary(&msg)?,
Expand All @@ -238,6 +250,7 @@ impl ConverterContract<'_> {
pub(crate) fn unstake(
&self,
deps: DepsMut<custom::ConverterQuery>,
delegator: String,
validator: String,
unstake: Coin,
) -> Result<custom::Response, ContractError> {
Expand All @@ -247,7 +260,11 @@ impl ConverterContract<'_> {
.add_attribute("validator", &validator)
.add_attribute("amount", amount.amount.to_string());

let msg = virtual_staking_api::sv::ExecMsg::Unbond { validator, amount };
let msg = virtual_staking_api::sv::ExecMsg::Unbond {
delegator,
validator,
amount,
};
let msg = WasmMsg::Execute {
contract_addr: self.virtual_stake.load(deps.storage)?.into(),
msg: to_json_binary(&msg)?,
Expand Down Expand Up @@ -603,4 +620,40 @@ impl ConverterApi for ConverterContract<'_> {
resp = resp.add_event(event);
Ok(resp)
}

fn internal_unstake(
&self,
ctx: ExecCtx<custom::ConverterQuery>,
delegator: String,
validator: String,
amount: Coin,
) -> Result<custom::Response, Self::Error> {
let virtual_stake = self.virtual_stake.load(ctx.deps.storage)?;
ensure_eq!(ctx.info.sender, virtual_stake, ContractError::Unauthorized);

#[allow(unused_mut)]
let mut resp = Response::new()
.add_attribute("action", "internal_unstake")
.add_attribute("amount", amount.amount.to_string())
.add_attribute("owner", delegator.clone());

let channel = IBC_CHANNEL.load(ctx.deps.storage)?;

// Recalculate the price when unbond
let inverted_amount = self.invert_price(ctx.deps.as_ref(), amount.clone())?;
let packet = ConsumerPacket::InternalUnstake {
delegator,
validator,
normalize_amount: amount,
inverted_amount,
};
let msg = IbcMsg::SendPacket {
channel_id: channel.endpoint.channel_id,
data: to_json_binary(&packet)?,
timeout: packet_timeout_internal_unstake(&ctx.env),
};
// send packet if we are ibc enabled
resp = resp.add_message(msg);
Ok(resp)
}
}
48 changes: 43 additions & 5 deletions contracts/consumer/converter/src/ibc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use cosmwasm_std::{
from_json, to_json_binary, DepsMut, Env, Event, Ibc3ChannelOpenResponse, IbcBasicResponse,
IbcChannel, IbcChannelCloseMsg, IbcChannelConnectMsg, IbcChannelOpenMsg,
IbcChannelOpenResponse, IbcMsg, IbcPacketAckMsg, IbcPacketReceiveMsg, IbcPacketTimeoutMsg,
IbcReceiveResponse, IbcTimeout, Validator,
IbcReceiveResponse, IbcTimeout, Validator, WasmMsg,
};
use cw_storage_plus::Item;

Expand All @@ -14,6 +14,7 @@ use mesh_apis::ibc::{
ack_success, validate_channel_order, AckWrapper, AddValidator, ConsumerPacket, ProtocolVersion,
ProviderPacket, StakeAck, TransferRewardsAck, UnstakeAck, PROTOCOL_NAME,
};
use mesh_apis::virtual_staking_api;
use sylvia::types::ExecCtx;

use crate::{
Expand All @@ -34,6 +35,8 @@ const DEFAULT_VALIDATOR_TIMEOUT: u64 = 24 * 60 * 60;
// But reward messages should go faster or timeout
const DEFAULT_REWARD_TIMEOUT: u64 = 60 * 60;

const DEFAULT_INTERNAL_UNSTAKE_TIMEOUT: u64 = 60 * 60;

pub fn packet_timeout_validator(env: &Env) -> IbcTimeout {
// No idea about their block time, but 24 hours ahead of our view of the clock
// should be decently in the future.
Expand All @@ -48,6 +51,16 @@ pub fn packet_timeout_rewards(env: &Env) -> IbcTimeout {
IbcTimeout::with_timestamp(timeout)
}

pub fn packet_timeout_internal_unstake(env: &Env) -> IbcTimeout {
// No idea about their block time, but 24 hours ahead of our view of the clock
// should be decently in the future.
let timeout = env
.block
.time
.plus_seconds(DEFAULT_INTERNAL_UNSTAKE_TIMEOUT);
IbcTimeout::with_timestamp(timeout)
}

#[cfg_attr(not(feature = "library"), entry_point)]
/// enforces ordering and versioning constraints
pub fn ibc_channel_open(
Expand Down Expand Up @@ -195,11 +208,12 @@ pub fn ibc_packet_receive(
let contract = ConverterContract::new();
let res = match packet {
ProviderPacket::Stake {
delegator,
validator,
stake,
tx_id: _,
} => {
let response = contract.stake(deps, validator, stake)?;
let response = contract.stake(deps, delegator, validator, stake)?;
let ack = ack_success(&StakeAck {})?;
IbcReceiveResponse::new()
.set_ack(ack)
Expand All @@ -208,11 +222,12 @@ pub fn ibc_packet_receive(
.add_attributes(response.attributes)
}
ProviderPacket::Unstake {
delegator,
validator,
unstake,
tx_id: _,
} => {
let response = contract.unstake(deps, validator, unstake)?;
let response = contract.unstake(deps, delegator, validator, unstake)?;
let ack = ack_success(&UnstakeAck {})?;
IbcReceiveResponse::new()
.set_ack(ack)
Expand Down Expand Up @@ -245,14 +260,37 @@ pub fn ibc_packet_receive(
/// If it succeeded, take no action. If it errored, we can't do anything else and let it go.
/// We just log the error cases so they can be detected.
pub fn ibc_packet_ack(
_deps: DepsMut,
deps: DepsMut,
_env: Env,
msg: IbcPacketAckMsg,
) -> Result<IbcBasicResponse, ContractError> {
let ack: AckWrapper = from_json(&msg.acknowledgement.data)?;
let contract = ConverterContract::new();
let mut res = IbcBasicResponse::new();
match ack {
AckWrapper::Result(_) => {}
AckWrapper::Result(_) => {
let packet: ConsumerPacket = from_json(&msg.original_packet.data)?;
if let ConsumerPacket::InternalUnstake {
delegator,
validator,
normalize_amount,
inverted_amount: _,
} = packet
{
// execute virtual contract's internal unbond
let msg = virtual_staking_api::sv::ExecMsg::InternalUnbond {
delegator,
validator,
amount: normalize_amount,
};
let msg = WasmMsg::Execute {
contract_addr: contract.virtual_stake.load(deps.storage)?.into(),
msg: to_json_binary(&msg)?,
funds: vec![],
};
res = res.add_message(msg);
}
}
AckWrapper::Error(e) => {
// The wasmd framework will label this with the contract_addr, which helps us find the port and issue.
// Provide info to find the actual packet.
Expand Down
11 changes: 6 additions & 5 deletions contracts/consumer/converter/src/multitest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ fn setup<'a>(app: &'a App<MtApp>, args: SetupArgs<'a>) -> SetupResponse<'a> {
JUNO.to_owned(),
virtual_staking_code.code_id(),
Some(admin.to_owned()),
50,
)
.with_label("Juno Converter")
.with_admin(admin)
Expand Down Expand Up @@ -172,17 +173,17 @@ fn ibc_stake_and_unstake() {

// let's stake some
converter
.test_stake(val1.to_string(), coin(1000, JUNO))
.test_stake(owner.to_string(), val1.to_string(), coin(1000, JUNO))
.call(owner)
.unwrap();
converter
.test_stake(val2.to_string(), coin(4000, JUNO))
.test_stake(owner.to_string(), val2.to_string(), coin(4000, JUNO))
.call(owner)
.unwrap();

// and unstake some
converter
.test_unstake(val2.to_string(), coin(2000, JUNO))
.test_unstake(owner.to_string(), val2.to_string(), coin(2000, JUNO))
.call(owner)
.unwrap();

Expand Down Expand Up @@ -258,11 +259,11 @@ fn ibc_stake_and_burn() {

// let's stake some
converter
.test_stake(val1.to_string(), coin(1000, JUNO))
.test_stake(owner.to_string(), val1.to_string(), coin(1000, JUNO))
.call(owner)
.unwrap();
converter
.test_stake(val2.to_string(), coin(4000, JUNO))
.test_stake(owner.to_string(), val2.to_string(), coin(4000, JUNO))
.call(owner)
.unwrap();

Expand Down
12 changes: 12 additions & 0 deletions contracts/consumer/converter/src/multitest/virtual_staking_mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ impl VirtualStakingApi for VirtualStakingMock<'_> {
fn bond(
&self,
ctx: ExecCtx<Self::QueryC>,
_delegator: String,
validator: String,
amount: Coin,
) -> Result<Response<Self::ExecC>, Self::Error> {
Expand Down Expand Up @@ -160,6 +161,7 @@ impl VirtualStakingApi for VirtualStakingMock<'_> {
fn unbond(
&self,
ctx: ExecCtx<Self::QueryC>,
_delegator: String,
validator: String,
amount: Coin,
) -> Result<Response<Self::ExecC>, Self::Error> {
Expand Down Expand Up @@ -241,6 +243,16 @@ impl VirtualStakingApi for VirtualStakingMock<'_> {
Ok(Response::new())
}

fn internal_unbond(
&self,
_ctx: ExecCtx<Self::QueryC>,
_delegator: String,
_validator: String,
_amount: Coin,
) -> Result<Response<Self::ExecC>, Self::Error> {
unimplemented!()
}

/// 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).
Expand Down
1 change: 1 addition & 0 deletions contracts/consumer/virtual-staking/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ serde = { workspace = true }
thiserror = { workspace = true }

[dev-dependencies]
sylvia = { workspace = true, features = ["mt"] }
mesh-simple-price-feed = { workspace = true, features = ["mt", "fake-custom"] }
mesh-converter = { workspace = true, features = ["mt", "fake-custom"] }
cw-multi-test = { workspace = true }
Expand Down
Loading

0 comments on commit 84d6b2a

Please sign in to comment.