Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Rebalancing Incentive Integration #36

Draft
wants to merge 36 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
0d633b6
initial setup
iboss-ptk Oct 29, 2024
552f1e4
fix decimal error
iboss-ptk Oct 28, 2024
9d8bd3a
refactor limiter pass logic
iboss-ptk Oct 28, 2024
db44add
test config
iboss-ptk Oct 30, 2024
2efae61
incentive pool
iboss-ptk Oct 30, 2024
7f2bc45
rename coin in test
iboss-ptk Oct 30, 2024
c0af094
set and query rebalance incentive config and pool
iboss-ptk Oct 31, 2024
d196e49
initialize incentive pool
iboss-ptk Oct 31, 2024
511a5bf
migration
iboss-ptk Oct 31, 2024
2267a17
clean up ideal balance if asset is removed
iboss-ptk Nov 1, 2024
0259d15
implement calc functions
iboss-ptk Sep 10, 2024
a81ac36
add test for calculate rebalancing fee
iboss-ptk Sep 11, 2024
f59c592
add prop test
iboss-ptk Sep 11, 2024
ef9fa55
test calc incentive
iboss-ptk Sep 11, 2024
91a739d
add test for test_calculate_impact_factor_component
iboss-ptk Sep 11, 2024
2f5af2f
change pow to checked_pow
iboss-ptk Sep 11, 2024
810acc9
add proptest for calculate_cumulative_impact_factor_component
iboss-ptk Sep 11, 2024
b2be5ae
rename for clarity
iboss-ptk Sep 16, 2024
56e87e8
restrict new on impact factor param group
iboss-ptk Sep 16, 2024
0e59e6b
test has no change in balance
iboss-ptk Sep 16, 2024
560d426
test calculate impact factor component
iboss-ptk Sep 16, 2024
dc6a112
resolve precision loss issue
iboss-ptk Sep 16, 2024
73e4f3e
test calculate_impact_factor
iboss-ptk Sep 19, 2024
068fd4a
use transmuter math error for lambda error
iboss-ptk Nov 1, 2024
1bc8eb1
add rebalancing pass
iboss-ptk Nov 7, 2024
30cc304
update formula to match the implementation
iboss-ptk Nov 7, 2024
68e8111
add tests for rebalancing incentive pass
iboss-ptk Nov 7, 2024
d325a65
update and test fee collection
iboss-ptk Nov 8, 2024
9759557
update incentive function
iboss-ptk Nov 8, 2024
ad47066
track prev lambda
iboss-ptk Nov 11, 2024
d400616
make incentive pool track historical lambda collected balance
iboss-ptk Nov 11, 2024
f9f002c
update incentive pool to support prev lambda tracking
iboss-ptk Nov 13, 2024
91c20a6
impl incentive fn
iboss-ptk Nov 14, 2024
b15f6d4
update incentive pool
iboss-ptk Nov 14, 2024
9c4c570
test multiple lambda
iboss-ptk Nov 14, 2024
8101382
integreate incentive mechanism to swaps
iboss-ptk Nov 21, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
284 changes: 268 additions & 16 deletions Cargo.lock

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions contracts/transmuter/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ cosmwasm-std = {workspace = true, features = ["cosmwasm_1_1"]}
cosmwasm-storage = "1.3.1"
cw-storage-plus = "2.0.0"
cw2 = "2.0.0"
itertools = "0.13.0"
osmosis-std = "0.26.0"
schemars = "0.8.12"
serde = {version = "1.0.183", default-features = false, features = ["derive"]}
Expand Down
2 changes: 1 addition & 1 deletion contracts/transmuter/src/asset.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ impl AssetConfig {

#[cw_serde]
pub struct Asset {
amount: Uint128,
amount: Uint128, // TODO: change to Uint256
denom: String,
normalization_factor: Uint128,
is_corrupted: bool,
Expand Down
39 changes: 39 additions & 0 deletions contracts/transmuter/src/coin256.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
use std::fmt::{self, Display};

use cosmwasm_schema::cw_serde;
use cosmwasm_std::{Coin, Uint256};

#[cw_serde]
pub struct Coin256 {
pub amount: Uint256,
pub denom: String,
}

impl Display for Coin256 {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}{}", self.amount, self.denom)
}
}

impl Coin256 {
pub fn new(amount: impl Into<Uint256>, denom: impl Into<String>) -> Self {
Self {
amount: amount.into(),
denom: denom.into(),
}
}

pub fn zero(denom: impl Into<String>) -> Self {
Self::new(0u128, denom)
}
}

pub fn coin256(amount: u128, denom: impl Into<String>) -> Coin256 {
Coin256::new(amount, denom)
}

impl From<Coin> for Coin256 {
fn from(c: Coin) -> Self {
Coin256::new(c.amount, c.denom)
}
}
252 changes: 251 additions & 1 deletion contracts/transmuter/src/contract.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
use crate::{corruptable::Corruptable, scope::Scope};
use crate::{
corruptable::Corruptable,
rebalancing_incentive::{
config::{IdealBalance, RebalancingIncentiveConfig},
incentive_pool::IncentivePool,
},
scope::Scope,
};
use std::{collections::BTreeMap, iter};

use crate::{
Expand Down Expand Up @@ -44,6 +51,8 @@ pub struct Transmuter {
pub(crate) alloyed_asset: AlloyedAsset,
pub(crate) role: Role,
pub(crate) limiters: Limiters,
pub(crate) rebalancing_incentive_config: Item<RebalancingIncentiveConfig>,
pub(crate) incentive_pool: Item<IncentivePool>,
}

pub mod key {
Expand All @@ -54,6 +63,8 @@ pub mod key {
pub const ADMIN: &str = "admin";
pub const MODERATOR: &str = "moderator";
pub const LIMITERS: &str = "limiters";
pub const REBALANCING_INCENTIVE_CONFIG: &str = "rebalancing_incentive_config";
pub const INCENTIVE_POOL: &str = "incentive_pool";
}

#[contract]
Expand All @@ -70,6 +81,8 @@ impl Transmuter {
),
role: Role::new(key::ADMIN, key::MODERATOR),
limiters: Limiters::new(key::LIMITERS),
rebalancing_incentive_config: Item::new(key::REBALANCING_INCENTIVE_CONFIG),
incentive_pool: Item::new(key::INCENTIVE_POOL),
}
}

Expand Down Expand Up @@ -134,6 +147,14 @@ impl Transmuter {
self.alloyed_asset
.set_normalization_factor(deps.storage, alloyed_asset_normalization_factor)?;

// initialize rebalancing incentive config
self.rebalancing_incentive_config
.save(deps.storage, &RebalancingIncentiveConfig::default())?;

// initialize incentive pool
self.incentive_pool
.save(deps.storage, &IncentivePool::default())?;

Ok(Response::new()
.add_attribute("method", "instantiate")
.add_attribute("contract_name", CONTRACT_NAME)
Expand Down Expand Up @@ -233,6 +254,7 @@ impl Transmuter {
.map(|(denom, weight)| (Scope::denom(&denom).key(), weight));
let asset_group_weights_iter = pool
.asset_group_weights()?
.unwrap_or_default()
.into_iter()
.map(|(label, weight)| (Scope::asset_group(&label).key(), weight));

Expand Down Expand Up @@ -631,6 +653,45 @@ impl Transmuter {
.map(|res| res.add_attribute("method", "exit_pool"))
}

/// Set the rebalancing incentive config.
/// If left any of the fields empty, the corresponding config will remain unchanged.
/// setting ideal balance to [0,1] deletes value from storage as it's the same as default value
#[sv::msg(exec)]
pub fn set_rebalancing_incentive_config(
&self,
ExecCtx { deps, env: _, info }: ExecCtx,
lambda: Option<Decimal>,
ideal_balances: Option<Vec<(Scope, IdealBalance)>>,
) -> Result<Response, ContractError> {
nonpayable(&info.funds)?;

// only admin can set rebalancing incentive config
ensure_admin_authority!(info.sender, self.role.admin, deps.as_ref());

let mut config = self.rebalancing_incentive_config.load(deps.storage)?;

if let Some(new_lambda) = lambda {
config.set_lambda(new_lambda)?;
}

let pool = self.pool.load(deps.storage)?;
let available_denoms = pool.pool_assets.iter().map(|a| a.denom().to_string());
let available_asset_groups = pool.asset_groups.keys().cloned();

if let Some(new_ideal_balances) = ideal_balances {
config.set_ideal_balances(
available_denoms,
available_asset_groups,
new_ideal_balances,
)?;
}

self.rebalancing_incentive_config
.save(deps.storage, &config)?;

Ok(Response::new().add_attribute("method", "set_rebalancing_incentive_config"))
}

// === queries ===

#[sv::msg(query)]
Expand Down Expand Up @@ -842,6 +903,26 @@ impl Transmuter {
})
}

#[sv::msg(query)]
pub fn get_rebalancing_incentive_config(
&self,
QueryCtx { deps, env: _ }: QueryCtx,
) -> Result<GetRebalancingIncentiveConfigResponse, ContractError> {
Ok(GetRebalancingIncentiveConfigResponse {
config: self.rebalancing_incentive_config.load(deps.storage)?,
})
}

#[sv::msg(query)]
pub fn get_incentive_pool(
&self,
QueryCtx { deps, env: _ }: QueryCtx,
) -> Result<GetIncentivePoolResponse, ContractError> {
Ok(GetIncentivePoolResponse {
pool: self.incentive_pool.load(deps.storage)?,
})
}

// --- admin ---

#[sv::msg(exec)]
Expand Down Expand Up @@ -1021,8 +1102,19 @@ pub struct GetModeratorResponse {
pub moderator: Addr,
}

#[cw_serde]
pub struct GetRebalancingIncentiveConfigResponse {
pub config: RebalancingIncentiveConfig,
}

#[cw_serde]
pub struct GetIncentivePoolResponse {
pub pool: IncentivePool,
}

#[cfg(test)]
mod tests {
use std::collections::HashMap;

use super::sv::*;
use super::*;
Expand Down Expand Up @@ -4812,4 +4904,162 @@ mod tests {
gas_used: 0,
}
}

#[test]
fn test_set_rebalancing_incentive_config() {
let mut deps = mock_dependencies();

let env = mock_env();
let admin = deps.api.addr_make("admin");
let non_admin = deps.api.addr_make("non_admin");
let moderator = deps.api.addr_make("moderator");
let info = message_info(&admin, &[]);

deps.querier.bank.update_balance(
admin.clone(),
vec![coin(1000000, "uusdc"), coin(1000000, "uusdt")],
);

// Instantiate the contract.
let init_msg = InstantiateMsg {
pool_asset_configs: vec![
AssetConfig {
denom: "uusdc".to_string(),
normalization_factor: Uint128::one(),
},
AssetConfig {
denom: "uusdt".to_string(),
normalization_factor: Uint128::one(),
},
],
alloyed_asset_subdenom: "alloyed".to_string(),
alloyed_asset_normalization_factor: Uint128::one(),
admin: Some(admin.to_string()),
moderator: moderator.to_string(),
};
instantiate(deps.as_mut(), env.clone(), info.clone(), init_msg).unwrap();

// Query initial rebalancing incentive config
let query_msg = ContractQueryMsg::Transmuter(QueryMsg::GetRebalancingIncentiveConfig {});
let query_res: GetRebalancingIncentiveConfigResponse =
from_json(&query(deps.as_ref(), env.clone(), query_msg).unwrap()).unwrap();

// Check that the initial rebalancing incentive config is the default value
assert_eq!(query_res.config.lambda, Decimal::zero());
assert!(query_res.config.ideal_balances.is_empty());

// Try to set rebalancing incentive config with non_admin
let non_admin_info = message_info(&non_admin, &[]);
let set_config_msg = ContractExecMsg::Transmuter(ExecMsg::SetRebalancingIncentiveConfig {
lambda: Some(Decimal::percent(5)),
ideal_balances: None,
});

let err = execute(
deps.as_mut(),
env.clone(),
non_admin_info,
set_config_msg.clone(),
)
.unwrap_err();
assert_eq!(err, ContractError::Unauthorized {});

// Set initial rebalancing incentive config
execute(deps.as_mut(), env.clone(), info.clone(), set_config_msg).unwrap();

// Query rebalancing incentive config
let query_msg = ContractQueryMsg::Transmuter(QueryMsg::GetRebalancingIncentiveConfig {});
let query_res: GetRebalancingIncentiveConfigResponse =
from_json(&query(deps.as_ref(), env.clone(), query_msg).unwrap()).unwrap();

assert_eq!(query_res.config.lambda, Decimal::percent(5));
assert!(query_res.config.ideal_balances.is_empty());

// not updating lambda, but updating ideal_balances
let set_config_msg = ContractExecMsg::Transmuter(ExecMsg::SetRebalancingIncentiveConfig {
lambda: None,
ideal_balances: Some(vec![
(
Scope::Denom("uusdc".to_string()),
IdealBalance::new(Decimal::percent(40), Decimal::percent(60)),
),
(
Scope::Denom("uusdt".to_string()),
IdealBalance::new(Decimal::percent(40), Decimal::percent(60)),
),
]),
});
execute(deps.as_mut(), env.clone(), info.clone(), set_config_msg).unwrap();

// Query rebalancing incentive config
let query_msg = ContractQueryMsg::Transmuter(QueryMsg::GetRebalancingIncentiveConfig {});
let query_res: GetRebalancingIncentiveConfigResponse =
from_json(&query(deps.as_ref(), env.clone(), query_msg).unwrap()).unwrap();

assert_eq!(query_res.config.lambda, Decimal::percent(5));
assert_eq!(
query_res.config.ideal_balances,
HashMap::from([
(
Scope::denom("uusdc"),
IdealBalance::new(Decimal::percent(40), Decimal::percent(60))
),
(
Scope::denom("uusdt"),
IdealBalance::new(Decimal::percent(40), Decimal::percent(60))
)
]),
);

// setting wrong denom
let set_config_msg = ContractExecMsg::Transmuter(ExecMsg::SetRebalancingIncentiveConfig {
lambda: None,
ideal_balances: Some(vec![(
Scope::denom("wrong_denom"),
IdealBalance::new(Decimal::percent(40), Decimal::percent(60)),
)]),
});
let err = execute(deps.as_mut(), env.clone(), info, set_config_msg).unwrap_err();
assert_eq!(
err,
ContractError::ScopeNotFound {
scope: Scope::denom("wrong_denom")
}
);
}

#[test]
fn test_initialize_incentive_pool() {
let mut deps = mock_dependencies();

let admin = deps.api.addr_make("admin");
let moderator = deps.api.addr_make("moderator");

deps.querier
.bank
.update_balance(admin.clone(), vec![coin(1000, "tbtc"), coin(1000, "nbtc")]);

let init_msg = InstantiateMsg {
pool_asset_configs: vec![
AssetConfig::from_denom_str("tbtc"),
AssetConfig::from_denom_str("nbtc"),
],
alloyed_asset_subdenom: "btc".to_string(),
alloyed_asset_normalization_factor: Uint128::one(),
admin: Some(admin.to_string()),
moderator: moderator.to_string(),
};
let env = mock_env();
let info = message_info(&admin, &[]);

// Instantiate the contract.
instantiate(deps.as_mut(), env.clone(), info.clone(), init_msg).unwrap();

// Query the incentive pool
let query_msg = ContractQueryMsg::Transmuter(QueryMsg::GetIncentivePool {});
let query_res: GetIncentivePoolResponse =
from_json(&query(deps.as_ref(), env, query_msg).unwrap()).unwrap();

assert!(query_res.pool.balances.is_empty());
}
}
Loading