diff --git a/Cargo.lock b/Cargo.lock index c9f87aa4..0df67f14 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -607,6 +607,7 @@ dependencies = [ "cw2", "derivative", "mesh-apis", + "mesh-bindings", "mesh-burn", "mesh-simple-price-feed", "schemars", @@ -736,6 +737,7 @@ dependencies = [ "cw2", "derivative", "mesh-apis", + "mesh-bindings", "schemars", "serde", "sylvia", diff --git a/Cargo.toml b/Cargo.toml index b15060e5..3937a620 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,58 +1,64 @@ [workspace] -members = ["packages/*", "contracts/provider/*", "contracts/consumer/*", "contracts/osmosis-price-provider"] +members = [ + "packages/*", + "contracts/provider/*", + "contracts/consumer/*", + "contracts/osmosis-price-provider", +] resolver = "2" [workspace.package] -edition = "2021" -version = "0.10.0-alpha.1" -license = "MIT" -repository = "https://github.com/osmosis-labs/mesh-security" +edition = "2021" +version = "0.10.0-alpha.1" +license = "MIT" +repository = "https://github.com/osmosis-labs/mesh-security" [workspace.dependencies] -mesh-apis = { path = "./packages/apis" } -mesh-bindings = { path = "./packages/bindings" } -mesh-burn = { path = "./packages/burn" } -mesh-sync = { path = "./packages/sync" } +mesh-apis = { path = "./packages/apis" } +mesh-bindings = { path = "./packages/bindings" } +mesh-burn = { path = "./packages/burn" } +mesh-sync = { path = "./packages/sync" } mesh-virtual-staking-mock = { path = "./packages/virtual-staking-mock" } -mesh-vault = { path = "./contracts/provider/vault" } -mesh-external-staking = { path = "./contracts/provider/external-staking" } -mesh-native-staking = { path = "./contracts/provider/native-staking" } -mesh-native-staking-proxy = { path = "./contracts/provider/native-staking-proxy" } - -mesh-converter = { path = "./contracts/consumer/converter" } -mesh-simple-price-feed = { path = "./contracts/consumer/simple-price-feed" } -mesh-virtual-staking = { path = "./contracts/consumer/virtual-staking" } - -sylvia = "0.10.1" -cosmwasm-schema = "1.5.4" -cosmwasm-std = { version = "1.5.4", features = ["ibc3", "cosmwasm_1_3"] } -cw-storage-plus = "1.2.0" -cw-utils = "1.0.3" -cw2 = "1.1.2" -osmosis-std = "0.20.1" -schemars = "0.8.17" -serde = { version = "1.0.199", default-features = false, features = ["derive"] } -thiserror = "1.0.59" +mesh-vault = { path = "./contracts/provider/vault" } +mesh-external-staking = { path = "./contracts/provider/external-staking" } +mesh-native-staking = { path = "./contracts/provider/native-staking" } +mesh-native-staking-proxy = { path = "./contracts/provider/native-staking-proxy" } + +mesh-converter = { path = "./contracts/consumer/converter" } +mesh-simple-price-feed = { path = "./contracts/consumer/simple-price-feed" } +mesh-virtual-staking = { path = "./contracts/consumer/virtual-staking" } + +sylvia = "0.10.1" + +cosmwasm-schema = "1.5.4" +cosmwasm-std = { version = "1.5.4", features = ["ibc3", "cosmwasm_1_3"] } +cw-storage-plus = "1.2.0" +cw-utils = "1.0.3" +cw2 = "1.1.2" +osmosis-std = "0.20.1" +schemars = "0.8.17" +serde = { version = "1.0.199", default-features = false, features = ["derive"] } +thiserror = "1.0.59" semver = "1.0.22" itertools = "0.12.1" # dev deps -anyhow = "1" +anyhow = "1" cw-multi-test = "0.20" -derivative = "2" -test-case = "3.3.1" +derivative = "2" +test-case = "3.3.1" [profile.release] -codegen-units = 1 -debug = false +codegen-units = 1 +debug = false debug-assertions = false -lto = true -panic = 'abort' -rpath = false -opt-level = 3 -overflow-checks = true +lto = true +panic = 'abort' +rpath = false +opt-level = 3 +overflow-checks = true [profile.release.package.mesh-vault] codegen-units = 1 -incremental = false +incremental = false diff --git a/contracts/consumer/converter/Cargo.toml b/contracts/consumer/converter/Cargo.toml index 547f896d..a152fbeb 100644 --- a/contracts/consumer/converter/Cargo.toml +++ b/contracts/consumer/converter/Cargo.toml @@ -3,8 +3,8 @@ name = "mesh-converter" description = "IBC connector on the Consumer side, converting their IBC messages into native tokens for virtual staking" version = { workspace = true } edition = { workspace = true } -license = { workspace = true } -repository = { workspace = true } +license = { workspace = true } +repository = { workspace = true } # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [lib] @@ -17,30 +17,33 @@ backtraces = ["cosmwasm-std/backtraces"] library = [] # enables generation of mt utilities mt = ["library", "sylvia/mt"] +# enable this for multi-tests where you need custom messages for compatibility with virtual staking +fake-custom = [ "mesh-simple-price-feed/fake-custom" ] [dependencies] -mesh-apis = { workspace = true } +mesh-apis = { workspace = true } +mesh-bindings = { workspace = true } -sylvia = { workspace = true } -cosmwasm-schema = { workspace = true } -cosmwasm-std = { workspace = true } -cw-storage-plus = { workspace = true } -cw2 = { workspace = true } -cw-utils = { workspace = true } +sylvia = { workspace = true } +cosmwasm-schema = { workspace = true } +cosmwasm-std = { workspace = true } +cw-storage-plus = { workspace = true } +cw2 = { workspace = true } +cw-utils = { workspace = true } -schemars = { workspace = true } -serde = { workspace = true } -thiserror = { workspace = true } +schemars = { workspace = true } +serde = { workspace = true } +thiserror = { workspace = true } [dev-dependencies] -mesh-burn = { workspace = true } +mesh-burn = { workspace = true } mesh-simple-price-feed = { workspace = true, features = ["mt"] } cw-multi-test = { workspace = true } -test-case = { workspace = true } -derivative = { workspace = true } -anyhow = { workspace = true } +test-case = { workspace = true } +derivative = { workspace = true } +anyhow = { workspace = true } [[bin]] name = "schema" -doc = false +doc = false diff --git a/contracts/consumer/converter/src/contract.rs b/contracts/consumer/converter/src/contract.rs index fc1d97cf..caedb92d 100644 --- a/contracts/consumer/converter/src/contract.rs +++ b/contracts/consumer/converter/src/contract.rs @@ -24,6 +24,19 @@ pub const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); const REPLY_ID_INSTANTIATE: u64 = 1; +#[cfg(not(feature = "fake-custom"))] +pub mod custom { + pub type ConverterMsg = cosmwasm_std::Empty; + pub type ConverterQuery = cosmwasm_std::Empty; + pub type Response = cosmwasm_std::Response; +} +#[cfg(feature = "fake-custom")] +pub mod custom { + pub type ConverterMsg = mesh_bindings::VirtualStakeCustomMsg; + pub type ConverterQuery = mesh_bindings::VirtualStakeCustomQuery; + pub type Response = cosmwasm_std::Response; +} + pub struct ConverterContract<'a> { pub config: Item<'a, Config>, pub virtual_stake: Item<'a, Addr>, @@ -33,6 +46,8 @@ pub struct ConverterContract<'a> { #[contract] #[sv::error(ContractError)] #[sv::messages(converter_api as ConverterApi)] +/// Workaround for lack of support in communication `Empty` <-> `Custom` Contracts. +#[sv::custom(query=custom::ConverterQuery, msg=custom::ConverterMsg)] impl ConverterContract<'_> { pub const fn new() -> Self { Self { @@ -51,13 +66,13 @@ impl ConverterContract<'_> { #[sv::msg(instantiate)] pub fn instantiate( &self, - ctx: InstantiateCtx, + ctx: InstantiateCtx, price_feed: String, discount: Decimal, remote_denom: String, virtual_staking_code_id: u64, admin: Option, - ) -> Result { + ) -> Result { nonpayable(&ctx.info)?; // validate args if discount >= Decimal::one() { @@ -94,7 +109,11 @@ impl ConverterContract<'_> { } #[sv::msg(reply)] - fn reply(&self, ctx: ReplyCtx, reply: Reply) -> Result { + fn reply( + &self, + ctx: ReplyCtx, + reply: Reply, + ) -> Result { match reply.id { REPLY_ID_INSTANTIATE => self.reply_init_callback(ctx.deps, reply.result.unwrap()), _ => Err(ContractError::InvalidReplyId(reply.id)), @@ -104,9 +123,9 @@ impl ConverterContract<'_> { /// Store virtual staking address fn reply_init_callback( &self, - deps: DepsMut, + deps: DepsMut, reply: SubMsgResponse, - ) -> Result { + ) -> Result { let init_data = parse_instantiate_response_data(&reply.data.unwrap())?; let virtual_staking = Addr::unchecked(init_data.contract_address); self.virtual_stake.save(deps.storage, &virtual_staking)?; @@ -118,10 +137,10 @@ impl ConverterContract<'_> { #[sv::msg(exec)] fn test_stake( &self, - ctx: ExecCtx, + ctx: ExecCtx, validator: String, stake: Coin, - ) -> Result { + ) -> Result { #[cfg(any(test, feature = "mt"))] { // This can only ever be called in tests @@ -139,10 +158,10 @@ impl ConverterContract<'_> { #[sv::msg(exec)] fn test_unstake( &self, - ctx: ExecCtx, + ctx: ExecCtx, validator: String, unstake: Coin, - ) -> Result { + ) -> Result { #[cfg(any(test, feature = "mt"))] { // This can only ever be called in tests @@ -160,10 +179,10 @@ impl ConverterContract<'_> { #[sv::msg(exec)] fn test_burn( &self, - ctx: ExecCtx, + ctx: ExecCtx, validators: Vec, burn: Coin, - ) -> Result { + ) -> Result { #[cfg(any(test, feature = "mt"))] { // This can only ever be called in tests @@ -177,7 +196,10 @@ impl ConverterContract<'_> { } #[sv::msg(query)] - fn config(&self, ctx: QueryCtx) -> Result { + fn config( + &self, + ctx: QueryCtx, + ) -> Result { let config = self.config.load(ctx.deps.storage)?; let virtual_staking = self.virtual_stake.load(ctx.deps.storage)?.into_string(); Ok(ConfigResponse { @@ -191,10 +213,10 @@ impl ConverterContract<'_> { /// It is pulled out into a method, so it can also be called by test_stake for testing pub(crate) fn stake( &self, - deps: DepsMut, + deps: DepsMut, validator: String, stake: Coin, - ) -> Result { + ) -> Result { let amount = self.normalize_price(deps.as_ref(), stake)?; let event = Event::new("mesh-bond") @@ -215,10 +237,10 @@ impl ConverterContract<'_> { /// It is pulled out into a method, so it can also be called by test_unstake for testing pub(crate) fn unstake( &self, - deps: DepsMut, + deps: DepsMut, validator: String, unstake: Coin, - ) -> Result { + ) -> Result { let amount = self.normalize_price(deps.as_ref(), unstake)?; let event = Event::new("mesh-unbond") @@ -239,10 +261,10 @@ impl ConverterContract<'_> { /// It is pulled out into a method, so it can also be called by test_burn for testing pub(crate) fn burn( &self, - deps: DepsMut, + deps: DepsMut, validators: &[String], burn: Coin, - ) -> Result { + ) -> Result { let amount = self.normalize_price(deps.as_ref(), burn)?; let event = Event::new("mesh-burn") @@ -262,7 +284,11 @@ impl ConverterContract<'_> { Ok(Response::new().add_message(msg).add_event(event)) } - fn normalize_price(&self, deps: Deps, amount: Coin) -> Result { + fn normalize_price( + &self, + deps: Deps, + amount: Coin, + ) -> Result { let config = self.config.load(deps.storage)?; ensure_eq!( config.remote_denom, @@ -278,10 +304,13 @@ impl ConverterContract<'_> { // also see https://github.com/CosmWasm/sylvia/issues/181 to just store Remote in state use price_feed_api::sv::Querier; use sylvia::types::Remote; - // NOTE: Jan, this feels hacky... I'm not sure if this is the right way to do it - // Also, I am sticking in a random error type here, not what I will get (which is unknown) - let remote = - Remote::<&dyn price_feed_api::PriceFeedApi>::new(config.price_feed); + let remote = Remote::< + &dyn price_feed_api::PriceFeedApi< + Error = StdError, + ExecC = custom::ConverterMsg, + QueryC = custom::ConverterQuery, + >, + >::new(config.price_feed); let price = remote.querier(&deps.querier).price()?.native_per_foreign; let converted = (amount.amount * price) * config.price_adjustment; @@ -291,7 +320,11 @@ impl ConverterContract<'_> { }) } - fn invert_price(&self, deps: Deps, amount: Coin) -> Result { + fn invert_price( + &self, + deps: Deps, + amount: Coin, + ) -> Result { let config = self.config.load(deps.storage)?; ensure_eq!( config.local_denom, @@ -306,10 +339,14 @@ impl ConverterContract<'_> { // also see https://github.com/CosmWasm/sylvia/issues/181 to just store Remote in state use price_feed_api::sv::Querier; use sylvia::types::Remote; - // NOTE: Jan, this feels hacky... I'm not sure if this is the right way to do it - // Also, I am sticking in a random error type here, not what I will get (which is unknown) - let remote = - Remote::<&dyn price_feed_api::PriceFeedApi>::new(config.price_feed); + // Note: it doesn't seem to matter which error type goes here... + let remote = Remote::< + &dyn price_feed_api::PriceFeedApi< + Error = StdError, + ExecC = custom::ConverterMsg, + QueryC = custom::ConverterQuery, + >, + >::new(config.price_feed); let price = remote.querier(&deps.querier).price()?.native_per_foreign; let converted = (amount.amount * price.inv().ok_or(ContractError::InvalidPrice {})?) * config @@ -325,10 +362,10 @@ impl ConverterContract<'_> { pub(crate) fn transfer_rewards( &self, - deps: Deps, + deps: Deps, recipient: String, rewards: Coin, - ) -> Result { + ) -> Result, ContractError> { // ensure the address is proper let recipient = deps.api.addr_validate(&recipient)?; @@ -351,7 +388,11 @@ impl ConverterContract<'_> { Ok(msg.into()) } - fn ensure_authorized(&self, deps: &DepsMut, info: &MessageInfo) -> Result<(), ContractError> { + fn ensure_authorized( + &self, + deps: &DepsMut, + info: &MessageInfo, + ) -> Result<(), ContractError> { let virtual_stake = self.virtual_stake.load(deps.storage)?; ensure_eq!(info.sender, virtual_stake, ContractError::Unauthorized {}); @@ -361,14 +402,16 @@ impl ConverterContract<'_> { impl ConverterApi for ConverterContract<'_> { type Error = ContractError; + type ExecC = custom::ConverterMsg; + type QueryC = custom::ConverterQuery; /// Rewards tokens (in native staking denom) are sent alongside the message, and should be distributed to all /// stakers who staked on this validator. This is tracked on the provider, so we send an IBC packet there. fn distribute_reward( &self, - mut ctx: ExecCtx, + mut ctx: ExecCtx, validator: String, - ) -> Result { + ) -> Result { self.ensure_authorized(&ctx.deps, &ctx.info)?; let config = self.config.load(ctx.deps.storage)?; @@ -391,9 +434,9 @@ impl ConverterApi for ConverterContract<'_> { /// in the native staking denom. fn distribute_rewards( &self, - mut ctx: ExecCtx, + mut ctx: ExecCtx, payments: Vec, - ) -> Result { + ) -> Result { self.ensure_authorized(&ctx.deps, &ctx.info)?; let config = self.config.load(ctx.deps.storage)?; @@ -431,7 +474,7 @@ impl ConverterApi for ConverterContract<'_> { #[allow(clippy::too_many_arguments)] fn valset_update( &self, - ctx: ExecCtx, + ctx: ExecCtx, additions: Vec, removals: Vec, updated: Vec, @@ -439,7 +482,7 @@ impl ConverterApi for ConverterContract<'_> { unjailed: Vec, tombstoned: Vec, mut slashed: Vec, - ) -> Result { + ) -> Result { self.ensure_authorized(&ctx.deps, &ctx.info)?; // Send over IBC to the Consumer diff --git a/contracts/consumer/converter/src/ibc.rs b/contracts/consumer/converter/src/ibc.rs index d711a2a5..c54a19dd 100644 --- a/contracts/consumer/converter/src/ibc.rs +++ b/contracts/consumer/converter/src/ibc.rs @@ -16,7 +16,10 @@ use mesh_apis::ibc::{ }; use sylvia::types::ExecCtx; -use crate::{contract::ConverterContract, error::ContractError}; +use crate::{ + contract::{custom, ConverterContract}, + error::ContractError, +}; /// This is the maximum version of the Mesh Security protocol that we support const SUPPORTED_IBC_PROTOCOL_VERSION: &str = "0.11.0"; @@ -184,10 +187,10 @@ pub fn ibc_channel_close( /// We cannot return any meaningful response value as we do not know the response value /// of execution. We just return ok if we dispatched, error if we failed to dispatch pub fn ibc_packet_receive( - deps: DepsMut, + deps: DepsMut, _env: Env, msg: IbcPacketReceiveMsg, -) -> Result { +) -> Result, ContractError> { let packet: ProviderPacket = from_json(msg.packet.data)?; let contract = ConverterContract::new(); let res = match packet { @@ -280,7 +283,7 @@ pub fn ibc_packet_timeout( } pub(crate) fn make_ibc_packet( - ctx: &mut ExecCtx, + ctx: &mut ExecCtx, packet: ConsumerPacket, ) -> Result { let channel = IBC_CHANNEL.load(ctx.deps.storage)?; diff --git a/contracts/consumer/converter/src/multitest.rs b/contracts/consumer/converter/src/multitest.rs index 883b38c9..22169d99 100644 --- a/contracts/consumer/converter/src/multitest.rs +++ b/contracts/consumer/converter/src/multitest.rs @@ -1,7 +1,7 @@ mod virtual_staking_mock; use cosmwasm_std::{coin, coins, Addr, Decimal, StdError, Uint128, Validator}; -use cw_multi_test::App as MtApp; +use cw_multi_test::{no_init, AppBuilder}; use mesh_apis::converter_api::sv::mt::ConverterApiProxy; use mesh_apis::converter_api::RewardInfo; use mesh_simple_price_feed::contract::sv::mt::CodeId as PriceFeedCodeId; @@ -12,13 +12,15 @@ use virtual_staking_mock::VirtualStakingMock; use crate::contract::sv::mt::CodeId as ConverterCodeId; use crate::contract::sv::mt::ConverterContractProxy; -use crate::contract::ConverterContract; +use crate::contract::{custom, ConverterContract}; use crate::error::ContractError; use crate::error::ContractError::Unauthorized; use crate::multitest::virtual_staking_mock::sv::mt::VirtualStakingMockProxy; const JUNO: &str = "ujuno"; +pub type MtApp = cw_multi_test::BasicApp; + struct SetupArgs<'a> { owner: &'a str, admin: &'a str, @@ -32,6 +34,10 @@ struct SetupResponse<'a> { virtual_staking: Proxy<'a, MtApp, VirtualStakingMock<'a>>, } +fn new_app() -> App { + App::new(AppBuilder::new_custom().build(no_init)) +} + fn setup<'a>(app: &'a App, args: SetupArgs<'a>) -> SetupResponse<'a> { let SetupArgs { owner, @@ -80,7 +86,7 @@ fn setup<'a>(app: &'a App, args: SetupArgs<'a>) -> SetupResponse<'a> { #[test] fn instantiation() { - let app = App::default(); + let app = new_app(); let owner = "sunny"; // Owner of the staking contract (i. e. the vault contract) let admin = "theman"; @@ -122,7 +128,7 @@ fn instantiation() { #[test] fn ibc_stake_and_unstake() { - let app = App::default(); + let app = new_app(); let owner = "sunny"; // Owner of the staking contract (i. e. the vault contract) let admin = "theman"; @@ -208,7 +214,7 @@ fn ibc_stake_and_unstake() { #[test] fn ibc_stake_and_burn() { - let app = App::default(); + let app = new_app(); let owner = "sunny"; // Owner of the staking contract (i. e. the vault contract) let admin = "theman"; @@ -294,7 +300,7 @@ fn ibc_stake_and_burn() { #[test] fn valset_update_works() { - let app = App::default(); + let app = new_app(); let owner = "sunny"; // Owner of the staking contract (i. e. the vault contract) let admin = "theman"; @@ -365,7 +371,7 @@ fn valset_update_works() { #[test] fn unauthorized() { - let app = App::default(); + let app = new_app(); let SetupResponse { converter, .. } = setup( &app, @@ -415,7 +421,7 @@ fn distribute_rewards_invalid_amount_is_rejected() { let discount = Decimal::percent(10); // 1 OSMO worth of JUNO should give 0.9 OSMO of stake let native_per_foreign = Decimal::percent(40); // 1 JUNO is worth 0.4 OSMO - let app = App::default(); + let app = new_app(); let SetupResponse { price_feed: _, @@ -490,14 +496,14 @@ fn distribute_rewards_invalid_amount_is_rejected() { } #[test] -#[ignore = "unsupported by Sylvia"] +#[ignore = "IBC unsupported by Sylvia"] fn distribute_rewards_valid_amount() { let owner = "sunny"; let admin = "theman"; let discount = Decimal::percent(10); // 1 OSMO worth of JUNO should give 0.9 OSMO of stake let native_per_foreign = Decimal::percent(40); // 1 JUNO is worth 0.4 OSMO - let app = App::default(); + let app = new_app(); let SetupResponse { price_feed: _, diff --git a/contracts/consumer/converter/src/multitest/virtual_staking_mock.rs b/contracts/consumer/converter/src/multitest/virtual_staking_mock.rs index ac313247..3faaedf8 100644 --- a/contracts/consumer/converter/src/multitest/virtual_staking_mock.rs +++ b/contracts/consumer/converter/src/multitest/virtual_staking_mock.rs @@ -7,6 +7,8 @@ use mesh_apis::virtual_staking_api::{self, ValidatorSlash, VirtualStakingApi}; use sylvia::contract; use sylvia::types::{ExecCtx, InstantiateCtx, QueryCtx, SudoCtx}; +use crate::contract::custom; + #[cw_serde] pub struct Config { /// The denom we accept for staking @@ -43,6 +45,7 @@ pub struct VirtualStakingMock<'a> { #[contract] #[sv::error(ContractError)] #[sv::messages(virtual_staking_api as VirtualStakingApi)] +#[sv::custom(query=custom::ConverterQuery, msg=custom::ConverterMsg)] impl VirtualStakingMock<'_> { pub const fn new() -> Self { Self { @@ -52,7 +55,10 @@ impl VirtualStakingMock<'_> { } #[sv::msg(instantiate)] - pub fn instantiate(&self, ctx: InstantiateCtx) -> Result { + pub fn instantiate( + &self, + ctx: InstantiateCtx, + ) -> Result { nonpayable(&ctx.info)?; let denom = ctx.deps.querier.query_bonded_denom()?; let config = Config { @@ -64,7 +70,10 @@ impl VirtualStakingMock<'_> { } #[sv::msg(query)] - fn config(&self, ctx: QueryCtx) -> Result { + fn config( + &self, + ctx: QueryCtx, + ) -> Result { let cfg = self.config.load(ctx.deps.storage)?; let denom = cfg.denom; let converter = cfg.converter.into_string(); @@ -72,7 +81,11 @@ impl VirtualStakingMock<'_> { } #[sv::msg(query)] - fn stake(&self, ctx: QueryCtx, validator: String) -> Result { + fn stake( + &self, + ctx: QueryCtx, + validator: String, + ) -> Result { let stake = self .stake .may_load(ctx.deps.storage, &validator)? @@ -81,7 +94,10 @@ impl VirtualStakingMock<'_> { } #[sv::msg(query)] - fn all_stake(&self, ctx: QueryCtx) -> Result { + fn all_stake( + &self, + ctx: QueryCtx, + ) -> Result { let stakes = self .stake .range(ctx.deps.storage, None, None, cosmwasm_std::Order::Ascending) @@ -108,13 +124,18 @@ pub struct ConfigResponse { impl VirtualStakingApi for VirtualStakingMock<'_> { type Error = ContractError; - type ExecC = cosmwasm_std::Empty; - type QueryC = cosmwasm_std::Empty; + type ExecC = custom::ConverterMsg; + type QueryC = custom::ConverterQuery; /// Requests to bond tokens to a validator. This will be actually handled at the next epoch. /// If the virtual staking module is over the max cap, it will trigger a rebalance. /// If the max cap is 0, then this will immediately return an error. - fn bond(&self, ctx: ExecCtx, validator: String, amount: Coin) -> Result { + fn bond( + &self, + ctx: ExecCtx, + validator: String, + amount: Coin, + ) -> Result, Self::Error> { nonpayable(&ctx.info)?; let cfg = self.config.load(ctx.deps.storage)?; ensure_eq!(ctx.info.sender, cfg.converter, ContractError::Unauthorized); // only the converter can call this @@ -138,10 +159,10 @@ impl VirtualStakingApi for VirtualStakingMock<'_> { /// If the virtual staking contract doesn't have at least amount tokens staked to the given validator, this will return an error. fn unbond( &self, - ctx: ExecCtx, + ctx: ExecCtx, validator: String, amount: Coin, - ) -> Result { + ) -> Result, Self::Error> { nonpayable(&ctx.info)?; let cfg = self.config.load(ctx.deps.storage)?; ensure_eq!(ctx.info.sender, cfg.converter, ContractError::Unauthorized); // only the converter can call this @@ -165,10 +186,10 @@ impl VirtualStakingApi for VirtualStakingMock<'_> { /// If the virtual staking contract doesn't have at least amount tokens staked over the given validators, this will return an error. fn burn( &self, - ctx: ExecCtx, + ctx: ExecCtx, validators: Vec, amount: Coin, - ) -> Result { + ) -> Result, Self::Error> { nonpayable(&ctx.info)?; let cfg = self.config.load(ctx.deps.storage)?; // only the converter can call this @@ -225,7 +246,10 @@ impl VirtualStakingApi for VirtualStakingMock<'_> { /// as to perform a rebalance if needed (over the max cap). /// /// It should also withdraw all pending rewards here, and send them to the converter contract. - fn handle_epoch(&self, _ctx: SudoCtx) -> Result { + fn handle_epoch( + &self, + _ctx: SudoCtx, + ) -> Result, Self::Error> { unimplemented!() } @@ -238,7 +262,7 @@ impl VirtualStakingApi for VirtualStakingMock<'_> { /// - Permanent removal (i.e. tombstoning) of a validator from the active set. Implies slashing fn handle_valset_update( &self, - _ctx: SudoCtx, + _ctx: SudoCtx, _additions: Option>, _removals: Option>, _updated: Option>, @@ -246,7 +270,7 @@ impl VirtualStakingApi for VirtualStakingMock<'_> { _unjailed: Option>, _tombstoned: Option>, _slashed: Option>, - ) -> Result { + ) -> Result, Self::Error> { unimplemented!() } } diff --git a/contracts/consumer/remote-price-feed/src/contract.rs b/contracts/consumer/remote-price-feed/src/contract.rs index 9640a096..745b1ea4 100644 --- a/contracts/consumer/remote-price-feed/src/contract.rs +++ b/contracts/consumer/remote-price-feed/src/contract.rs @@ -84,6 +84,9 @@ impl RemotePriceFeedContract { impl PriceFeedApi for RemotePriceFeedContract { type Error = ContractError; + // FIXME: make these under a feature flag if we need virtual-staking multitest compatibility + type ExecC = cosmwasm_std::Empty; + type QueryC = cosmwasm_std::Empty; /// Return the price of the foreign token. That is, how many native tokens /// are needed to buy one foreign token. diff --git a/contracts/consumer/simple-price-feed/Cargo.toml b/contracts/consumer/simple-price-feed/Cargo.toml index d19c68b7..b32fe2e4 100644 --- a/contracts/consumer/simple-price-feed/Cargo.toml +++ b/contracts/consumer/simple-price-feed/Cargo.toml @@ -3,8 +3,8 @@ name = "mesh-simple-price-feed" description = "Returns a fixed price for assets to the converter contract - for tests or gov-defined prices" version = { workspace = true } edition = { workspace = true } -license = { workspace = true } -repository = { workspace = true } +license = { workspace = true } +repository = { workspace = true } # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [lib] @@ -17,27 +17,30 @@ backtraces = ["cosmwasm-std/backtraces"] library = [] # enables generation of mt utilities mt = ["library", "sylvia/mt"] +# enable this for multi-tests where you need custom messages for compatibility with virtual staking +fake-custom = [] [dependencies] -mesh-apis = { workspace = true } +mesh-apis = { workspace = true } +mesh-bindings = { workspace = true } sylvia = { workspace = true } -cosmwasm-schema = { workspace = true } -cosmwasm-std = { workspace = true } -cw-storage-plus = { workspace = true } -cw2 = { workspace = true } -cw-utils = { workspace = true } +cosmwasm-schema = { workspace = true } +cosmwasm-std = { workspace = true } +cw-storage-plus = { workspace = true } +cw2 = { workspace = true } +cw-utils = { workspace = true } -schemars = { workspace = true } -serde = { workspace = true } -thiserror = { workspace = true } +schemars = { workspace = true } +serde = { workspace = true } +thiserror = { workspace = true } [dev-dependencies] cw-multi-test = { workspace = true } -test-case = { workspace = true } -derivative = { workspace = true } -anyhow = { workspace = true } +test-case = { workspace = true } +derivative = { workspace = true } +anyhow = { workspace = true } [[bin]] name = "schema" -doc = false +doc = false diff --git a/contracts/consumer/simple-price-feed/src/contract.rs b/contracts/consumer/simple-price-feed/src/contract.rs index 59896fc4..b3a5e2d8 100644 --- a/contracts/consumer/simple-price-feed/src/contract.rs +++ b/contracts/consumer/simple-price-feed/src/contract.rs @@ -14,6 +14,19 @@ use crate::state::Config; pub const CONTRACT_NAME: &str = env!("CARGO_PKG_NAME"); pub const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); +#[cfg(not(feature = "fake-custom"))] +pub mod custom { + pub type PriceFeedMsg = cosmwasm_std::Empty; + pub type PriceFeedQuery = cosmwasm_std::Empty; + pub type Response = cosmwasm_std::Response; +} +#[cfg(feature = "fake-custom")] +pub mod custom { + pub type PriceFeedMsg = mesh_bindings::VirtualStakeCustomMsg; + pub type PriceFeedQuery = mesh_bindings::VirtualStakeCustomQuery; + pub type Response = cosmwasm_std::Response; +} + pub struct SimplePriceFeedContract<'a> { pub config: Item<'a, Config>, } @@ -22,6 +35,10 @@ pub struct SimplePriceFeedContract<'a> { #[contract] #[sv::error(ContractError)] #[sv::messages(price_feed_api as PriceFeedApi)] +// #[cfg_attr(any(test, feature = "mt"), sv::messages(price_feed_api as PriceFeedApi: custom(msg, query)))] +// #[cfg_attr(not(any(test, feature = "mt")), sv::messages(price_feed_api as PriceFeedApi))] +/// Workaround for lack of support in communication `Empty` <-> `Custom` Contracts. +#[sv::custom(query=custom::PriceFeedQuery, msg=custom::PriceFeedMsg)] impl SimplePriceFeedContract<'_> { pub const fn new() -> Self { Self { @@ -34,10 +51,10 @@ impl SimplePriceFeedContract<'_> { #[sv::msg(instantiate)] pub fn instantiate( &self, - ctx: InstantiateCtx, + ctx: InstantiateCtx, native_per_foreign: Decimal, owner: Option, - ) -> Result { + ) -> Result { nonpayable(&ctx.info)?; let owner = match owner { Some(owner) => ctx.deps.api.addr_validate(&owner)?, @@ -56,9 +73,9 @@ impl SimplePriceFeedContract<'_> { #[sv::msg(exec)] fn update_price( &self, - ctx: ExecCtx, + ctx: ExecCtx, native_per_foreign: Decimal, - ) -> Result { + ) -> Result { nonpayable(&ctx.info)?; let mut config = self.config.load(ctx.deps.storage)?; @@ -76,7 +93,10 @@ impl SimplePriceFeedContract<'_> { } #[sv::msg(query)] - fn config(&self, ctx: QueryCtx) -> Result { + fn config( + &self, + ctx: QueryCtx, + ) -> Result { let config = self.config.load(ctx.deps.storage)?; Ok(ConfigResponse { owner: config.owner.into_string(), @@ -87,10 +107,12 @@ impl SimplePriceFeedContract<'_> { impl PriceFeedApi for SimplePriceFeedContract<'_> { type Error = ContractError; + type ExecC = custom::PriceFeedMsg; + type QueryC = custom::PriceFeedQuery; /// Return the price of the foreign token. That is, how many native tokens /// are needed to buy one foreign token. - fn price(&self, ctx: QueryCtx) -> Result { + fn price(&self, ctx: QueryCtx) -> Result { let config = self.config.load(ctx.deps.storage)?; Ok(PriceResponse { native_per_foreign: config.native_per_foreign, @@ -98,7 +120,10 @@ impl PriceFeedApi for SimplePriceFeedContract<'_> { } /// Nothing needs to be done on the epoch - fn handle_epoch(&self, _ctx: SudoCtx) -> Result { + fn handle_epoch( + &self, + _ctx: SudoCtx, + ) -> Result, Self::Error> { Ok(Response::new()) } } diff --git a/contracts/consumer/virtual-staking/Cargo.toml b/contracts/consumer/virtual-staking/Cargo.toml index 47e84cad..2952a33b 100644 --- a/contracts/consumer/virtual-staking/Cargo.toml +++ b/contracts/consumer/virtual-staking/Cargo.toml @@ -35,8 +35,8 @@ serde = { workspace = true } thiserror = { workspace = true } [dev-dependencies] -mesh-simple-price-feed = { workspace = true, features = ["mt"] } -mesh-converter = { 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 } test-case = { workspace = true } derivative = { workspace = true } diff --git a/contracts/consumer/virtual-staking/src/contract.rs b/contracts/consumer/virtual-staking/src/contract.rs index 41f24682..52af7d8b 100644 --- a/contracts/consumer/virtual-staking/src/contract.rs +++ b/contracts/consumer/virtual-staking/src/contract.rs @@ -360,7 +360,6 @@ impl VirtualStakingApi for VirtualStakingContract<'_> { fn bond( &self, ctx: ExecCtx, - validator: String, amount: Coin, ) -> Result, Self::Error> { @@ -1323,7 +1322,7 @@ mod tests { impl VirtualStakingExt for VirtualStakingContract<'_> { fn quick_inst(&self, deps: DepsMut) { self.instantiate(InstantiateCtx { - deps: deps, + deps, env: mock_env(), info: mock_info("me", &[]), }) @@ -1372,7 +1371,7 @@ mod tests { self.bond( ExecCtx { - deps: deps, + deps, env: mock_env(), info: mock_info("me", &[]), }, @@ -1387,7 +1386,7 @@ mod tests { self.unbond( ExecCtx { - deps: deps, + deps, env: mock_env(), info: mock_info("me", &[]), }, @@ -1407,7 +1406,7 @@ mod tests { self.burn( ExecCtx { - deps: deps, + deps, env: mock_env(), info: mock_info("me", &[]), }, diff --git a/contracts/consumer/virtual-staking/src/multitest.rs b/contracts/consumer/virtual-staking/src/multitest.rs index 4baeedb0..54be3760 100644 --- a/contracts/consumer/virtual-staking/src/multitest.rs +++ b/contracts/consumer/virtual-staking/src/multitest.rs @@ -1,12 +1,12 @@ use cosmwasm_std::{Addr, Decimal, Validator}; use cw_multi_test::no_init; +use mesh_apis::virtual_staking_api::sv::mt::VirtualStakingApiProxy; use sylvia::multitest::Proxy; use mesh_converter::contract::sv::mt::ConverterContractProxy; use crate::contract; use crate::contract::sv::mt::VirtualStakingContractProxy; -use crate::contract::sv::ContractSudoMsg; const JUNO: &str = "ujuno"; @@ -120,7 +120,8 @@ fn instantiation() { } #[test] -#[ignore] // FIXME: Enable / finish this test once custom query support is added to sylvia +// FIXME: Enable / finish this test once custom query support is added to sylvia +#[ignore = "IBC Messages not supported yet"] fn valset_update_sudo() { let app = new_app(); @@ -160,8 +161,8 @@ fn valset_update_sudo() { ]; let rems = vec!["cosmosval2".to_string()]; let tombs = vec!["cosmosval3".to_string()]; - // See this as an example how we can make working directly with these genertaed enums nicer - let inner = mesh_apis::virtual_staking_api::sv::VirtualStakingApiSudoMsg::handle_valset_update( + + let res = virtual_staking.handle_valset_update( Some(adds), Some(rems), None, @@ -170,12 +171,6 @@ fn valset_update_sudo() { Some(tombs), None, ); - let msg = ContractSudoMsg::VirtualStakingApi(inner); - - let res = app - .app_mut() - .wasm_sudo(virtual_staking.contract_addr, &msg) - .unwrap(); - println!("res: {:?}", res); + res.unwrap(); } diff --git a/contracts/provider/native-staking-proxy/src/multitest.rs b/contracts/provider/native-staking-proxy/src/multitest.rs index 410f067c..02321a7d 100644 --- a/contracts/provider/native-staking-proxy/src/multitest.rs +++ b/contracts/provider/native-staking-proxy/src/multitest.rs @@ -293,7 +293,6 @@ fn unstaking() { ); } - #[test] fn burning() { let owner = "vault_admin"; @@ -336,7 +335,6 @@ fn burning() { coin(0, OSMO) ); - // Advance time until the unbonding period is over process_staking_unbondings(&app); @@ -551,8 +549,8 @@ fn process_staking_unbondings(app: &App) { // This is deprecated as unneeded, but tests fail if it isn't here. What's up??? app.app_mut() .sudo(cw_multi_test::SudoMsg::Staking( + #[allow(deprecated)] cw_multi_test::StakingSudo::ProcessQueue {}, )) .unwrap(); } - diff --git a/contracts/provider/vault/src/multitest.rs b/contracts/provider/vault/src/multitest.rs index 7b448b89..49893402 100644 --- a/contracts/provider/vault/src/multitest.rs +++ b/contracts/provider/vault/src/multitest.rs @@ -111,6 +111,7 @@ fn setup_without_local_staking<'app>( (vault, external) } +#[allow(clippy::type_complexity)] fn setup_inner<'app>( app: &'app App, owner: &'app str, @@ -280,6 +281,7 @@ fn process_staking_unbondings(app: &App) { // This is deprecated as unneeded, but tests fail if it isn't here. What's up??? app.app_mut() .sudo(cw_multi_test::SudoMsg::Staking( + #[allow(deprecated)] cw_multi_test::StakingSudo::ProcessQueue {}, )) .unwrap(); diff --git a/packages/apis/src/converter_api.rs b/packages/apis/src/converter_api.rs index 2a26474d..8fc26412 100644 --- a/packages/apis/src/converter_api.rs +++ b/packages/apis/src/converter_api.rs @@ -1,5 +1,8 @@ +// only for valset_update but doesn't work for the autogenerated code +#![allow(clippy::too_many_arguments)] + use cosmwasm_schema::cw_serde; -use cosmwasm_std::{Coin, Response, StdError, Uint128, Validator}; +use cosmwasm_std::{Coin, CustomMsg, CustomQuery, Response, StdError, Uint128, Validator}; use sylvia::types::ExecCtx; use sylvia::{interface, schemars}; @@ -9,11 +12,17 @@ use sylvia::{interface, schemars}; #[interface] pub trait ConverterApi { type Error: From; + type ExecC: CustomMsg; + type QueryC: CustomQuery; /// Rewards tokens (in native staking denom) are sent alongside the message, and should be distributed to all /// stakers who staked on this validator. #[sv::msg(exec)] - fn distribute_reward(&self, ctx: ExecCtx, validator: String) -> Result; + fn distribute_reward( + &self, + ctx: ExecCtx, + validator: String, + ) -> Result, Self::Error>; /// This is a batch for of distribute_reward, including the payment for multiple validators. /// This is more efficient than calling distribute_reward multiple times, but also more complex. @@ -23,18 +32,17 @@ pub trait ConverterApi { #[sv::msg(exec)] fn distribute_rewards( &self, - ctx: ExecCtx, + ctx: ExecCtx, payments: Vec, - ) -> Result; + ) -> Result, Self::Error>; /// Valset updates. /// /// TODO: pubkeys need to be part of the Validator struct (requires CosmWasm support). - #[allow(clippy::too_many_arguments)] #[sv::msg(exec)] fn valset_update( &self, - ctx: ExecCtx, + ctx: ExecCtx, additions: Vec, removals: Vec, updated: Vec, @@ -42,7 +50,7 @@ pub trait ConverterApi { unjailed: Vec, tombstoned: Vec, slashed: Vec, - ) -> Result; + ) -> Result, Self::Error>; } #[cw_serde] diff --git a/packages/apis/src/price_feed_api.rs b/packages/apis/src/price_feed_api.rs index 49723bf1..92679320 100644 --- a/packages/apis/src/price_feed_api.rs +++ b/packages/apis/src/price_feed_api.rs @@ -1,5 +1,5 @@ use cosmwasm_schema::cw_serde; -use cosmwasm_std::{Decimal, Response, StdError}; +use cosmwasm_std::{CustomMsg, CustomQuery, Decimal, Response, StdError}; use sylvia::types::{QueryCtx, SudoCtx}; use sylvia::{interface, schemars}; @@ -12,14 +12,19 @@ use sylvia::{interface, schemars}; #[interface] pub trait PriceFeedApi { type Error: From; + type ExecC: CustomMsg; + type QueryC: CustomQuery; /// Return the price of the foreign token. That is, how many native tokens /// are needed to buy one foreign token. #[sv::msg(query)] - fn price(&self, ctx: QueryCtx) -> Result; + fn price(&self, ctx: QueryCtx) -> Result; #[sv::msg(sudo)] - fn handle_epoch(&self, ctx: SudoCtx) -> Result; + fn handle_epoch( + &self, + ctx: SudoCtx, + ) -> Result, Self::Error>; } #[cw_serde] diff --git a/packages/apis/src/virtual_staking_api.rs b/packages/apis/src/virtual_staking_api.rs index d820a668..982855ce 100644 --- a/packages/apis/src/virtual_staking_api.rs +++ b/packages/apis/src/virtual_staking_api.rs @@ -1,3 +1,6 @@ +// only for handle_valset_update but doesn't work for the autogenerated code +#![allow(clippy::too_many_arguments)] + use cosmwasm_schema::cw_serde; use cosmwasm_std::{Coin, Response, StdError, Uint128, Validator}; use sylvia::cw_std::{CustomMsg, CustomQuery}; @@ -66,7 +69,6 @@ pub trait VirtualStakingApi { /// - Temporary removal of a validator from the active set due to jailing. Implies slashing. /// - Addition of an existing validator to the active validator set. /// - Permanent removal (i.e. tombstoning) of a validator from the active set. Implies slashing - #[allow(clippy::too_many_arguments)] #[sv::msg(sudo)] fn handle_valset_update( &self, diff --git a/scripts/optimizer.sh b/scripts/optimizer.sh index 2d196567..12d84b87 100755 --- a/scripts/optimizer.sh +++ b/scripts/optimizer.sh @@ -1,4 +1,6 @@ -: +#!/bin/bash + +set -x U="cosmwasm" V="0.15.0"