From b4ca20fe044b41b755884659dd29de8cb73dcd96 Mon Sep 17 00:00:00 2001 From: piobab Date: Mon, 4 Sep 2023 22:24:11 +0200 Subject: [PATCH 1/3] Incentives migration. --- Cargo.lock | 180 ++++++++++------- Cargo.toml | 16 +- contracts/incentives/Cargo.toml | 21 +- contracts/incentives/src/contract.rs | 7 +- contracts/incentives/src/error.rs | 3 + contracts/incentives/src/lib.rs | 1 + contracts/incentives/src/migrations/mod.rs | 1 + contracts/incentives/src/migrations/v2_0_0.rs | 183 ++++++++++++++++++ contracts/incentives/tests/tests/mod.rs | 1 + .../tests/tests/test_migration_v2.rs | 127 ++++++++++++ contracts/oracle/osmosis/src/migrations.rs | 4 +- packages/types/src/incentives.rs | 14 +- 12 files changed, 472 insertions(+), 86 deletions(-) create mode 100644 contracts/incentives/src/migrations/mod.rs create mode 100644 contracts/incentives/src/migrations/v2_0_0.rs create mode 100644 contracts/incentives/tests/tests/test_migration_v2.rs diff --git a/Cargo.lock b/Cargo.lock index ce2082c52..ac9a3e0e8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1726,43 +1726,59 @@ checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4" [[package]] name = "mars-address-provider" -version = "1.2.0" +version = "2.0.0" dependencies = [ "bech32", "cosmwasm-schema", "cosmwasm-std", "cw-storage-plus 1.1.0", "cw2 1.1.0", - "mars-owner", - "mars-red-bank-types", + "mars-owner 2.0.0", + "mars-red-bank-types 2.0.0", "serde", "thiserror", ] [[package]] name = "mars-health" -version = "1.2.0" +version = "2.0.0" dependencies = [ "cosmwasm-std", "mars-params", - "mars-red-bank-types", + "mars-red-bank-types 2.0.0", "mars-testing", "thiserror", ] [[package]] name = "mars-incentives" -version = "1.2.0" +version = "1.0.0" +source = "git+https://github.com/mars-protocol/red-bank?rev=29ee60b#29ee60bf560033b7de99a463479d1cda3390343a" +dependencies = [ + "cosmwasm-std", + "cw-storage-plus 1.1.0", + "cw2 1.1.0", + "mars-owner 1.2.0", + "mars-red-bank-types 1.0.0", + "mars-utils 1.0.0", + "thiserror", +] + +[[package]] +name = "mars-incentives" +version = "2.0.0" dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cw-storage-plus 1.1.0", "cw2 1.1.0", - "mars-owner", + "mars-incentives 1.0.0", + "mars-owner 2.0.0", "mars-red-bank", - "mars-red-bank-types", + "mars-red-bank-types 1.0.0", + "mars-red-bank-types 2.0.0", "mars-testing", - "mars-utils", + "mars-utils 2.0.0", "osmosis-std", "test-case", "thiserror", @@ -1770,23 +1786,23 @@ dependencies = [ [[package]] name = "mars-integration-tests" -version = "1.2.0" +version = "2.0.0" dependencies = [ "anyhow", "cosmwasm-std", "cw-it", "cw-multi-test", - "mars-incentives", + "mars-incentives 2.0.0", "mars-oracle-base", "mars-oracle-osmosis", "mars-osmosis", "mars-params", "mars-red-bank", - "mars-red-bank-types", + "mars-red-bank-types 2.0.0", "mars-rewards-collector-osmosis", "mars-swapper-osmosis", "mars-testing", - "mars-utils", + "mars-utils 2.0.0", "osmosis-std", "osmosis-test-tube", "serde", @@ -1794,12 +1810,12 @@ dependencies = [ [[package]] name = "mars-interest-rate" -version = "1.2.0" +version = "2.0.0" dependencies = [ "cosmwasm-schema", "cosmwasm-std", - "mars-red-bank-types", - "mars-utils", + "mars-red-bank-types 2.0.0", + "mars-utils 2.0.0", ] [[package]] @@ -1814,7 +1830,7 @@ dependencies = [ [[package]] name = "mars-mock-pyth" -version = "1.2.0" +version = "2.0.0" dependencies = [ "cosmwasm-schema", "cosmwasm-std", @@ -1823,14 +1839,14 @@ dependencies = [ [[package]] name = "mars-oracle-base" -version = "1.2.0" +version = "2.0.0" dependencies = [ "cosmwasm-std", "cw-storage-plus 1.1.0", "cw2 1.1.0", - "mars-owner", - "mars-red-bank-types", - "mars-utils", + "mars-owner 2.0.0", + "mars-red-bank-types 2.0.0", + "mars-utils 2.0.0", "pyth-sdk-cw", "schemars", "serde", @@ -1839,7 +1855,7 @@ dependencies = [ [[package]] name = "mars-oracle-osmosis" -version = "1.2.0" +version = "2.0.0" dependencies = [ "cosmwasm-schema", "cosmwasm-std", @@ -1847,10 +1863,10 @@ dependencies = [ "cw2 1.1.0", "mars-oracle-base", "mars-osmosis", - "mars-owner", - "mars-red-bank-types", + "mars-owner 2.0.0", + "mars-red-bank-types 2.0.0", "mars-testing", - "mars-utils", + "mars-utils 2.0.0", "osmosis-std", "pyth-sdk-cw", "schemars", @@ -1859,7 +1875,7 @@ dependencies = [ [[package]] name = "mars-oracle-wasm" -version = "1.2.0" +version = "2.0.0" dependencies = [ "astroport", "cosmwasm-schema", @@ -1868,8 +1884,8 @@ dependencies = [ "cw-storage-plus 1.1.0", "cw2 1.1.0", "mars-oracle-base", - "mars-owner", - "mars-red-bank-types", + "mars-owner 2.0.0", + "mars-red-bank-types 2.0.0", "mars-testing", "proptest", "pyth-sdk-cw", @@ -1878,7 +1894,7 @@ dependencies = [ [[package]] name = "mars-osmosis" -version = "1.2.0" +version = "2.0.0" dependencies = [ "cosmwasm-std", "osmosis-std", @@ -1886,6 +1902,19 @@ dependencies = [ "serde", ] +[[package]] +name = "mars-owner" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acd53908ffc561da878ce5ff4f5ec9f25a193af28ec0b6e7c8e6d1a0866d9dfc" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-storage-plus 1.1.0", + "schemars", + "thiserror", +] + [[package]] name = "mars-owner" version = "2.0.0" @@ -1901,7 +1930,7 @@ dependencies = [ [[package]] name = "mars-params" -version = "1.2.0" +version = "2.0.0" dependencies = [ "anyhow", "cosmwasm-schema", @@ -1910,17 +1939,17 @@ dependencies = [ "cw-storage-plus 1.1.0", "cw2 1.1.0", "mars-interest-rate", - "mars-owner", - "mars-red-bank-types", + "mars-owner 2.0.0", + "mars-red-bank-types 2.0.0", "mars-testing", - "mars-utils", + "mars-utils 2.0.0", "test-case", "thiserror", ] [[package]] name = "mars-red-bank" -version = "1.2.0" +version = "2.0.0" dependencies = [ "anyhow", "cosmwasm-schema", @@ -1932,11 +1961,11 @@ dependencies = [ "mars-health", "mars-interest-rate", "mars-liquidation", - "mars-owner", + "mars-owner 2.0.0", "mars-params", - "mars-red-bank-types", + "mars-red-bank-types 2.0.0", "mars-testing", - "mars-utils", + "mars-utils 2.0.0", "pyth-sdk-cw", "test-case", "thiserror", @@ -1944,26 +1973,38 @@ dependencies = [ [[package]] name = "mars-red-bank-types" -version = "1.2.0" +version = "1.0.0" +source = "git+https://github.com/mars-protocol/red-bank?rev=29ee60b#29ee60bf560033b7de99a463479d1cda3390343a" dependencies = [ "cosmwasm-schema", "cosmwasm-std", - "mars-owner", - "mars-utils", + "mars-owner 1.2.0", + "mars-utils 1.0.0", + "thiserror", +] + +[[package]] +name = "mars-red-bank-types" +version = "2.0.0" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "mars-owner 2.0.0", + "mars-utils 2.0.0", "strum 0.25.0", "thiserror", ] [[package]] name = "mars-rewards-collector-base" -version = "1.2.0" +version = "2.0.0" dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cw-storage-plus 1.1.0", - "mars-owner", - "mars-red-bank-types", - "mars-utils", + "mars-owner 2.0.0", + "mars-red-bank-types 2.0.0", + "mars-utils 2.0.0", "schemars", "serde", "thiserror", @@ -1971,34 +2012,34 @@ dependencies = [ [[package]] name = "mars-rewards-collector-neutron" -version = "1.2.0" +version = "2.0.0" dependencies = [ "cosmwasm-std", "cw2 1.1.0", - "mars-red-bank-types", + "mars-red-bank-types 2.0.0", "mars-rewards-collector-base", "neutron-sdk", ] [[package]] name = "mars-rewards-collector-osmosis" -version = "1.2.0" +version = "2.0.0" dependencies = [ "cosmwasm-std", "cw2 1.1.0", "mars-osmosis", - "mars-owner", - "mars-red-bank-types", + "mars-owner 2.0.0", + "mars-red-bank-types 2.0.0", "mars-rewards-collector-base", "mars-testing", - "mars-utils", + "mars-utils 2.0.0", "osmosis-std", "serde", ] [[package]] name = "mars-swapper-astroport" -version = "1.2.0" +version = "2.0.0" dependencies = [ "anyhow", "astroport", @@ -2007,7 +2048,7 @@ dependencies = [ "cw-it", "cw2 1.1.0", "mars-oracle-wasm", - "mars-red-bank-types", + "mars-red-bank-types 2.0.0", "mars-swapper-base", "mars-testing", "test-case", @@ -2015,14 +2056,14 @@ dependencies = [ [[package]] name = "mars-swapper-base" -version = "1.2.0" +version = "2.0.0" dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cw-paginate", "cw-storage-plus 1.1.0", - "mars-owner", - "mars-red-bank-types", + "mars-owner 2.0.0", + "mars-red-bank-types 2.0.0", "schemars", "serde", "thiserror", @@ -2030,15 +2071,15 @@ dependencies = [ [[package]] name = "mars-swapper-mock" -version = "1.2.0" +version = "2.0.0" dependencies = [ "cosmwasm-std", - "mars-red-bank-types", + "mars-red-bank-types 2.0.0", ] [[package]] name = "mars-swapper-osmosis" -version = "1.2.0" +version = "2.0.0" dependencies = [ "anyhow", "cosmwasm-schema", @@ -2046,15 +2087,15 @@ dependencies = [ "cw-it", "cw2 1.1.0", "mars-osmosis", - "mars-owner", - "mars-red-bank-types", + "mars-owner 2.0.0", + "mars-red-bank-types 2.0.0", "mars-swapper-base", "osmosis-std", ] [[package]] name = "mars-testing" -version = "1.2.0" +version = "2.0.0" dependencies = [ "anyhow", "astroport", @@ -2063,15 +2104,15 @@ dependencies = [ "cw-it", "cw-multi-test", "mars-address-provider", - "mars-incentives", + "mars-incentives 2.0.0", "mars-mock-pyth", "mars-oracle-osmosis", "mars-oracle-wasm", "mars-osmosis", - "mars-owner", + "mars-owner 2.0.0", "mars-params", "mars-red-bank", - "mars-red-bank-types", + "mars-red-bank-types 2.0.0", "mars-rewards-collector-osmosis", "mars-swapper-astroport", "osmosis-std", @@ -2081,7 +2122,16 @@ dependencies = [ [[package]] name = "mars-utils" -version = "1.2.0" +version = "1.0.0" +source = "git+https://github.com/mars-protocol/red-bank?rev=29ee60b#29ee60bf560033b7de99a463479d1cda3390343a" +dependencies = [ + "cosmwasm-std", + "thiserror", +] + +[[package]] +name = "mars-utils" +version = "2.0.0" dependencies = [ "cosmwasm-std", "thiserror", diff --git a/Cargo.toml b/Cargo.toml index 71a9d92e2..814aeb606 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,7 +20,7 @@ members = [ resolver = "2" [workspace.package] -version = "1.2.0" +version = "2.0.0" authors = [ "Gabe R. ", "Larry Engineer ", @@ -65,13 +65,13 @@ proptest = "1.2.0" test-case = "3.1.0" # packages -mars-health = { path = "./packages/health" } -mars-interest-rate = { path = "./packages/interest-rate" } -mars-liquidation = { path = "./packages/liquidation" } -mars-osmosis = { path = "./packages/chains/osmosis" } -mars-red-bank-types = { path = "./packages/types" } -mars-testing = { path = "./packages/testing" } -mars-utils = { path = "./packages/utils" } +mars-health = { path = "./packages/health" } +mars-interest-rate = { path = "./packages/interest-rate" } +mars-liquidation = { path = "./packages/liquidation" } +mars-osmosis = { path = "./packages/chains/osmosis" } +mars-red-bank-types = { path = "./packages/types" } +mars-testing = { path = "./packages/testing" } +mars-utils = { path = "./packages/utils" } # contracts mars-address-provider = { path = "./contracts/address-provider" } diff --git a/contracts/incentives/Cargo.toml b/contracts/incentives/Cargo.toml index 68d95a662..2d2691de6 100644 --- a/contracts/incentives/Cargo.toml +++ b/contracts/incentives/Cargo.toml @@ -19,16 +19,21 @@ doctest = false backtraces = ["cosmwasm-std/backtraces", "mars-testing/backtraces", "mars-utils/backtraces", "mars-red-bank/backtraces"] [dependencies] -cosmwasm-std = { workspace = true } -cw2 = { workspace = true } -cw-storage-plus = { workspace = true } -mars-owner = { workspace = true } -mars-red-bank-types = { workspace = true } -mars-utils = { workspace = true } -thiserror = { workspace = true } +cosmwasm-schema = { workspace = true } +cosmwasm-std = { workspace = true } +cw2 = { workspace = true } +cw-storage-plus = { workspace = true } +mars-owner = { workspace = true } +mars-red-bank-types = { workspace = true } +mars-utils = { workspace = true } +thiserror = { workspace = true } + +# Old red-bank types used for migration. +# It comes from v1.0.0 tag with one commit enabling library feature +mars-incentives-old = { package = "mars-incentives", git = "https://github.com/mars-protocol/red-bank", rev = "29ee60b", features = ["library"] } +mars-red-bank-types-old = { package = "mars-red-bank-types", git = "https://github.com/mars-protocol/red-bank", rev = "29ee60b" } [dev-dependencies] -cosmwasm-schema = { workspace = true } mars-testing = { workspace = true } osmosis-std = { workspace = true } mars-red-bank = { workspace = true } diff --git a/contracts/incentives/src/contract.rs b/contracts/incentives/src/contract.rs index 22f3d099e..849a0055b 100644 --- a/contracts/incentives/src/contract.rs +++ b/contracts/incentives/src/contract.rs @@ -22,6 +22,7 @@ use crate::{ helpers::{ self, compute_user_accrued_rewards, compute_user_unclaimed_rewards, update_incentive_index, }, + migrations, state::{ self, CONFIG, DEFAULT_LIMIT, EMISSIONS, EPOCH_DURATION, INCENTIVE_STATES, MAX_LIMIT, OWNER, USER_ASSET_INDICES, USER_UNCLAIMED_REWARDS, WHITELIST, WHITELIST_COUNT, @@ -765,6 +766,8 @@ pub fn query_emissions( /// MIGRATION #[cfg_attr(not(feature = "library"), entry_point)] -pub fn migrate(_deps: DepsMut, _env: Env, _msg: MigrateMsg) -> Result { - Ok(Response::default()) +pub fn migrate(deps: DepsMut, env: Env, msg: MigrateMsg) -> Result { + match msg { + MigrateMsg::V1_0_0ToV2_0_0(updates) => migrations::v2_0_0::migrate(deps, env, updates), + } } diff --git a/contracts/incentives/src/error.rs b/contracts/incentives/src/error.rs index 7a77a92f8..167efdfb0 100644 --- a/contracts/incentives/src/error.rs +++ b/contracts/incentives/src/error.rs @@ -66,6 +66,9 @@ pub enum ContractError { DuplicateDenom { denom: String, }, + + #[error("{0}")] + Version(#[from] cw2::VersionError), } impl From for StdError { diff --git a/contracts/incentives/src/lib.rs b/contracts/incentives/src/lib.rs index 627a6e152..88d83aeaf 100644 --- a/contracts/incentives/src/lib.rs +++ b/contracts/incentives/src/lib.rs @@ -1,6 +1,7 @@ pub mod contract; mod error; pub mod helpers; +pub mod migrations; pub mod state; pub use error::ContractError; diff --git a/contracts/incentives/src/migrations/mod.rs b/contracts/incentives/src/migrations/mod.rs new file mode 100644 index 000000000..7592b6f12 --- /dev/null +++ b/contracts/incentives/src/migrations/mod.rs @@ -0,0 +1 @@ +pub mod v2_0_0; diff --git a/contracts/incentives/src/migrations/v2_0_0.rs b/contracts/incentives/src/migrations/v2_0_0.rs new file mode 100644 index 000000000..da2567a7f --- /dev/null +++ b/contracts/incentives/src/migrations/v2_0_0.rs @@ -0,0 +1,183 @@ +use std::collections::HashMap; + +use cosmwasm_std::{DepsMut, Env, Order, Response, StdResult, Uint128}; +use cw2::{assert_contract_version, set_contract_version}; +use mars_owner::OwnerInit; +use mars_red_bank_types::incentives::{Config, IncentiveState, V2Updates}; + +use crate::{ + contract::{CONTRACT_NAME, CONTRACT_VERSION, MIN_EPOCH_DURATION}, + error::ContractError, + state::{CONFIG, EPOCH_DURATION, INCENTIVE_STATES, OWNER, USER_ASSET_INDICES}, +}; + +const FROM_VERSION: &str = "1.0.0"; + +pub mod v1_state { + use cosmwasm_schema::cw_serde; + use cosmwasm_std::{Addr, Decimal, Uint128}; + use cw_storage_plus::{Item, Map}; + use mars_red_bank_types_old::incentives::{AssetIncentive, Config}; + + pub const OWNER: Item = Item::new("owner"); + pub const CONFIG: Item = Item::new("config"); + + pub const ASSET_INCENTIVES: Map<&str, AssetIncentive> = Map::new("incentives"); + pub const USER_ASSET_INDICES: Map<(&Addr, &str), Decimal> = Map::new("indices"); + pub const USER_UNCLAIMED_REWARDS: Map<&Addr, Uint128> = Map::new("unclaimed_rewards"); + + #[cw_serde] + pub enum OwnerState { + B(OwnerSetNoneProposed), + } + + #[cw_serde] + pub struct OwnerSetNoneProposed { + pub owner: Addr, + } + + pub fn current_owner(state: OwnerState) -> Addr { + match state { + OwnerState::B(b) => b.owner, + } + } +} + +pub fn migrate(mut deps: DepsMut, env: Env, updates: V2Updates) -> Result { + // make sure we're migrating the correct contract and from the correct version + assert_contract_version(deps.storage, &format!("crates.io:{CONTRACT_NAME}"), FROM_VERSION)?; + + // Owner package updated, re-initializing + let old_owner_state = v1_state::OWNER.load(deps.storage)?; + let old_owner = v1_state::current_owner(old_owner_state); + v1_state::OWNER.remove(deps.storage); + OWNER.initialize( + deps.storage, + deps.api, + OwnerInit::SetInitialOwner { + owner: old_owner.to_string(), + }, + )?; + + // CONFIG updated, re-initializing + let old_config_state = v1_state::CONFIG.load(deps.storage)?; + v1_state::CONFIG.remove(deps.storage); + CONFIG.save( + deps.storage, + &Config { + address_provider: old_config_state.address_provider, + max_whitelisted_denoms: updates.max_whitelisted_denoms, + }, + )?; + + // EPOCH_DURATION not existent in v1, initializing + if updates.epoch_duration < MIN_EPOCH_DURATION { + return Err(ContractError::EpochDurationTooShort { + min_epoch_duration: MIN_EPOCH_DURATION, + }); + } + EPOCH_DURATION.save(deps.storage, &updates.epoch_duration)?; + + migrate_idx(&mut deps, env, &old_config_state.mars_denom)?; + + set_contract_version(deps.storage, format!("crates.io:{CONTRACT_NAME}"), CONTRACT_VERSION)?; + + Ok(Response::new() + .add_attribute("action", "migrate") + .add_attribute("from_version", FROM_VERSION) + .add_attribute("to_version", CONTRACT_VERSION)) +} + +fn migrate_idx(deps: &mut DepsMut, env: Env, mars_denom: &str) -> Result<(), ContractError> { + let current_block_time = env.block.time.seconds(); + + let config = CONFIG.load(deps.storage)?; + + let red_bank_addr = mars_red_bank_types::address_provider::helpers::query_contract_addr( + deps.as_ref(), + &config.address_provider, + mars_red_bank_types::address_provider::MarsAddressType::RedBank, + )?; + + let asset_incentives: StdResult> = + v1_state::ASSET_INCENTIVES.range(deps.storage, None, None, Order::Ascending).collect(); + let mut asset_incentives = asset_incentives?; + + for (denom, asset_incentive) in asset_incentives.iter_mut() { + let market: mars_red_bank_types_old::red_bank::Market = deps.querier.query_wasm_smart( + red_bank_addr.clone(), + &mars_red_bank_types_old::red_bank::QueryMsg::Market { + denom: denom.clone(), + }, + )?; + + mars_incentives_old::helpers::update_asset_incentive_index( + asset_incentive, + market.collateral_total_scaled, + current_block_time, + )?; + + // Update incentive state for collateral and incentive denom (Mars) + INCENTIVE_STATES.save( + deps.storage, + (denom, mars_denom), + &IncentiveState { + index: asset_incentive.index, + last_updated: current_block_time, + }, + )?; + } + + let user_asset_indices: StdResult> = + v1_state::USER_ASSET_INDICES.range(deps.storage, None, None, Order::Ascending).collect(); + let user_asset_indices = user_asset_indices?; + + let user_unclaimed_rewards: StdResult> = v1_state::USER_UNCLAIMED_REWARDS + .range(deps.storage, None, None, Order::Ascending) + .collect(); + let mut user_unclaimed_rewards = user_unclaimed_rewards?; + + for ((user, denom), user_asset_index) in user_asset_indices { + let collateral: mars_red_bank_types_old::red_bank::UserCollateralResponse = + deps.querier.query_wasm_smart( + red_bank_addr.clone(), + &mars_red_bank_types_old::red_bank::QueryMsg::UserCollateral { + user: user.to_string(), + denom: denom.clone(), + }, + )?; + + // If user's balance is 0 there should be no rewards to accrue, so we don't care about + // updating indexes. If the user's balance changes, the indexes will be updated correctly at + // that point in time. + if collateral.amount_scaled.is_zero() { + continue; + } + + let denom_idx = asset_incentives.get(&denom); + if let Some(asset_incentive) = denom_idx { + if user_asset_index != asset_incentive.index { + // Compute user accrued rewards + let asset_accrued_rewards = + mars_incentives_old::helpers::compute_user_accrued_rewards( + collateral.amount_scaled, + user_asset_index, + asset_incentive.index, + )?; + + // Update user unclaimed rewards + *user_unclaimed_rewards.entry(user.clone()).or_insert_with(Uint128::zero) += + asset_accrued_rewards; + } + + // Update user asset index + USER_ASSET_INDICES.save( + deps.storage, + ((&user, ""), &denom, mars_denom), + &asset_incentive.index, + )?; + } + } + + Ok(()) +} diff --git a/contracts/incentives/tests/tests/mod.rs b/contracts/incentives/tests/tests/mod.rs index 85b241bca..e95d9f695 100644 --- a/contracts/incentives/tests/tests/mod.rs +++ b/contracts/incentives/tests/tests/mod.rs @@ -4,6 +4,7 @@ mod test_admin; mod test_balance_change; mod test_claim_rewards; mod test_indices_usage; +mod test_migration_v2; mod test_quering; mod test_set_asset_incentive; mod test_update_owner; diff --git a/contracts/incentives/tests/tests/test_migration_v2.rs b/contracts/incentives/tests/tests/test_migration_v2.rs new file mode 100644 index 000000000..e9d075782 --- /dev/null +++ b/contracts/incentives/tests/tests/test_migration_v2.rs @@ -0,0 +1,127 @@ +use cosmwasm_std::{attr, testing::mock_env, Addr, Event}; +use cw2::VersionError; +use mars_incentives::{ + contract::migrate, + migrations::v2_0_0::v1_state::{self, OwnerSetNoneProposed}, + state::OWNER, + ContractError, +}; +use mars_red_bank_types::incentives::{MigrateMsg, V2Updates}; +use mars_red_bank_types_old::incentives::Config; +use mars_testing::mock_dependencies; + +#[test] +fn wrong_contract_name() { + let mut deps = mock_dependencies(&[]); + cw2::set_contract_version(deps.as_mut().storage, "contract_xyz", "1.0.0").unwrap(); + + let err = migrate( + deps.as_mut(), + mock_env(), + MigrateMsg::V1_0_0ToV2_0_0(V2Updates { + epoch_duration: 604800, + max_whitelisted_denoms: 10, + }), + ) + .unwrap_err(); + + assert_eq!( + err, + ContractError::Version(VersionError::WrongContract { + expected: "crates.io:mars-incentives".to_string(), + found: "contract_xyz".to_string() + }) + ); +} + +#[test] +fn wrong_contract_version() { + let mut deps = mock_dependencies(&[]); + cw2::set_contract_version(deps.as_mut().storage, "crates.io:mars-incentives", "4.1.0").unwrap(); + + let err = migrate( + deps.as_mut(), + mock_env(), + MigrateMsg::V1_0_0ToV2_0_0(V2Updates { + epoch_duration: 604800, + max_whitelisted_denoms: 10, + }), + ) + .unwrap_err(); + + assert_eq!( + err, + ContractError::Version(VersionError::WrongVersion { + expected: "1.0.0".to_string(), + found: "4.1.0".to_string() + }) + ); +} + +#[test] +fn successful_migration() { + let mut deps = mock_dependencies(&[]); + cw2::set_contract_version(deps.as_mut().storage, "crates.io:mars-incentives", "1.0.0").unwrap(); + + let old_owner = "spiderman_246"; + v1_state::OWNER + .save( + deps.as_mut().storage, + &v1_state::OwnerState::B(OwnerSetNoneProposed { + owner: Addr::unchecked(old_owner), + }), + ) + .unwrap(); + + let mars_denom = "umars"; + v1_state::CONFIG + .save( + deps.as_mut().storage, + &Config { + address_provider: Addr::unchecked("address_provider"), + mars_denom: mars_denom.to_string(), + }, + ) + .unwrap(); + + let res = migrate( + deps.as_mut(), + mock_env(), + MigrateMsg::V1_0_0ToV2_0_0(V2Updates { + epoch_duration: 604800, + max_whitelisted_denoms: 10, + }), + ) + .unwrap(); + + assert_eq!(res.messages, vec![]); + assert_eq!(res.events, vec![] as Vec); + assert!(res.data.is_none()); + assert_eq!( + res.attributes, + vec![attr("action", "migrate"), attr("from_version", "1.0.0"), attr("to_version", "2.0.0")] + ); + + // let set_health_contract = + // HEALTH_CONTRACT.load(deps.as_ref().storage).unwrap().address().to_string(); + // assert_eq!(health_contract, set_health_contract); + + // let set_params = PARAMS.load(deps.as_ref().storage).unwrap().address().to_string(); + // assert_eq!(params, set_params); + + // let set_incentives = INCENTIVES.load(deps.as_ref().storage).unwrap().addr.to_string(); + // assert_eq!(incentives, set_incentives); + + // let set_swapper = SWAPPER.load(deps.as_ref().storage).unwrap().address().to_string(); + // assert_eq!(swapper, set_swapper); + + // let set_rewards = REWARDS_COLLECTOR.may_load(deps.as_ref().storage).unwrap(); + // assert_eq!(None, set_rewards); + + let o = OWNER.query(deps.as_ref().storage).unwrap(); + assert_eq!(old_owner.to_string(), o.owner.unwrap()); + assert!(o.proposed.is_none()); + assert!(o.initialized); + assert!(!o.abolished); + assert!(o.emergency_owner.is_none()); +} diff --git a/contracts/oracle/osmosis/src/migrations.rs b/contracts/oracle/osmosis/src/migrations.rs index 8ffa77965..a74db63d1 100644 --- a/contracts/oracle/osmosis/src/migrations.rs +++ b/contracts/oracle/osmosis/src/migrations.rs @@ -114,7 +114,7 @@ pub mod v1_0_1 { vec![ attr("action", "migrate"), attr("from_version", "1.0.1"), - attr("to_version", "1.2.0") + attr("to_version", "2.0.0") ] ); @@ -149,7 +149,7 @@ pub mod v1_0_1 { vec![ attr("action", "migrate"), attr("from_version", "1.0.1"), - attr("to_version", "1.2.0") + attr("to_version", "2.0.0") ] ); diff --git a/packages/types/src/incentives.rs b/packages/types/src/incentives.rs index c94cad794..db37b8375 100644 --- a/packages/types/src/incentives.rs +++ b/packages/types/src/incentives.rs @@ -252,7 +252,19 @@ pub enum QueryMsg { } #[cw_serde] -pub struct MigrateMsg {} +pub struct V2Updates { + /// The amount of time in seconds for each incentive epoch. This is the minimum amount of time + /// that an incentive can last, and each incentive must be a multiple of this duration. + pub epoch_duration: u64, + /// The maximum number of incentive denoms that can be whitelisted at any given time. This is + /// a guard against accidentally whitelisting too many denoms, which could cause max gas errors. + pub max_whitelisted_denoms: u8, +} + +#[cw_serde] +pub enum MigrateMsg { + V1_0_0ToV2_0_0(V2Updates), +} #[cw_serde] pub struct EmissionResponse { From 561ef8c9769e6d35c8cdc42f4d40d429c08bc71f Mon Sep 17 00:00:00 2001 From: piobab Date: Tue, 5 Sep 2023 15:13:32 +0200 Subject: [PATCH 2/3] Add tests for migration. --- contracts/incentives/Cargo.toml | 2 +- contracts/incentives/src/migrations/v2_0_0.rs | 74 ++-- .../tests/tests/test_migration_v2.rs | 338 ++++++++++++++++-- 3 files changed, 356 insertions(+), 58 deletions(-) diff --git a/contracts/incentives/Cargo.toml b/contracts/incentives/Cargo.toml index 2d2691de6..7cde6a9a7 100644 --- a/contracts/incentives/Cargo.toml +++ b/contracts/incentives/Cargo.toml @@ -30,7 +30,7 @@ thiserror = { workspace = true } # Old red-bank types used for migration. # It comes from v1.0.0 tag with one commit enabling library feature -mars-incentives-old = { package = "mars-incentives", git = "https://github.com/mars-protocol/red-bank", rev = "29ee60b", features = ["library"] } +mars-incentives-old = { package = "mars-incentives", git = "https://github.com/mars-protocol/red-bank", rev = "29ee60b", features = ["library"] } mars-red-bank-types-old = { package = "mars-red-bank-types", git = "https://github.com/mars-protocol/red-bank", rev = "29ee60b" } [dev-dependencies] diff --git a/contracts/incentives/src/migrations/v2_0_0.rs b/contracts/incentives/src/migrations/v2_0_0.rs index da2567a7f..606ef075f 100644 --- a/contracts/incentives/src/migrations/v2_0_0.rs +++ b/contracts/incentives/src/migrations/v2_0_0.rs @@ -8,7 +8,10 @@ use mars_red_bank_types::incentives::{Config, IncentiveState, V2Updates}; use crate::{ contract::{CONTRACT_NAME, CONTRACT_VERSION, MIN_EPOCH_DURATION}, error::ContractError, - state::{CONFIG, EPOCH_DURATION, INCENTIVE_STATES, OWNER, USER_ASSET_INDICES}, + state::{ + CONFIG, EPOCH_DURATION, INCENTIVE_STATES, OWNER, USER_ASSET_INDICES, + USER_UNCLAIMED_REWARDS, WHITELIST, WHITELIST_COUNT, + }, }; const FROM_VERSION: &str = "1.0.0"; @@ -70,6 +73,10 @@ pub fn migrate(mut deps: DepsMut, env: Env, updates: V2Updates) -> Result Result Result Result<(), ContractError> { +// Migrate indices and unclaimed rewards from v1 to v2 with helpers from v1.0.0 tag: +// https://github.com/mars-protocol/red-bank/blob/v1.0.0/contracts/incentives/src/helpers.rs +// +// This is done by querying the Red Bank contract for the collateral total supply and +// user collateral amount for each collateral denom. +fn migrate_indices_and_unclaimed_rewards( + deps: &mut DepsMut, + env: Env, + mars_denom: &str, +) -> Result<(), ContractError> { let current_block_time = env.block.time.seconds(); let config = CONFIG.load(deps.storage)?; @@ -99,14 +115,15 @@ fn migrate_idx(deps: &mut DepsMut, env: Env, mars_denom: &str) -> Result<(), Con mars_red_bank_types::address_provider::MarsAddressType::RedBank, )?; - let asset_incentives: StdResult> = - v1_state::ASSET_INCENTIVES.range(deps.storage, None, None, Order::Ascending).collect(); - let mut asset_incentives = asset_incentives?; + let mut asset_incentives = v1_state::ASSET_INCENTIVES + .range(deps.storage, None, None, Order::Ascending) + .collect::>>()?; + v1_state::ASSET_INCENTIVES.clear(deps.storage); for (denom, asset_incentive) in asset_incentives.iter_mut() { - let market: mars_red_bank_types_old::red_bank::Market = deps.querier.query_wasm_smart( + let market: mars_red_bank_types::red_bank::Market = deps.querier.query_wasm_smart( red_bank_addr.clone(), - &mars_red_bank_types_old::red_bank::QueryMsg::Market { + &mars_red_bank_types::red_bank::QueryMsg::Market { denom: denom.clone(), }, )?; @@ -128,34 +145,34 @@ fn migrate_idx(deps: &mut DepsMut, env: Env, mars_denom: &str) -> Result<(), Con )?; } - let user_asset_indices: StdResult> = - v1_state::USER_ASSET_INDICES.range(deps.storage, None, None, Order::Ascending).collect(); - let user_asset_indices = user_asset_indices?; + let user_asset_indices = v1_state::USER_ASSET_INDICES + .range(deps.storage, None, None, Order::Ascending) + .collect::>>()?; + v1_state::USER_ASSET_INDICES.clear(deps.storage); - let user_unclaimed_rewards: StdResult> = v1_state::USER_UNCLAIMED_REWARDS + let mut user_unclaimed_rewards = v1_state::USER_UNCLAIMED_REWARDS .range(deps.storage, None, None, Order::Ascending) - .collect(); - let mut user_unclaimed_rewards = user_unclaimed_rewards?; + .collect::>>()?; + v1_state::USER_UNCLAIMED_REWARDS.clear(deps.storage); for ((user, denom), user_asset_index) in user_asset_indices { - let collateral: mars_red_bank_types_old::red_bank::UserCollateralResponse = + let collateral: mars_red_bank_types::red_bank::UserCollateralResponse = deps.querier.query_wasm_smart( red_bank_addr.clone(), - &mars_red_bank_types_old::red_bank::QueryMsg::UserCollateral { + &mars_red_bank_types::red_bank::QueryMsg::UserCollateral { user: user.to_string(), + account_id: None, denom: denom.clone(), }, )?; - // If user's balance is 0 there should be no rewards to accrue, so we don't care about - // updating indexes. If the user's balance changes, the indexes will be updated correctly at - // that point in time. - if collateral.amount_scaled.is_zero() { - continue; - } - + // Get asset incentive for a denom. It should be available but just in case we don't unwrap let denom_idx = asset_incentives.get(&denom); if let Some(asset_incentive) = denom_idx { + // Since we didn't track unclaimed rewards per collateral denom in v1 we add them + // to the user unclaimed rewards for the first user collateral denom. + let mut unclaimed_rewards = user_unclaimed_rewards.remove(&user).unwrap_or_default(); + if user_asset_index != asset_incentive.index { // Compute user accrued rewards let asset_accrued_rewards = @@ -165,9 +182,16 @@ fn migrate_idx(deps: &mut DepsMut, env: Env, mars_denom: &str) -> Result<(), Con asset_incentive.index, )?; + unclaimed_rewards += asset_accrued_rewards; + } + + if !unclaimed_rewards.is_zero() { // Update user unclaimed rewards - *user_unclaimed_rewards.entry(user.clone()).or_insert_with(Uint128::zero) += - asset_accrued_rewards; + USER_UNCLAIMED_REWARDS.save( + deps.storage, + ((&user, ""), &denom, mars_denom), + &unclaimed_rewards, + )?; } // Update user asset index diff --git a/contracts/incentives/tests/tests/test_migration_v2.rs b/contracts/incentives/tests/tests/test_migration_v2.rs index e9d075782..f26d17246 100644 --- a/contracts/incentives/tests/tests/test_migration_v2.rs +++ b/contracts/incentives/tests/tests/test_migration_v2.rs @@ -1,14 +1,23 @@ -use cosmwasm_std::{attr, testing::mock_env, Addr, Event}; +use std::collections::HashMap; + +use cosmwasm_std::{ + attr, testing::mock_env, Addr, Decimal, Event, Order, StdResult, Timestamp, Uint128, +}; use cw2::VersionError; use mars_incentives::{ contract::migrate, migrations::v2_0_0::v1_state::{self, OwnerSetNoneProposed}, - state::OWNER, + state::{ + CONFIG, INCENTIVE_STATES, OWNER, USER_ASSET_INDICES, USER_UNCLAIMED_REWARDS, WHITELIST, + WHITELIST_COUNT, + }, ContractError, }; -use mars_red_bank_types::incentives::{MigrateMsg, V2Updates}; -use mars_red_bank_types_old::incentives::Config; -use mars_testing::mock_dependencies; +use mars_red_bank_types::{ + incentives::{Config, IncentiveState, MigrateMsg, V2Updates}, + red_bank::{Market, UserCollateralResponse}, +}; +use mars_testing::{mock_dependencies, MockEnvParams}; #[test] fn wrong_contract_name() { @@ -74,22 +83,139 @@ fn successful_migration() { .unwrap(); let mars_denom = "umars"; - v1_state::CONFIG - .save( - deps.as_mut().storage, - &Config { - address_provider: Addr::unchecked("address_provider"), - mars_denom: mars_denom.to_string(), - }, - ) + let old_config = mars_red_bank_types_old::incentives::Config { + address_provider: Addr::unchecked("address_provider"), + mars_denom: mars_denom.to_string(), + }; + v1_state::CONFIG.save(deps.as_mut().storage, &old_config).unwrap(); + + let atom_denom = "uatom"; + let usdc_denom = "uusdc"; + let osmo_denom = "uosmo"; + + let incentive_start_time = 500_000u64; + let duration = 864_000u64; // 10 days + let migration_time = incentive_start_time + duration + 100u64; + + // The incentive will have to be recalculated for the entire duration + let atom_incentive = mars_red_bank_types_old::incentives::AssetIncentive { + emission_per_second: Uint128::new(100), + start_time: incentive_start_time, + duration, + index: Decimal::one(), + last_updated: incentive_start_time, + }; + v1_state::ASSET_INCENTIVES.save(deps.as_mut().storage, atom_denom, &atom_incentive).unwrap(); + + // The incentive will have to be recalculated for the part of the duration + let usdc_incentive = mars_red_bank_types_old::incentives::AssetIncentive { + emission_per_second: Uint128::new(50), + start_time: incentive_start_time, + duration, + index: Decimal::from_ratio(12u128, 10u128), + last_updated: incentive_start_time + 86400u64, // + 1 day + }; + v1_state::ASSET_INCENTIVES.save(deps.as_mut().storage, usdc_denom, &usdc_incentive).unwrap(); + + // The incentive won't be recalculated because it finished before migration time + let osmo_incentive = mars_red_bank_types_old::incentives::AssetIncentive { + emission_per_second: Uint128::new(50), + start_time: incentive_start_time, + duration, + index: Decimal::from_ratio(15u128, 10u128), + last_updated: migration_time - 10u64, + }; + v1_state::ASSET_INCENTIVES.save(deps.as_mut().storage, osmo_denom, &osmo_incentive).unwrap(); + + // Set user asset indices for all incentive assets + let user_1 = Addr::unchecked("user_1"); + let user_1_atom_idx_old = Decimal::one(); + v1_state::USER_ASSET_INDICES + .save(deps.as_mut().storage, (&user_1, atom_denom), &user_1_atom_idx_old) + .unwrap(); + let user_1_usdc_idx_old = Decimal::one(); + v1_state::USER_ASSET_INDICES + .save(deps.as_mut().storage, (&user_1, usdc_denom), &user_1_usdc_idx_old) + .unwrap(); + let user_1_osmo_idx_old = Decimal::one(); + v1_state::USER_ASSET_INDICES + .save(deps.as_mut().storage, (&user_1, osmo_denom), &user_1_osmo_idx_old) .unwrap(); + // Set user asset indices only for osmo. Index is up to date with asset incentive index. No rewards accured. + let user_2 = Addr::unchecked("user_2"); + let user_2_osmo_idx_old = osmo_incentive.index; + v1_state::USER_ASSET_INDICES + .save(deps.as_mut().storage, (&user_2, osmo_denom), &user_2_osmo_idx_old) + .unwrap(); + + // Set user asset indices only for atom + let user_3 = Addr::unchecked("user_3"); + let user_3_atom_idx_old = Decimal::one(); + v1_state::USER_ASSET_INDICES + .save(deps.as_mut().storage, (&user_3, atom_denom), &user_3_atom_idx_old) + .unwrap(); + + // Set unclaimed rewards only for user_1. + // user_2 doesn't accrue any new rewards because osmo incentive finished before migration time. + // user_3 not set in order to check if new state creation works for him. + let user_1_unclaimed_rewards = Uint128::new(1000); + v1_state::USER_UNCLAIMED_REWARDS + .save(deps.as_mut().storage, &user_1, &user_1_unclaimed_rewards) + .unwrap(); + + // Setup markets + let atom_collateral_total_scaled = Uint128::new(100_000_000); + deps.querier.set_redbank_market(create_market(atom_denom, atom_collateral_total_scaled)); + let usdc_collateral_total_scaled = Uint128::new(1_250_000_000); + deps.querier.set_redbank_market(create_market(usdc_denom, usdc_collateral_total_scaled)); + let osmo_collateral_total_scaled = Uint128::new(520_000_000); + deps.querier.set_redbank_market(create_market(osmo_denom, osmo_collateral_total_scaled)); + + // Setup atom collaterals. Sum of all positions should be equal to atom_collateral_total_scaled. + let user_1_atom_amount_scaled = Uint128::zero(); // Setting zero to check if user_1 index is updated correctly + deps.querier.set_red_bank_user_collateral( + &user_1, + create_user_collateral(atom_denom, user_1_atom_amount_scaled), + ); + let user_3_atom_amount_scaled = atom_collateral_total_scaled; + deps.querier.set_red_bank_user_collateral( + &user_3, + create_user_collateral(atom_denom, user_3_atom_amount_scaled), + ); + + // Setup usdc collaterals. Sum of all positions should be equal to usdc_collateral_total_scaled + let user_1_usdc_amount_scaled = usdc_collateral_total_scaled; + deps.querier.set_red_bank_user_collateral( + &user_1, + create_user_collateral(usdc_denom, user_1_usdc_amount_scaled), + ); + + // Setup osmo collaterals. Sum of all positions should be equal to osmo_collateral_total_scaled + let user_1_osmo_amount_scaled = Uint128::new(120_000_000); + deps.querier.set_red_bank_user_collateral( + &user_1, + create_user_collateral(osmo_denom, user_1_osmo_amount_scaled), + ); + let user_2_osmo_amount_scaled = Uint128::new(400_000_000); + deps.querier.set_red_bank_user_collateral( + &user_2, + create_user_collateral(osmo_denom, user_2_osmo_amount_scaled), + ); + + let env = mars_testing::mock_env(MockEnvParams { + block_time: Timestamp::from_seconds(migration_time), + ..Default::default() + }); + + let epoch_duration = 604800; + let max_whitelisted_denoms = 12; let res = migrate( deps.as_mut(), - mock_env(), + env, MigrateMsg::V1_0_0ToV2_0_0(V2Updates { - epoch_duration: 604800, - max_whitelisted_denoms: 10, + epoch_duration, + max_whitelisted_denoms, }), ) .unwrap(); @@ -102,26 +228,174 @@ fn successful_migration() { vec![attr("action", "migrate"), attr("from_version", "1.0.0"), attr("to_version", "2.0.0")] ); - // let set_health_contract = - // HEALTH_CONTRACT.load(deps.as_ref().storage).unwrap().address().to_string(); - // assert_eq!(health_contract, set_health_contract); - - // let set_params = PARAMS.load(deps.as_ref().storage).unwrap().address().to_string(); - // assert_eq!(params, set_params); - - // let set_incentives = INCENTIVES.load(deps.as_ref().storage).unwrap().addr.to_string(); - // assert_eq!(incentives, set_incentives); - - // let set_swapper = SWAPPER.load(deps.as_ref().storage).unwrap().address().to_string(); - // assert_eq!(swapper, set_swapper); - - // let set_rewards = REWARDS_COLLECTOR.may_load(deps.as_ref().storage).unwrap(); - // assert_eq!(None, set_rewards); - let o = OWNER.query(deps.as_ref().storage).unwrap(); assert_eq!(old_owner.to_string(), o.owner.unwrap()); assert!(o.proposed.is_none()); assert!(o.initialized); assert!(!o.abolished); assert!(o.emergency_owner.is_none()); + + let new_config = CONFIG.load(deps.as_ref().storage).unwrap(); + assert_eq!( + new_config, + Config { + address_provider: old_config.address_provider, + max_whitelisted_denoms + } + ); + + let whitelist_count = WHITELIST_COUNT.load(deps.as_ref().storage).unwrap(); + assert_eq!(whitelist_count, 1); + let whitelist = WHITELIST + .range(deps.as_ref().storage, None, None, Order::Ascending) + .collect::>>() + .unwrap(); + assert_eq!(whitelist.len(), 1); + assert_eq!(whitelist.get("umars").unwrap(), &Uint128::one()); + + // Update asset incentive indices and check if indices changed + let mut new_atom_incentive = atom_incentive.clone(); + mars_incentives_old::helpers::update_asset_incentive_index( + &mut new_atom_incentive, + atom_collateral_total_scaled, + migration_time, + ) + .unwrap(); + assert_ne!(atom_incentive.index, new_atom_incentive.index); + let mut new_usdc_incentive = usdc_incentive.clone(); + mars_incentives_old::helpers::update_asset_incentive_index( + &mut new_usdc_incentive, + usdc_collateral_total_scaled, + migration_time, + ) + .unwrap(); + assert_ne!(usdc_incentive.index, new_usdc_incentive.index); + let mut new_osmo_incentive = osmo_incentive.clone(); + mars_incentives_old::helpers::update_asset_incentive_index( + &mut new_osmo_incentive, + osmo_collateral_total_scaled, + migration_time, + ) + .unwrap(); + assert_eq!(osmo_incentive.index, new_osmo_incentive.index); // should be equal because last_updated is after incentive end time + + // Check if incentive states are updated correctly + let incentive_states = INCENTIVE_STATES + .range(deps.as_ref().storage, None, None, Order::Ascending) + .collect::>>() + .unwrap(); + assert_eq!(incentive_states.len(), 3); + assert_eq!( + incentive_states.get(&(atom_denom.to_string(), mars_denom.to_string())).unwrap(), + &IncentiveState { + index: new_atom_incentive.index, + last_updated: migration_time + } + ); + assert_eq!( + incentive_states.get(&(usdc_denom.to_string(), mars_denom.to_string())).unwrap(), + &IncentiveState { + index: new_usdc_incentive.index, + last_updated: migration_time + } + ); + assert_eq!( + incentive_states.get(&(osmo_denom.to_string(), mars_denom.to_string())).unwrap(), + &IncentiveState { + index: new_osmo_incentive.index, + last_updated: migration_time + } + ); + + // Check if user asset indices are updated correctly + let user_1_atom_idx = USER_ASSET_INDICES + .load(deps.as_ref().storage, ((&user_1, ""), atom_denom, mars_denom)) + .unwrap(); + assert_eq!(user_1_atom_idx, new_atom_incentive.index); + let user_1_usdc_idx = USER_ASSET_INDICES + .load(deps.as_ref().storage, ((&user_1, ""), usdc_denom, mars_denom)) + .unwrap(); + assert_eq!(user_1_usdc_idx, new_usdc_incentive.index); + let user_1_osmo_idx = USER_ASSET_INDICES + .load(deps.as_ref().storage, ((&user_1, ""), osmo_denom, mars_denom)) + .unwrap(); + assert_eq!(user_1_osmo_idx, new_osmo_incentive.index); + + let user_2_osmo_idx = USER_ASSET_INDICES + .load(deps.as_ref().storage, ((&user_2, ""), osmo_denom, mars_denom)) + .unwrap(); + assert_eq!(user_2_osmo_idx, new_osmo_incentive.index); + + let user_3_atom_idx = USER_ASSET_INDICES + .load(deps.as_ref().storage, ((&user_3, ""), atom_denom, mars_denom)) + .unwrap(); + assert_eq!(user_3_atom_idx, new_atom_incentive.index); + + // Check if user unclaimed rewards are migrated correctly + let user_1_atom_rewards = mars_incentives_old::helpers::compute_user_accrued_rewards( + user_1_atom_amount_scaled, + user_1_atom_idx_old, + new_atom_incentive.index, + ) + .unwrap(); + let user_1_atom_rewards_migrated = USER_UNCLAIMED_REWARDS + .load(deps.as_ref().storage, ((&user_1, ""), atom_denom, mars_denom)) + .unwrap(); + assert_eq!(user_1_atom_rewards_migrated, user_1_unclaimed_rewards + user_1_atom_rewards); + let user_1_usdc_rewards = mars_incentives_old::helpers::compute_user_accrued_rewards( + user_1_usdc_amount_scaled, + user_1_usdc_idx_old, + new_usdc_incentive.index, + ) + .unwrap(); + let user_1_usdc_rewards_migrated = USER_UNCLAIMED_REWARDS + .load(deps.as_ref().storage, ((&user_1, ""), usdc_denom, mars_denom)) + .unwrap(); + assert_eq!(user_1_usdc_rewards_migrated, user_1_usdc_rewards); + let user_1_osmo_rewards = mars_incentives_old::helpers::compute_user_accrued_rewards( + user_1_osmo_amount_scaled, + user_1_osmo_idx_old, + new_osmo_incentive.index, + ) + .unwrap(); + let user_1_osmo_rewards_migrated = USER_UNCLAIMED_REWARDS + .load(deps.as_ref().storage, ((&user_1, ""), osmo_denom, mars_denom)) + .unwrap(); + assert_eq!(user_1_osmo_rewards_migrated, user_1_osmo_rewards); + + let user_2_osmo_rewards = mars_incentives_old::helpers::compute_user_accrued_rewards( + user_2_osmo_amount_scaled, + user_2_osmo_idx_old, + new_osmo_incentive.index, + ) + .unwrap(); + assert_eq!(user_2_osmo_rewards, Uint128::zero()); + + let user_3_atom_rewards = mars_incentives_old::helpers::compute_user_accrued_rewards( + user_3_atom_amount_scaled, + user_3_atom_idx_old, + new_atom_incentive.index, + ) + .unwrap(); + let user_3_atom_rewards_migrated = USER_UNCLAIMED_REWARDS + .load(deps.as_ref().storage, ((&user_3, ""), atom_denom, mars_denom)) + .unwrap(); + assert_eq!(user_3_atom_rewards_migrated, user_3_atom_rewards); +} + +fn create_market(denom: &str, scaled_amt: Uint128) -> Market { + Market { + denom: denom.to_string(), + collateral_total_scaled: scaled_amt, + ..Default::default() + } +} + +fn create_user_collateral(denom: &str, scaled_amt: Uint128) -> UserCollateralResponse { + UserCollateralResponse { + denom: denom.to_string(), + amount_scaled: scaled_amt, + amount: Uint128::zero(), // doesn't matter for this test + enabled: true, + } } From 182ff1fe38bf81efd7608770051ca96395d6415b Mon Sep 17 00:00:00 2001 From: piobab Date: Wed, 6 Sep 2023 10:51:19 +0200 Subject: [PATCH 3/3] Copy helpers for incentives from v1.0.0 tag. Update schema. --- Cargo.lock | 23 +---- contracts/incentives/Cargo.toml | 4 +- contracts/incentives/src/migrations/v2_0_0.rs | 93 +++++++++++++++++-- .../tests/tests/test_migration_v2.rs | 16 ++-- .../mars-address-provider.json | 2 +- schemas/mars-incentives/mars-incentives.json | 2 +- .../mars-oracle-osmosis.json | 2 +- .../mars-oracle-wasm/mars-oracle-wasm.json | 2 +- schemas/mars-params/mars-params.json | 2 +- schemas/mars-red-bank/mars-red-bank.json | 2 +- .../mars-rewards-collector-base.json | 2 +- .../mars-swapper-astroport.json | 2 +- .../mars-swapper-osmosis.json | 2 +- 13 files changed, 108 insertions(+), 46 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ac9a3e0e8..2309a7203 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1750,20 +1750,6 @@ dependencies = [ "thiserror", ] -[[package]] -name = "mars-incentives" -version = "1.0.0" -source = "git+https://github.com/mars-protocol/red-bank?rev=29ee60b#29ee60bf560033b7de99a463479d1cda3390343a" -dependencies = [ - "cosmwasm-std", - "cw-storage-plus 1.1.0", - "cw2 1.1.0", - "mars-owner 1.2.0", - "mars-red-bank-types 1.0.0", - "mars-utils 1.0.0", - "thiserror", -] - [[package]] name = "mars-incentives" version = "2.0.0" @@ -1772,7 +1758,6 @@ dependencies = [ "cosmwasm-std", "cw-storage-plus 1.1.0", "cw2 1.1.0", - "mars-incentives 1.0.0", "mars-owner 2.0.0", "mars-red-bank", "mars-red-bank-types 1.0.0", @@ -1792,7 +1777,7 @@ dependencies = [ "cosmwasm-std", "cw-it", "cw-multi-test", - "mars-incentives 2.0.0", + "mars-incentives", "mars-oracle-base", "mars-oracle-osmosis", "mars-osmosis", @@ -1974,7 +1959,7 @@ dependencies = [ [[package]] name = "mars-red-bank-types" version = "1.0.0" -source = "git+https://github.com/mars-protocol/red-bank?rev=29ee60b#29ee60bf560033b7de99a463479d1cda3390343a" +source = "git+https://github.com/mars-protocol/red-bank?tag=v1.0.0#13fcc446fe687dfb8f08ffacd57b1ab6ad6cfcc9" dependencies = [ "cosmwasm-schema", "cosmwasm-std", @@ -2104,7 +2089,7 @@ dependencies = [ "cw-it", "cw-multi-test", "mars-address-provider", - "mars-incentives 2.0.0", + "mars-incentives", "mars-mock-pyth", "mars-oracle-osmosis", "mars-oracle-wasm", @@ -2123,7 +2108,7 @@ dependencies = [ [[package]] name = "mars-utils" version = "1.0.0" -source = "git+https://github.com/mars-protocol/red-bank?rev=29ee60b#29ee60bf560033b7de99a463479d1cda3390343a" +source = "git+https://github.com/mars-protocol/red-bank?tag=v1.0.0#13fcc446fe687dfb8f08ffacd57b1ab6ad6cfcc9" dependencies = [ "cosmwasm-std", "thiserror", diff --git a/contracts/incentives/Cargo.toml b/contracts/incentives/Cargo.toml index 7cde6a9a7..37ec5391e 100644 --- a/contracts/incentives/Cargo.toml +++ b/contracts/incentives/Cargo.toml @@ -29,9 +29,7 @@ mars-utils = { workspace = true } thiserror = { workspace = true } # Old red-bank types used for migration. -# It comes from v1.0.0 tag with one commit enabling library feature -mars-incentives-old = { package = "mars-incentives", git = "https://github.com/mars-protocol/red-bank", rev = "29ee60b", features = ["library"] } -mars-red-bank-types-old = { package = "mars-red-bank-types", git = "https://github.com/mars-protocol/red-bank", rev = "29ee60b" } +mars-red-bank-types-old = { package = "mars-red-bank-types", git = "https://github.com/mars-protocol/red-bank", tag = "v1.0.0" } [dev-dependencies] mars-testing = { workspace = true } diff --git a/contracts/incentives/src/migrations/v2_0_0.rs b/contracts/incentives/src/migrations/v2_0_0.rs index 606ef075f..4e3da098a 100644 --- a/contracts/incentives/src/migrations/v2_0_0.rs +++ b/contracts/incentives/src/migrations/v2_0_0.rs @@ -44,6 +44,86 @@ pub mod v1_state { OwnerState::B(b) => b.owner, } } + + // Copy of helpers from v1.0.0 tag: + // https://github.com/mars-protocol/red-bank/blob/v1.0.0/contracts/incentives/src/helpers.rs + // Included as dependency coudn't generate proper schema for mars-incentive, even with specified + // version. + pub mod helpers { + use std::cmp::{max, min}; + + use cosmwasm_std::{ + Decimal, OverflowError, OverflowOperation, StdError, StdResult, Uint128, + }; + use mars_red_bank_types_old::incentives::AssetIncentive; + + /// Updates asset incentive index and last updated timestamp by computing + /// how many rewards were accrued since last time updated given incentive's + /// emission per second. + /// Total supply is the total (liquidity) token supply during the period being computed. + /// Note that this method does not commit updates to state as that should be executed by the + /// caller + pub fn update_asset_incentive_index( + asset_incentive: &mut AssetIncentive, + total_amount_scaled: Uint128, + current_block_time: u64, + ) -> StdResult<()> { + let end_time_sec = asset_incentive.start_time + asset_incentive.duration; + if (current_block_time != asset_incentive.last_updated) + && current_block_time > asset_incentive.start_time + && asset_incentive.last_updated < end_time_sec + && !total_amount_scaled.is_zero() + && !asset_incentive.emission_per_second.is_zero() + { + let time_start = max(asset_incentive.start_time, asset_incentive.last_updated); + let time_end = min(current_block_time, end_time_sec); + asset_incentive.index = compute_asset_incentive_index( + asset_incentive.index, + asset_incentive.emission_per_second, + total_amount_scaled, + time_start, + time_end, + )?; + } + asset_incentive.last_updated = current_block_time; + Ok(()) + } + + pub fn compute_asset_incentive_index( + previous_index: Decimal, + emission_per_second: Uint128, + total_amount_scaled: Uint128, + time_start: u64, + time_end: u64, + ) -> StdResult { + if time_start > time_end { + return Err(StdError::overflow(OverflowError::new( + OverflowOperation::Sub, + time_start, + time_end, + ))); + } + let seconds_elapsed = time_end - time_start; + let emission_for_elapsed_seconds = + emission_per_second.checked_mul(Uint128::from(seconds_elapsed))?; + let new_index = previous_index + + Decimal::from_ratio(emission_for_elapsed_seconds, total_amount_scaled); + Ok(new_index) + } + + /// Computes user accrued rewards using the difference between asset_incentive index and + /// user current index + /// asset_incentives index should be up to date. + pub fn compute_user_accrued_rewards( + user_amount_scaled: Uint128, + user_asset_index: Decimal, + asset_incentive_index: Decimal, + ) -> StdResult { + let result = (user_amount_scaled * asset_incentive_index) + .checked_sub(user_amount_scaled * user_asset_index)?; + Ok(result) + } + } } pub fn migrate(mut deps: DepsMut, env: Env, updates: V2Updates) -> Result { @@ -128,7 +208,7 @@ fn migrate_indices_and_unclaimed_rewards( }, )?; - mars_incentives_old::helpers::update_asset_incentive_index( + v1_state::helpers::update_asset_incentive_index( asset_incentive, market.collateral_total_scaled, current_block_time, @@ -175,12 +255,11 @@ fn migrate_indices_and_unclaimed_rewards( if user_asset_index != asset_incentive.index { // Compute user accrued rewards - let asset_accrued_rewards = - mars_incentives_old::helpers::compute_user_accrued_rewards( - collateral.amount_scaled, - user_asset_index, - asset_incentive.index, - )?; + let asset_accrued_rewards = v1_state::helpers::compute_user_accrued_rewards( + collateral.amount_scaled, + user_asset_index, + asset_incentive.index, + )?; unclaimed_rewards += asset_accrued_rewards; } diff --git a/contracts/incentives/tests/tests/test_migration_v2.rs b/contracts/incentives/tests/tests/test_migration_v2.rs index f26d17246..463ca2529 100644 --- a/contracts/incentives/tests/tests/test_migration_v2.rs +++ b/contracts/incentives/tests/tests/test_migration_v2.rs @@ -255,7 +255,7 @@ fn successful_migration() { // Update asset incentive indices and check if indices changed let mut new_atom_incentive = atom_incentive.clone(); - mars_incentives_old::helpers::update_asset_incentive_index( + v1_state::helpers::update_asset_incentive_index( &mut new_atom_incentive, atom_collateral_total_scaled, migration_time, @@ -263,7 +263,7 @@ fn successful_migration() { .unwrap(); assert_ne!(atom_incentive.index, new_atom_incentive.index); let mut new_usdc_incentive = usdc_incentive.clone(); - mars_incentives_old::helpers::update_asset_incentive_index( + v1_state::helpers::update_asset_incentive_index( &mut new_usdc_incentive, usdc_collateral_total_scaled, migration_time, @@ -271,7 +271,7 @@ fn successful_migration() { .unwrap(); assert_ne!(usdc_incentive.index, new_usdc_incentive.index); let mut new_osmo_incentive = osmo_incentive.clone(); - mars_incentives_old::helpers::update_asset_incentive_index( + v1_state::helpers::update_asset_incentive_index( &mut new_osmo_incentive, osmo_collateral_total_scaled, migration_time, @@ -332,7 +332,7 @@ fn successful_migration() { assert_eq!(user_3_atom_idx, new_atom_incentive.index); // Check if user unclaimed rewards are migrated correctly - let user_1_atom_rewards = mars_incentives_old::helpers::compute_user_accrued_rewards( + let user_1_atom_rewards = v1_state::helpers::compute_user_accrued_rewards( user_1_atom_amount_scaled, user_1_atom_idx_old, new_atom_incentive.index, @@ -342,7 +342,7 @@ fn successful_migration() { .load(deps.as_ref().storage, ((&user_1, ""), atom_denom, mars_denom)) .unwrap(); assert_eq!(user_1_atom_rewards_migrated, user_1_unclaimed_rewards + user_1_atom_rewards); - let user_1_usdc_rewards = mars_incentives_old::helpers::compute_user_accrued_rewards( + let user_1_usdc_rewards = v1_state::helpers::compute_user_accrued_rewards( user_1_usdc_amount_scaled, user_1_usdc_idx_old, new_usdc_incentive.index, @@ -352,7 +352,7 @@ fn successful_migration() { .load(deps.as_ref().storage, ((&user_1, ""), usdc_denom, mars_denom)) .unwrap(); assert_eq!(user_1_usdc_rewards_migrated, user_1_usdc_rewards); - let user_1_osmo_rewards = mars_incentives_old::helpers::compute_user_accrued_rewards( + let user_1_osmo_rewards = v1_state::helpers::compute_user_accrued_rewards( user_1_osmo_amount_scaled, user_1_osmo_idx_old, new_osmo_incentive.index, @@ -363,7 +363,7 @@ fn successful_migration() { .unwrap(); assert_eq!(user_1_osmo_rewards_migrated, user_1_osmo_rewards); - let user_2_osmo_rewards = mars_incentives_old::helpers::compute_user_accrued_rewards( + let user_2_osmo_rewards = v1_state::helpers::compute_user_accrued_rewards( user_2_osmo_amount_scaled, user_2_osmo_idx_old, new_osmo_incentive.index, @@ -371,7 +371,7 @@ fn successful_migration() { .unwrap(); assert_eq!(user_2_osmo_rewards, Uint128::zero()); - let user_3_atom_rewards = mars_incentives_old::helpers::compute_user_accrued_rewards( + let user_3_atom_rewards = v1_state::helpers::compute_user_accrued_rewards( user_3_atom_amount_scaled, user_3_atom_idx_old, new_atom_incentive.index, diff --git a/schemas/mars-address-provider/mars-address-provider.json b/schemas/mars-address-provider/mars-address-provider.json index 34ce77c5b..a8ce16fdd 100644 --- a/schemas/mars-address-provider/mars-address-provider.json +++ b/schemas/mars-address-provider/mars-address-provider.json @@ -1,6 +1,6 @@ { "contract_name": "mars-address-provider", - "contract_version": "1.2.0", + "contract_version": "2.0.0", "idl_version": "1.0.0", "instantiate": { "$schema": "http://json-schema.org/draft-07/schema#", diff --git a/schemas/mars-incentives/mars-incentives.json b/schemas/mars-incentives/mars-incentives.json index ae8071490..d2d6badd1 100644 --- a/schemas/mars-incentives/mars-incentives.json +++ b/schemas/mars-incentives/mars-incentives.json @@ -1,6 +1,6 @@ { "contract_name": "mars-incentives", - "contract_version": "1.2.0", + "contract_version": "2.0.0", "idl_version": "1.0.0", "instantiate": { "$schema": "http://json-schema.org/draft-07/schema#", diff --git a/schemas/mars-oracle-osmosis/mars-oracle-osmosis.json b/schemas/mars-oracle-osmosis/mars-oracle-osmosis.json index 1a1ef011e..a6324eacb 100644 --- a/schemas/mars-oracle-osmosis/mars-oracle-osmosis.json +++ b/schemas/mars-oracle-osmosis/mars-oracle-osmosis.json @@ -1,6 +1,6 @@ { "contract_name": "mars-oracle-osmosis", - "contract_version": "1.2.0", + "contract_version": "2.0.0", "idl_version": "1.0.0", "instantiate": { "$schema": "http://json-schema.org/draft-07/schema#", diff --git a/schemas/mars-oracle-wasm/mars-oracle-wasm.json b/schemas/mars-oracle-wasm/mars-oracle-wasm.json index 19d235a88..c5e3474dd 100644 --- a/schemas/mars-oracle-wasm/mars-oracle-wasm.json +++ b/schemas/mars-oracle-wasm/mars-oracle-wasm.json @@ -1,6 +1,6 @@ { "contract_name": "mars-oracle-wasm", - "contract_version": "1.2.0", + "contract_version": "2.0.0", "idl_version": "1.0.0", "instantiate": { "$schema": "http://json-schema.org/draft-07/schema#", diff --git a/schemas/mars-params/mars-params.json b/schemas/mars-params/mars-params.json index dbf0f6338..2350dddbc 100644 --- a/schemas/mars-params/mars-params.json +++ b/schemas/mars-params/mars-params.json @@ -1,6 +1,6 @@ { "contract_name": "mars-params", - "contract_version": "1.2.0", + "contract_version": "2.0.0", "idl_version": "1.0.0", "instantiate": { "$schema": "http://json-schema.org/draft-07/schema#", diff --git a/schemas/mars-red-bank/mars-red-bank.json b/schemas/mars-red-bank/mars-red-bank.json index 6bafea4e5..d38a7dd5f 100644 --- a/schemas/mars-red-bank/mars-red-bank.json +++ b/schemas/mars-red-bank/mars-red-bank.json @@ -1,6 +1,6 @@ { "contract_name": "mars-red-bank", - "contract_version": "1.2.0", + "contract_version": "2.0.0", "idl_version": "1.0.0", "instantiate": { "$schema": "http://json-schema.org/draft-07/schema#", diff --git a/schemas/mars-rewards-collector-base/mars-rewards-collector-base.json b/schemas/mars-rewards-collector-base/mars-rewards-collector-base.json index 30e68fddc..c9f4e9ce1 100644 --- a/schemas/mars-rewards-collector-base/mars-rewards-collector-base.json +++ b/schemas/mars-rewards-collector-base/mars-rewards-collector-base.json @@ -1,6 +1,6 @@ { "contract_name": "mars-rewards-collector-base", - "contract_version": "1.2.0", + "contract_version": "2.0.0", "idl_version": "1.0.0", "instantiate": { "$schema": "http://json-schema.org/draft-07/schema#", diff --git a/schemas/mars-swapper-astroport/mars-swapper-astroport.json b/schemas/mars-swapper-astroport/mars-swapper-astroport.json index 93ef8f7c2..778ee5515 100644 --- a/schemas/mars-swapper-astroport/mars-swapper-astroport.json +++ b/schemas/mars-swapper-astroport/mars-swapper-astroport.json @@ -1,6 +1,6 @@ { "contract_name": "mars-swapper-astroport", - "contract_version": "1.2.0", + "contract_version": "2.0.0", "idl_version": "1.0.0", "instantiate": { "$schema": "http://json-schema.org/draft-07/schema#", diff --git a/schemas/mars-swapper-osmosis/mars-swapper-osmosis.json b/schemas/mars-swapper-osmosis/mars-swapper-osmosis.json index 512d773a2..6d4b6a009 100644 --- a/schemas/mars-swapper-osmosis/mars-swapper-osmosis.json +++ b/schemas/mars-swapper-osmosis/mars-swapper-osmosis.json @@ -1,6 +1,6 @@ { "contract_name": "mars-swapper-osmosis", - "contract_version": "1.2.0", + "contract_version": "2.0.0", "idl_version": "1.0.0", "instantiate": { "$schema": "http://json-schema.org/draft-07/schema#",