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

Better ibc tests #73

Closed
wants to merge 8 commits into from
Closed
4 changes: 2 additions & 2 deletions Cargo.lock

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

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ semver = "1.0.4"

# dev deps
anyhow = "1"
cw-multi-test = "0.16.4"
cw-multi-test = "0.16.5"
derivative = "2"
test-case = "2.2.0"

Expand Down
103 changes: 85 additions & 18 deletions contracts/consumer/converter/src/contract.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
use cosmwasm_std::{
ensure_eq, to_binary, Addr, Coin, Decimal, Deps, DepsMut, Event, Reply, Response, SubMsg,
SubMsgResponse, WasmMsg,
ensure_eq, to_binary, Addr, BankMsg, Coin, CosmosMsg, Decimal, Deps, DepsMut, Event, IbcMsg,
Reply, Response, SubMsg, SubMsgResponse, WasmMsg,
};
use cw2::set_contract_version;
use cw_storage_plus::Item;
use cw_utils::{nonpayable, parse_instantiate_response_data};
use cw_utils::{must_pay, nonpayable, parse_instantiate_response_data};
use mesh_apis::ibc::ConsumerPacket;
use sylvia::types::{ExecCtx, InstantiateCtx, QueryCtx, ReplyCtx};
use sylvia::{contract, schemars};

Expand All @@ -13,6 +14,7 @@ use mesh_apis::price_feed_api;
use mesh_apis::virtual_staking_api;

use crate::error::ContractError;
use crate::ibc::{packet_timeout_rewards, IBC_CHANNEL};
use crate::msg::ConfigResponse;
use crate::state::Config;

Expand All @@ -38,11 +40,11 @@ impl ConverterContract<'_> {
}
}

// TODO: there is a chicken and egg problem here.
// converter needs fixed address for virtual stake contract
// virtual stake contract needs fixed address for converter
// (Price feed can be made first and then converter second, that is no issue)
/// The caller of the instantiation will be the converter contract
/// We must first instantiate the price feed contract, then the converter contract.
/// The converter will then instantiate a virtual staking contract to work with it,
/// as they both need references to each other. The admin of the virtual staking
/// contract is taken as an explicit argument.
///
/// Discount is applied to foreign tokens after adjusting foreign/native price,
/// such that 0.3 discount means foreign assets have 70% of their value
#[msg(instantiate)]
Expand All @@ -56,12 +58,17 @@ impl ConverterContract<'_> {
admin: Option<String>,
) -> Result<Response, ContractError> {
nonpayable(&ctx.info)?;
// valdiate args
if discount > Decimal::one() {
return Err(ContractError::InvalidDiscount);
}
if remote_denom.is_empty() {
return Err(ContractError::InvalidDenom(remote_denom));
}
let config = Config {
price_feed: ctx.deps.api.addr_validate(&price_feed)?,
// TODO: better error if discount greater than 1 (this will panic)
price_adjustment: Decimal::one() - discount,
local_denom: ctx.deps.querier.query_bonded_denom()?,
// TODO: validation here? Just that it is non-empty?
remote_denom,
};
self.config.save(ctx.deps.storage, &config)?;
Expand Down Expand Up @@ -229,6 +236,34 @@ impl ConverterContract<'_> {
amount: converted,
})
}

pub(crate) fn transfer_rewards(
&self,
deps: Deps,
recipient: String,
rewards: Coin,
) -> Result<CosmosMsg, ContractError> {
// ensure the address is proper
let recipient = deps.api.addr_validate(&recipient)?;

// ensure this is the reward denom (same as staking denom)
let config = self.config.load(deps.storage)?;
ensure_eq!(
config.local_denom,
rewards.denom,
ContractError::WrongDenom {
sent: rewards.denom,
expected: config.local_denom
}
);

// send the coins
let msg = BankMsg::Send {
to_address: recipient.into(),
amount: vec![rewards],
};
Ok(msg.into())
}
}

#[contract]
Expand All @@ -237,25 +272,57 @@ impl ConverterApi for ConverterContract<'_> {
type Error = ContractError;

/// Rewards tokens (in native staking denom) are sent alongside the message, and should be distributed to all
/// stakers who staked on this validator.
/// stakers who staked on this validator. This is tracked on the provider, so we send an IBC packet there.
#[msg(exec)]
fn distribute_reward(&self, ctx: ExecCtx, validator: String) -> Result<Response, Self::Error> {
let _ = (ctx, validator);
todo!();
fn distribute_reward(
&self,
mut ctx: ExecCtx,
validator: String,
) -> Result<Response, Self::Error> {
let config = self.config.load(ctx.deps.storage)?;
let denom = config.local_denom;
must_pay(&ctx.info, &denom)?;
let rewards = ctx.info.funds.remove(0);

let event = Event::new("distribute_reward")
.add_attribute("validator", &validator)
.add_attribute("amount", rewards.amount.to_string());

// Create a packet for the provider, informing of distribution
let packet = ConsumerPacket::Distribute { validator, rewards };
let channel = IBC_CHANNEL.load(ctx.deps.storage)?;
let msg = IbcMsg::SendPacket {
channel_id: channel.endpoint.channel_id,
data: to_binary(&packet)?,
timeout: packet_timeout_rewards(&ctx.env),
};

Ok(Response::new().add_message(msg).add_event(event))
}

/// This is a batch for of distribute_reward, including the payment for multiple validators.
/// This is a batch form of distribute_reward, including the payment for multiple validators.
/// This is more efficient than calling distribute_reward multiple times, but also more complex.
///
/// info.funds sent along with the message should be the sum of all rewards for all validators,
/// in the native staking denom.
#[msg(exec)]
fn distribute_rewards(
&self,
ctx: ExecCtx,
mut ctx: ExecCtx,
payments: Vec<RewardInfo>,
) -> Result<Response, Self::Error> {
let _ = (ctx, payments);
todo!();
// TODO: Optimize this, when we actually get such calls
let mut resp = Response::new();
for RewardInfo { validator, reward } in payments {
let mut sub_ctx = ctx.branch();
sub_ctx.info.funds[0].amount = reward;
let r = self.distribute_reward(sub_ctx, validator)?;
// put all values on the parent one
resp = resp
.add_submessages(r.messages)
.add_attributes(r.attributes)
.add_events(r.events);
}
Ok(resp)
}
}
6 changes: 6 additions & 0 deletions contracts/consumer/converter/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,10 @@ pub enum ContractError {

#[error("Invalid reply id: {0}")]
InvalidReplyId(u64),

#[error("Invalid discount, must be between 0.0 and 1.0")]
InvalidDiscount,

#[error("Invalid denom: {0}")]
InvalidDenom(String),
}
30 changes: 23 additions & 7 deletions contracts/consumer/converter/src/ibc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use cw_storage_plus::Item;

use mesh_apis::ibc::{
ack_success, validate_channel_order, AckWrapper, AddValidator, ConsumerPacket, ProtocolVersion,
ProviderPacket, StakeAck, UnstakeAck, PROTOCOL_NAME,
ProviderPacket, StakeAck, TransferRewardsAck, UnstakeAck, PROTOCOL_NAME,
};

use crate::{contract::ConverterContract, error::ContractError};
Expand All @@ -22,15 +22,24 @@ const SUPPORTED_IBC_PROTOCOL_VERSION: &str = "1.0.0";
const MIN_IBC_PROTOCOL_VERSION: &str = "1.0.0";

// IBC specific state
const IBC_CHANNEL: Item<IbcChannel> = Item::new("ibc_channel");
pub const IBC_CHANNEL: Item<IbcChannel> = Item::new("ibc_channel");

// Let those validator syncs take a day...
const DEFAULT_TIMEOUT: u64 = 24 * 60 * 60;
const DEFAULT_VALIDATOR_TIMEOUT: u64 = 24 * 60 * 60;
// But reward messages should go faster or timeout
const DEFAULT_REWARD_TIMEOUT: u64 = 60 * 60;

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

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

Expand Down Expand Up @@ -120,7 +129,7 @@ pub fn ibc_channel_connect(
let msg = IbcMsg::SendPacket {
channel_id: channel.endpoint.channel_id,
data: to_binary(&packet)?,
timeout: packet_timeout(&env),
timeout: packet_timeout_validator(&env),
};

Ok(IbcBasicResponse::new().add_message(msg))
Expand Down Expand Up @@ -175,6 +184,13 @@ pub fn ibc_packet_receive(
.add_events(response.events)
.add_attributes(response.attributes)
}
ProviderPacket::TransferRewards {
rewards, recipient, ..
} => {
let msg = contract.transfer_rewards(deps.as_ref(), recipient, rewards)?;
let ack = ack_success(&TransferRewardsAck {})?;
IbcReceiveResponse::new().set_ack(ack).add_message(msg)
}
};
Ok(res)
}
Expand Down Expand Up @@ -216,7 +232,7 @@ pub fn ibc_packet_timeout(
let msg = IbcMsg::SendPacket {
channel_id: msg.packet.src.channel_id,
data: msg.packet.data,
timeout: packet_timeout(&env),
timeout: packet_timeout_validator(&env),
};
Ok(IbcBasicResponse::new().add_message(msg))
}
Loading