Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Handle zero max cap #192

Merged
merged 16 commits into from
Sep 5, 2024
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
Loading