From e2f2b66b5a5df90d23be6816a08cb083c8a3234e Mon Sep 17 00:00:00 2001 From: itshaseebsaeed Date: Fri, 3 Nov 2023 10:10:26 +0500 Subject: [PATCH 1/5] rewards_distribution added --- .../liquidity_book/lb_factory/src/contract.rs | 2 + .../liquidity_book/lb_factory/src/state.rs | 1 + .../liquidity_book/lb_pair/src/contract.rs | 267 +++++++- contracts/liquidity_book/lb_pair/src/state.rs | 30 +- .../lb_pair/src/unittest/test_helper.rs | 1 + .../tests/src/multitests/lb_factory.rs | 1 + .../tests/src/multitests/lb_pair_fees.rs | 596 +++++++++++------- .../tests/src/multitests/test_helper.rs | 31 +- .../multi_test/src/interfaces/lb_factory.rs | 2 + packages/multi_test/src/interfaces/lb_pair.rs | 26 +- .../liquidity_book/lb_factory.rs | 1 + .../liquidity_book/lb_pair.rs | 25 + .../src/utils/liquidity_book/bin_helper.rs | 3 +- .../src/utils/liquidity_book/mod.rs | 15 + 14 files changed, 770 insertions(+), 231 deletions(-) diff --git a/contracts/liquidity_book/lb_factory/src/contract.rs b/contracts/liquidity_book/lb_factory/src/contract.rs index d11a1a6c..3587c780 100644 --- a/contracts/liquidity_book/lb_factory/src/contract.rs +++ b/contracts/liquidity_book/lb_factory/src/contract.rs @@ -81,6 +81,7 @@ pub fn instantiate( lb_pair_implementation: ContractInstantiationInfo::default(), lb_token_implementation: ContractInstantiationInfo::default(), admin_auth: msg.admin_auth.into_valid(deps.api)?, + total_reward_bins: msg.total_reward_bins, }; CONFIG.save(deps.storage, &state)?; @@ -389,6 +390,7 @@ fn try_create_lb_pair( entropy, protocol_fee_recipient: state.fee_recipient, admin_auth: state.admin_auth.into(), + total_reward_bins: state.total_reward_bins, })?, code_hash: state.lb_pair_implementation.code_hash.clone(), funds: vec![], diff --git a/contracts/liquidity_book/lb_factory/src/state.rs b/contracts/liquidity_book/lb_factory/src/state.rs index 347ef7e2..fa3a8115 100644 --- a/contracts/liquidity_book/lb_factory/src/state.rs +++ b/contracts/liquidity_book/lb_factory/src/state.rs @@ -60,6 +60,7 @@ pub struct State { pub lb_pair_implementation: ContractInstantiationInfo, pub lb_token_implementation: ContractInstantiationInfo, pub admin_auth: Contract, + pub total_reward_bins: u32, } pub fn ephemeral_storage_w(storage: &mut dyn Storage) -> Singleton { diff --git a/contracts/liquidity_book/lb_pair/src/contract.rs b/contracts/liquidity_book/lb_pair/src/contract.rs index 3ffdb7a2..47825a5c 100644 --- a/contracts/liquidity_book/lb_pair/src/contract.rs +++ b/contracts/liquidity_book/lb_pair/src/contract.rs @@ -24,6 +24,7 @@ use cosmwasm_std::{ Timestamp, Uint128, Uint256, + Uint512, WasmMsg, }; @@ -39,13 +40,14 @@ use shade_protocol::{ }, lb_libraries::{ bin_helper::{self, BinHelper}, + ceil_div, constants::{self, MAX_FEE, PRECISION, SCALE_OFFSET}, fee_helper::{self, FeeHelper}, lb_token::state_structs::{LbPair, TokenAmount, TokenIdBalance}, math::{ encoded_sample::EncodedSample, liquidity_configurations::LiquidityConfigurations, - packed_u128_math::{Decode, Encode, PackedMath}, + packed_u128_math::{Decode, Encode, PackedMath, BASIS_POINT_MAX}, sample_math::OracleSample, tree_math::TreeUint24, u24::U24, @@ -65,7 +67,10 @@ use shade_protocol::{ BLOCK_SIZE, }; use shadeswap_shared::router::ExecuteMsgResponse; -use std::{collections::HashMap, ops::Sub}; +use std::{ + collections::HashMap, + ops::{Mul, Sub}, +}; use tokens::TokenType; use types::{Bytes32, LBPairInformation, MintArrays}; @@ -163,6 +168,9 @@ pub fn instantiate( } } + if msg.total_reward_bins > U24::MAX { + return Err(Error::InvalidInput {}); + } let state = State { creator: info.sender.clone(), factory: msg.factory, @@ -179,12 +187,17 @@ pub fn instantiate( viewing_key, protocol_fees_recipient: msg.protocol_fee_recipient, admin_auth: msg.admin_auth.into_valid(deps.api)?, + last_rewards_epoch: env.block.time, + rewards_epoch_id: 0, + total_rewards_bin: msg.total_reward_bins, + //TODO: set using the setter function and instantiate msg + rewards_distribution_algorithm: RewardsDistributionAlgorithm::VolumeBasedRewards, }; // deps.api // .debug(format!("Contract was initialized by {}", info.sender).as_str()); - let tree = TreeUint24::new(); + let tree: TreeUint24 = TreeUint24::new(); let oracle = Oracle { samples: HashMap::::new(), }; @@ -193,6 +206,7 @@ pub fn instantiate( ORACLE.save(deps.storage, &oracle)?; CONTRACT_STATUS.save(deps.storage, &ContractStatus::Active); BIN_TREE.save(deps.storage, &tree)?; + FEE_MAP_TREE.save(deps.storage, 0, &tree)?; ephemeral_storage_w(deps.storage).save(&NextTokenKey { code_hash: msg.lb_token_implementation.code_hash, @@ -295,6 +309,10 @@ pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> R max_volatility_accumulator, ), ExecuteMsg::ForceDecay {} => try_force_decay(deps, env, info), + ExecuteMsg::CalculateRewards {} => try_calculate_rewards(deps, env, info), + ExecuteMsg::ResetRewardsEpoch { distribution } => { + try_reset_rewards_epoch(deps, env, info, distribution) + } } } @@ -384,6 +402,11 @@ fn try_swap( let mut params = state.pair_parameters; let bin_step = state.bin_step; + let reward_stats = REWARDS_STATS_STORE + .load(deps.storage, state.rewards_epoch_id) + .unwrap_or_default(); + let mut cum_fee_bin = reward_stats.cumm_fee_x_bin; + let mut cum_fee = reward_stats.cumm_fee; let mut active_id = params.get_active_id(); @@ -395,6 +418,8 @@ fn try_swap( .map_err(|_| Error::ZeroBinReserve { active_id })?; if !BinHelper::is_empty(bin_reserves, !swap_for_y) { + let price = PriceHelper::get_price_from_id(active_id, bin_step)?; + params = params.update_volatility_accumulator(active_id)?; let (mut amounts_in_with_fees, amounts_out_of_bin, fees) = BinHelper::get_amounts( @@ -404,9 +429,92 @@ fn try_swap( swap_for_y, active_id, amounts_left, + price, )?; if U256::from_le_bytes(amounts_in_with_fees) > U256::ZERO { + let fee_obj = FeeLog { + is_token_x: swap_for_y, + fee: Uint128::from(fees.decode_alt(swap_for_y)), + bin_id: active_id, + timestamp: env.block.time, + last_rewards_epoch_id: state.rewards_epoch_id, + }; + //TODO: check if appending is needed + FEE_APPEND_STORE.push(deps.storage, &fee_obj)?; + + if fee_obj.is_token_x { + let divisor: Uint512 = Uint512::from(2u32).pow(SCALE_OFFSET.into()); + + let fee_temp: Uint512 = + Uint512::from((fee_obj.fee.u128().rotate_left(SCALE_OFFSET.into()))); + let prod = (fee_temp * Uint512::from(price.u256_to_uint256())) + .checked_div(divisor) + .unwrap(); + let prod_fee_price = U256::from_str_prefixed(&(prod).to_string()) + .unwrap() + .u256_to_uint256(); + + cum_fee += prod_fee_price; + cum_fee_bin += prod_fee_price * (Uint256::from(fee_obj.bin_id)); + + if state.rewards_distribution_algorithm + == RewardsDistributionAlgorithm::VolumeBasedRewards + { + let mut fee_map_tree = FEE_MAP_TREE.update( + deps.storage, + state.rewards_epoch_id, + |mut fee_tree| -> Result<_> { + Ok(match fee_tree { + Some(mut t) => { + t.add(active_id); + t + } + None => panic!("Fee tree not initialized"), + }) + }, + ); + + FEE_MAP.update(deps.storage, active_id, |mut cumm_fee| -> Result<_> { + let updated_cumm_fee = match cumm_fee { + Some(f) => f + prod_fee_price, + None => prod_fee_price, + }; + Ok(updated_cumm_fee) + })?; + } + } else { + let fee = Uint256::from(fee_obj.fee.u128()); + cum_fee += fee; + cum_fee_bin += fee * (Uint256::from(fee_obj.bin_id)); + + if state.rewards_distribution_algorithm + == RewardsDistributionAlgorithm::VolumeBasedRewards + { + let mut fee_map_tree = FEE_MAP_TREE.update( + deps.storage, + state.rewards_epoch_id, + |mut fee_tree| -> Result<_> { + Ok(match fee_tree { + Some(mut t) => { + t.add(active_id); + t + } + None => panic!("Fee tree not initialized"), + }) + }, + ); + + FEE_MAP.update(deps.storage, active_id, |mut cumm_fee| -> Result<_> { + let updated_cumm_fee = match cumm_fee { + Some(f) => f + fee, + None => fee, + }; + Ok(updated_cumm_fee) + })?; + } + } + amounts_left = amounts_left.sub(amounts_in_with_fees); amounts_out = amounts_out.add(amounts_out_of_bin); @@ -421,6 +529,8 @@ fn try_swap( amounts_in_with_fees = amounts_in_with_fees.sub(p_fees); } + // println!("SWAP MADE {:?}", active_id); + BIN_MAP.save( deps.storage, active_id, @@ -443,6 +553,11 @@ fn try_swap( } } + REWARDS_STATS_STORE.save(deps.storage, state.rewards_epoch_id, &RewardStats { + cumm_fee: cum_fee, + cumm_fee_x_bin: cum_fee_bin, + })?; + if amounts_out == [0u8; 32] { return Err(Error::InsufficientAmountOut); } @@ -926,7 +1041,6 @@ fn _update_bin( let bin_reserves = BIN_MAP.load(deps.storage, id).unwrap_or([0u8; 32]); let config = CONFIG.load(deps.storage)?; let price = PriceHelper::get_price_from_id(id, bin_step)?; - let total_supply = _query_total_supply( deps.as_ref(), id, @@ -1416,6 +1530,131 @@ fn try_force_decay(deps: DepsMut, env: Env, info: MessageInfo) -> Result Result { + let mut state = CONFIG.load(deps.storage)?; + validate_admin( + &deps.querier, + AdminPermissions::LiquidityBookAdmin, + info.sender.to_string(), + &state.admin_auth, + )?; + + // loop through the fee_logs uptil a maximum iterations + // save the results in temporary storage + + let reward_stats = REWARDS_STATS_STORE.load(deps.storage, state.rewards_epoch_id)?; + + let distribution = match state.rewards_distribution_algorithm { + RewardsDistributionAlgorithm::BaseRewards => { + calculate_base_rewards_distribution(&state, reward_stats)? + } + RewardsDistributionAlgorithm::VolumeBasedRewards => { + calculate_volume_based_rewards_distribution(deps.as_ref(), &state, reward_stats)? + } + }; + + REWARDS_DISTRIBUTION.save(deps.storage, state.rewards_epoch_id, &distribution)?; + + state.rewards_epoch_id += state.rewards_epoch_id + 1; + CONFIG.save(deps.storage, &state)?; + + let tree: TreeUint24 = TreeUint24::new(); + FEE_MAP_TREE.save(deps.storage, state.rewards_epoch_id, &tree)?; + + Ok(Response::default()) +} + +fn calculate_base_rewards_distribution( + state: &State, + reward_stats: RewardStats, +) -> Result { + let mut cum_fee_bin = reward_stats.cumm_fee_x_bin; + let mut cum_fee = reward_stats.cumm_fee; + let avg_bin = ceil_div(cum_fee_bin, cum_fee).uint256_to_u256().as_u32(); + + //TODO: make it variable and also make a setter function + let half_total = state.total_rewards_bin / 2; + let min_bin = avg_bin.saturating_sub(half_total) + 1; + let max_bin = avg_bin.saturating_add(half_total); + + let difference = max_bin - min_bin + 1; + + //TODO: unit test edge cases for different bin_ids and lengths + let ids: Vec = (min_bin..=max_bin).collect(); + let weightages = vec![BASIS_POINT_MAX as u16 / difference as u16; difference as usize]; + + let distribution = RewardsDistribution { + ids, + weightages, + denominator: BASIS_POINT_MAX as u16, + }; + + Ok(distribution) +} + +fn calculate_volume_based_rewards_distribution( + deps: Deps, + state: &State, + reward_stats: RewardStats, +) -> Result { + let mut cum_fee = reward_stats.cumm_fee; + //TODO: unit test edge cases for different bin_ids and lengths + let mut ids: Vec = Vec::new(); + let mut weightages: Vec = Vec::new(); + + let fee_tree: TreeUint24 = FEE_MAP_TREE.load(deps.storage, state.rewards_epoch_id)?; + let mut id: u32 = 0; + let basis_point_max: Uint256 = Uint256::from(BASIS_POINT_MAX); + + loop { + id = fee_tree.find_first_left(id); + if id == U24::MAX || id == 0 { + break; + } + + let fee: Uint256 = FEE_MAP.load(deps.storage, id)?; + ids.push(id); + let weightage: u16 = fee + .multiply_ratio(basis_point_max, cum_fee) + .uint256_to_u256() + .as_u16(); + weightages.push(weightage); + } + + let distribution = RewardsDistribution { + ids, + weightages, + denominator: BASIS_POINT_MAX as u16, + }; + + Ok(distribution) +} + +//Can only change the distribution algorithm at the start of next epoch +fn try_reset_rewards_epoch( + deps: DepsMut, + env: Env, + info: MessageInfo, + rewards_distribution_algorithm: Option, +) -> Result { + let mut state = CONFIG.load(deps.storage)?; + validate_admin( + &deps.querier, + AdminPermissions::LiquidityBookAdmin, + info.sender.to_string(), + &state.admin_auth, + )?; + + match rewards_distribution_algorithm { + Some(distribution) => state.rewards_distribution_algorithm = distribution, + None => {} + } + + CONFIG.save(deps.storage, &state)?; + + Ok(Response::default()) +} + fn only_factory(sender: &Addr, factory: &Addr) -> Result<()> { if sender != factory { return Err(Error::OnlyFactory); @@ -1534,6 +1773,9 @@ pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> Result { QueryMsg::SwapSimulation { offer, exclude_fee } => { to_binary(&query_swap_simulation(deps, env, offer, exclude_fee)?).map_err(Error::CwErr) } + QueryMsg::GetRewardsDistribution { epoch_id } => { + to_binary(&query_rewards_distribution(deps, epoch_id)?).map_err(Error::CwErr) + } } } @@ -2148,6 +2390,8 @@ fn query_swap_out( loop { let bin_reserves = BIN_MAP.load(deps.storage, id).unwrap_or_default(); if !BinHelper::is_empty(bin_reserves, !swap_for_y) { + let price = PriceHelper::get_price_from_id(id, bin_step)?; + params = params.update_volatility_accumulator(id)?; let (amounts_in_with_fees, amounts_out_of_bin, fees) = BinHelper::get_amounts( @@ -2157,6 +2401,7 @@ fn query_swap_out( swap_for_y, id, amounts_in_left, + price, )?; if U256::from_le_bytes(amounts_in_with_fees) > U256::ZERO { @@ -2210,6 +2455,20 @@ fn query_total_supply(deps: Deps, id: u32) -> Result { Ok(TotalSupplyResponse { total_supply }) } +fn query_rewards_distribution( + deps: Deps, + epoch_id: Option, +) -> Result { + let (epoch_id) = match epoch_id { + Some(id) => id, + None => CONFIG.load(deps.storage)?.rewards_epoch_id - 1, + }; + + Ok(RewardsDistributionResponse { + distribution: REWARDS_DISTRIBUTION.load(deps.storage, epoch_id)?, + }) +} + #[shd_entry_point] pub fn reply(deps: DepsMut, env: Env, msg: Reply) -> StdResult { match (msg.id, msg.result) { diff --git a/contracts/liquidity_book/lb_pair/src/state.rs b/contracts/liquidity_book/lb_pair/src/state.rs index 87eb485c..36311cee 100644 --- a/contracts/liquidity_book/lb_pair/src/state.rs +++ b/contracts/liquidity_book/lb_pair/src/state.rs @@ -1,9 +1,10 @@ use cosmwasm_schema::cw_serde; -use cosmwasm_std::{Addr, ContractInfo, Storage}; +use cosmwasm_std::{Addr, ContractInfo, Storage, Timestamp, Uint128, Uint256}; use cosmwasm_storage::{singleton, singleton_read, ReadonlySingleton, Singleton}; use pair_parameter_helper::PairParameters; //? use shade_protocol::{ - secret_storage_plus::{Bincode2, Item, Map}, + liquidity_book::lb_pair::{RewardsDistribution, RewardsDistributionAlgorithm}, + secret_storage_plus::{AppendStore, Bincode2, Item, Map}, Contract, }; @@ -27,6 +28,27 @@ pub const BIN_MAP: Map = Map::new("bins"); //? pub const BIN_TREE: Item = Item::new("bin_tree"); //? pub const ORACLE: Item = Item::new("oracle"); //? pub static EPHEMERAL_STORAGE_KEY: &[u8] = b"ephemeral_storage"; +pub const FEE_APPEND_STORE: AppendStore = AppendStore::new("fee_logs"); //? +pub const REWARDS_STATS_STORE: Map = Map::new("rewards_stats"); //? +pub const REWARDS_DISTRIBUTION: Map = Map::new("rewards_distribution"); //? +pub const FEE_MAP_TREE: Map = Map::new("fee_tree"); //? +pub const FEE_MAP: Map = Map::new("fee_map"); //? + +#[cw_serde] +#[derive(Default)] +pub struct RewardStats { + pub cumm_fee: Uint256, + pub cumm_fee_x_bin: Uint256, +} + +#[cw_serde] +pub struct FeeLog { + pub is_token_x: bool, + pub fee: Uint128, + pub bin_id: u32, + pub timestamp: Timestamp, + pub last_rewards_epoch_id: u64, +} #[cw_serde] pub enum ContractStatus { @@ -49,6 +71,10 @@ pub struct State { pub lb_token: ContractInfo, pub protocol_fees_recipient: Addr, pub admin_auth: Contract, + pub last_rewards_epoch: Timestamp, + pub rewards_epoch_id: u64, + pub total_rewards_bin: u32, + pub rewards_distribution_algorithm: RewardsDistributionAlgorithm, } pub fn ephemeral_storage_w(storage: &mut dyn Storage) -> Singleton { diff --git a/contracts/liquidity_book/lb_pair/src/unittest/test_helper.rs b/contracts/liquidity_book/lb_pair/src/unittest/test_helper.rs index 2daa2c6c..f4fcfd7d 100644 --- a/contracts/liquidity_book/lb_pair/src/unittest/test_helper.rs +++ b/contracts/liquidity_book/lb_pair/src/unittest/test_helper.rs @@ -179,6 +179,7 @@ pub fn init_lb_pair() -> Result<(App, Contract, DeployedContracts), anyhow::Erro String::new(), addrs.admin(), admin_contract.into(), + 100, )?; Ok((app, lb_pair, deployed_contracts)) diff --git a/contracts/liquidity_book/tests/src/multitests/lb_factory.rs b/contracts/liquidity_book/tests/src/multitests/lb_factory.rs index 8a40d7b5..6af79155 100644 --- a/contracts/liquidity_book/tests/src/multitests/lb_factory.rs +++ b/contracts/liquidity_book/tests/src/multitests/lb_factory.rs @@ -346,6 +346,7 @@ fn test_revert_create_lb_pair() -> Result<(), anyhow::Error> { addrs.admin(), 0, admin_contract.into(), + 100, )?; //can't create a pair if the preset is not set diff --git a/contracts/liquidity_book/tests/src/multitests/lb_pair_fees.rs b/contracts/liquidity_book/tests/src/multitests/lb_pair_fees.rs index 0486332b..03b57115 100644 --- a/contracts/liquidity_book/tests/src/multitests/lb_pair_fees.rs +++ b/contracts/liquidity_book/tests/src/multitests/lb_pair_fees.rs @@ -1,4 +1,6 @@ -use crate::multitests::test_helper::*; +use std::ops::Sub; + +use crate::multitests::{lb_pair_liquidity::PRECISION, test_helper::*}; use super::test_helper::{ increase_allowance_helper, @@ -2223,223 +2225,375 @@ pub fn test_revert_total_fee_exceeded() -> Result<(), anyhow::Error> { Ok(()) } -// #[test] -// pub fn test_fuzz_user_fee_swap_in_x() -> Result<(), anyhow::Error> { -// let addrs = init_addrs(); -// let (mut app, _lb_factory, deployed_contracts, lb_pair, lb_token) = lb_pair_setup()?; -// let amount_out = Uint128::from(generate_random(1u128, DEPOSIT_AMOUNT - 1)); - -// let (amount_in, amount_out_left, _fee) = -// lb_pair::query_swap_in(&app, &lb_pair.lb_pair.contract, amount_out, true)?; -// assert_eq!(amount_out_left, Uint128::zero()); - -// let tokens_to_mint = vec![(SHADE, amount_in)]; - -// mint_token_helper( -// &mut app, -// &deployed_contracts, -// &addrs, -// addrs.joker().into_string(), -// tokens_to_mint.clone(), -// )?; - -// let token_x = &extract_contract_info(&deployed_contracts, SHADE)?; - -// lb_pair::swap_snip_20( -// &mut app, -// addrs.joker().as_str(), -// &lb_pair.lb_pair.contract, -// Some(addrs.joker().to_string()), -// token_x, -// amount_in, -// )?; - -// let shd_balance = snip20::balance_query( -// &mut app, -// addrs.joker().as_str(), -// &deployed_contracts, -// SHADE, -// "viewing_key".to_owned(), -// )?; -// assert_eq!(shd_balance, Uint128::zero()); - -// let silk_balance = snip20::balance_query( -// &mut app, -// addrs.joker().as_str(), -// &deployed_contracts, -// SILK, -// "viewing_key".to_owned(), -// )?; -// assert_eq!(silk_balance, amount_out); - -// //REMOVE LIQUIDITY - -// let token_x = extract_contract_info(&deployed_contracts, SHADE)?; -// let token_y = extract_contract_info(&deployed_contracts, SILK)?; - -// let total_bins = get_total_bins(10, 10) as u32; -// let mut balances = vec![Uint256::zero(); total_bins as usize]; -// let mut ids = vec![0u32; total_bins as usize]; - -// for i in 0..total_bins { -// let id = get_id(ACTIVE_ID, i, 10); -// ids[i as usize] = id; -// balances[i as usize] = lb_token::query_balance( -// &app, -// &lb_token, -// addrs.batman(), -// addrs.batman(), -// String::from("viewing_key"), -// id.to_string(), -// )?; -// } - -// let (reserves_x, reserves_y) = lb_pair::query_reserves(&app, &lb_pair.lb_pair.contract)?; -// lb_pair::remove_liquidity( -// &mut app, -// addrs.batman().as_str(), -// &lb_pair.lb_pair.contract, -// RemoveLiquidity { -// token_x: token_type_snip20_generator(&token_x)?, -// token_y: token_type_snip20_generator(&token_y)?, -// bin_step: lb_pair.bin_step, -// amount_x_min: Uint128::from(reserves_x), -// amount_y_min: Uint128::from(reserves_y), -// ids: ids.clone(), -// amounts: balances.clone(), -// deadline: 99999999999, -// }, -// )?; - -// let (protocol_fee_x, _) = lb_pair::query_protocol_fees(&app, &lb_pair.lb_pair.contract)?; - -// let balance_x = snip20::balance_query( -// &mut app, -// addrs.batman().as_str(), -// &deployed_contracts, -// SHADE, -// "viewing_key".to_owned(), -// )?; - -// let balance_y = snip20::balance_query( -// &mut app, -// addrs.batman().as_str(), -// &deployed_contracts, -// SILK, -// "viewing_key".to_owned(), -// )?; - -// assert_eq!( -// balance_x.u128(), -// DEPOSIT_AMOUNT + amount_in.u128() - protocol_fee_x -// ); - -// assert_eq!(balance_y.u128(), reserves_y); - -// let amount_x = Uint128::from(DEPOSIT_AMOUNT); -// let amount_y = Uint128::from(DEPOSIT_AMOUNT); -// let nb_bins_x = 10; -// let nb_bins_y = 10; - -// let token_x = extract_contract_info(&deployed_contracts, SHADE)?; -// let token_y = extract_contract_info(&deployed_contracts, SILK)?; - -// let tokens_to_mint = vec![(SHADE, amount_x), (SILK, amount_y)]; - -// mint_token_helper( -// &mut app, -// &deployed_contracts, -// &addrs, -// addrs.scare_crow().into_string(), -// tokens_to_mint.clone(), -// )?; - -// increase_allowance_helper( -// &mut app, -// &deployed_contracts, -// addrs.scare_crow().into_string(), -// lb_pair.lb_pair.contract.address.to_string(), -// tokens_to_mint, -// )?; - -// //Adding liquidity -// let liquidity_parameters = liquidity_parameters_generator( -// &deployed_contracts, -// ACTIVE_ID, -// token_x.clone(), -// token_y.clone(), -// amount_x, -// amount_y, -// nb_bins_x, -// nb_bins_y, -// )?; - -// lb_pair::add_liquidity( -// &mut app, -// addrs.scare_crow().as_str(), -// &lb_pair.lb_pair.contract, -// liquidity_parameters, -// )?; - -// let total_bins = get_total_bins(10, 10) as u32; -// let mut balances = vec![Uint256::zero(); total_bins as usize]; -// let mut ids = vec![0u32; total_bins as usize]; - -// lb_token::set_viewing_key( -// &mut app, -// addrs.scare_crow().as_str(), -// &lb_token, -// "viewing_key".to_owned(), -// )?; -// for i in 0..total_bins { -// let id = get_id(ACTIVE_ID, i, 10); -// ids[i as usize] = id; -// balances[i as usize] = lb_token::query_balance( -// &app, -// &lb_token, -// addrs.scare_crow(), -// addrs.scare_crow(), -// String::from("viewing_key"), -// id.to_string(), -// )?; -// } - -// let (reserves_x, reserves_y) = lb_pair::query_reserves(&app, &lb_pair.lb_pair.contract)?; -// lb_pair::remove_liquidity( -// &mut app, -// addrs.scare_crow().as_str(), -// &lb_pair.lb_pair.contract, -// RemoveLiquidity { -// token_x: token_type_snip20_generator(&token_x)?, -// token_y: token_type_snip20_generator(&token_y)?, -// bin_step: lb_pair.bin_step, -// amount_x_min: Uint128::from(reserves_x), -// amount_y_min: Uint128::from(reserves_y), -// ids, -// amounts: balances, -// deadline: 99999999999, -// }, -// )?; - -// // let balance_x = snip20::balance_query( -// // &mut app, -// // addrs.scare_crow().as_str(), -// // &deployed_contracts, -// // SHADE, -// // "viewing_key".to_owned(), -// // )?; - -// // let balance_y = snip20::balance_query( -// // &mut app, -// // addrs.scare_crow().as_str(), -// // &deployed_contracts, -// // SILK, -// // "viewing_key".to_owned(), -// // )?; - -// // assert_eq!(balance_x.u128(), DEPOSIT_AMOUNT); - -// // assert_eq!(balance_y.u128(), DEPOSIT_AMOUNT); - -// Ok(()) -// } +#[test] +pub fn test_fuzz_swap_in_x_and_y_btc_silk() -> Result<(), anyhow::Error> { + let addrs = init_addrs(); + let (mut app, lb_factory, deployed_contracts) = setup(None)?; + + let btc = extract_contract_info(&deployed_contracts, SBTC)?; + let silk = extract_contract_info(&deployed_contracts, SILK)?; + let token_x = token_type_snip20_generator(&btc)?; + let token_y = token_type_snip20_generator(&silk)?; + + //assuming the ratio of btc to silk 1:40000 + //Hence 1 usilk = 400 satoishi + // (1+DEFAULT_BIN_STEP/BASIS_POINT)^x = 400 + // x = 5994 + + lb_factory::create_lb_pair( + &mut app, + addrs.admin().as_str(), + &lb_factory.clone().into(), + DEFAULT_BIN_STEP, + ACTIVE_ID + 5994, + token_x.clone(), + token_y.clone(), + "viewing_key".to_string(), + "entropy".to_string(), + )?; + let all_pairs = + lb_factory::query_all_lb_pairs(&mut app, &lb_factory.clone().into(), token_x, token_y)?; + let lb_pair = all_pairs[0].clone(); + + let lb_token = lb_pair::lb_token_query(&app, &lb_pair.lb_pair.contract)?; + + lb_token::set_viewing_key( + &mut app, + addrs.batman().as_str(), + &lb_token, + "viewing_key".to_owned(), + )?; + + let amount_x = Uint128::from(((10000u128) * 10000_0000) / 40000); // 25_000_000 satoshi + let amount_y = Uint128::from((10000u128) * 1000_000); // 10_000 silk + + let nb_bins_x = 10; + let nb_bins_y = 10; + + let token_x = extract_contract_info(&deployed_contracts, SBTC)?; + let token_y = extract_contract_info(&deployed_contracts, SILK)?; + + let tokens_to_mint = vec![(SBTC, amount_x), (SILK, amount_y)]; + + mint_token_helper( + &mut app, + &deployed_contracts, + &addrs, + addrs.batman().into_string(), + tokens_to_mint.clone(), + )?; + + snip20::set_viewing_key_exec( + &mut app, + addrs.scare_crow().as_str(), + &deployed_contracts, + SBTC, + "viewing_key".to_owned(), + )?; + + snip20::set_viewing_key_exec( + &mut app, + addrs.scare_crow().as_str(), + &deployed_contracts, + SILK, + "viewing_key".to_owned(), + )?; + + snip20::set_viewing_key_exec( + &mut app, + addrs.joker().as_str(), + &deployed_contracts, + SBTC, + "viewing_key".to_string(), + )?; + + snip20::set_viewing_key_exec( + &mut app, + addrs.joker().as_str(), + &deployed_contracts, + SILK, + "viewing_key".to_string(), + )?; + + increase_allowance_helper( + &mut app, + &deployed_contracts, + addrs.batman().into_string(), + lb_pair.lb_pair.contract.address.to_string(), + tokens_to_mint, + )?; + + //Adding liquidity + let liquidity_parameters = liquidity_parameters_generator( + &deployed_contracts, + ACTIVE_ID + 5994, + token_x, + token_y, + amount_x, + amount_y, + nb_bins_x, + nb_bins_y, + )?; + + lb_pair::add_liquidity( + &mut app, + addrs.batman().as_str(), + &lb_pair.lb_pair.contract, + liquidity_parameters, + )?; + + //generate random number + // let amount_y_out = Uint128::from(generate_random(1u128, DEPOSIT_AMOUNT - 1)); + let amount_y_out = Uint128::from(1 * 1000_000u128); //1000 silk + // get swap_in for y + let (amount_x_in, amount_y_out_left, _fee) = + lb_pair::query_swap_in(&app, &lb_pair.lb_pair.contract, amount_y_out, true)?; + assert_eq!(amount_y_out_left, Uint128::zero()); + + // mint the tokens + let tokens_to_mint = vec![(SBTC, amount_x_in)]; + mint_token_helper( + &mut app, + &deployed_contracts, + &addrs, + addrs.batman().into_string(), + tokens_to_mint.clone(), + )?; + // make a swap with amount_x_in + let token_x: &ContractInfo = &extract_contract_info(&deployed_contracts, SBTC)?; + lb_pair::swap_snip_20( + &mut app, + addrs.batman().as_str(), + &lb_pair.lb_pair.contract, + Some(addrs.batman().to_string()), + token_x, + amount_x_in, + )?; + + // check the balance of silk if it's equal to the amount_y_out + + let btc_balance = snip20::balance_query( + &mut app, + addrs.batman().as_str(), + &deployed_contracts, + SBTC, + "viewing_key".to_owned(), + )?; + assert_eq!(btc_balance, Uint128::zero()); + let silk_balance = snip20::balance_query( + &mut app, + addrs.batman().as_str(), + &deployed_contracts, + SILK, + "viewing_key".to_owned(), + )?; + assert_approx_eq_rel( + Uint256::from(silk_balance), + Uint256::from(amount_y_out), + Uint256::from(1u128).checked_mul(Uint256::from(PRECISION))?, + "Error greater than 1%", + ); + + //generate random number + // let amount_y_out = Uint128::from(generate_random(1u128, DEPOSIT_AMOUNT - 1)); + let amount_x_out = Uint128::from(2 * 1000_000u128); //5_000_000 satoshi + // get swap_in for y + let (amount_y_in, amount_x_out_left, _fee) = + lb_pair::query_swap_in(&app, &lb_pair.lb_pair.contract, amount_x_out, false)?; + assert_eq!(amount_x_out_left, Uint128::zero()); + + // mint the tokens + let tokens_to_mint = vec![(SILK, amount_y_in)]; + mint_token_helper( + &mut app, + &deployed_contracts, + &addrs, + addrs.batman().into_string(), + tokens_to_mint.clone(), + )?; + // make a swap with amount_x_in + let token_x: &ContractInfo = &extract_contract_info(&deployed_contracts, SILK)?; + lb_pair::swap_snip_20( + &mut app, + addrs.batman().as_str(), + &lb_pair.lb_pair.contract, + Some(addrs.batman().to_string()), + token_x, + amount_y_in, + )?; + + // check the balance of silk if it's equal to the amount_y_out + let silk_balance = snip20::balance_query( + &mut app, + addrs.batman().as_str(), + &deployed_contracts, + SILK, + "viewing_key".to_owned(), + )?; + assert_approx_eq_rel( + Uint256::from(silk_balance), + Uint256::from(amount_y_out), + Uint256::from(1u128).checked_mul(Uint256::from(PRECISION))?, + "Error greater than 1%", + ); + let btc_balance = snip20::balance_query( + &mut app, + addrs.batman().as_str(), + &deployed_contracts, + SBTC, + "viewing_key".to_owned(), + )?; + assert_eq!(btc_balance, amount_x_out); + + lb_pair::calculate_rewards(&mut app, addrs.admin().as_str(), &lb_pair.lb_pair.contract)?; + + Ok(()) +} + +#[test] +pub fn test_fuzz_calculate_rewards() -> Result<(), anyhow::Error> { + let addrs = init_addrs(); + let (mut app, lb_factory, deployed_contracts) = setup(None)?; + + let btc = extract_contract_info(&deployed_contracts, SBTC)?; + let silk = extract_contract_info(&deployed_contracts, SILK)?; + let token_x = token_type_snip20_generator(&btc)?; + let token_y = token_type_snip20_generator(&silk)?; + + //assuming the ratio of btc to silk 1:40000 + //Hence 1 usilk = 400 satoishi + // (1+DEFAULT_BIN_STEP/BASIS_POINT)^x = 400 + // x = 5994 + + lb_factory::create_lb_pair( + &mut app, + addrs.admin().as_str(), + &lb_factory.clone().into(), + DEFAULT_BIN_STEP, + ACTIVE_ID + 5994, + token_x.clone(), + token_y.clone(), + "viewing_key".to_string(), + "entropy".to_string(), + )?; + let all_pairs = + lb_factory::query_all_lb_pairs(&mut app, &lb_factory.clone().into(), token_x, token_y)?; + let lb_pair = all_pairs[0].clone(); + + let lb_token = lb_pair::lb_token_query(&app, &lb_pair.lb_pair.contract)?; + + lb_token::set_viewing_key( + &mut app, + addrs.batman().as_str(), + &lb_token, + "viewing_key".to_owned(), + )?; + + let amount_x = Uint128::from(((10000u128) * 10000_0000) / 40000); // 25_000_000 satoshi + let amount_y = Uint128::from((10000u128) * 1000_000); // 10_000 silk + + let nb_bins_x = 10; + let nb_bins_y = 10; + + let token_x = extract_contract_info(&deployed_contracts, SBTC)?; + let token_y = extract_contract_info(&deployed_contracts, SILK)?; + + let tokens_to_mint = vec![(SBTC, amount_x), (SILK, amount_y)]; + + mint_token_helper( + &mut app, + &deployed_contracts, + &addrs, + addrs.batman().into_string(), + tokens_to_mint.clone(), + )?; + + increase_allowance_helper( + &mut app, + &deployed_contracts, + addrs.batman().into_string(), + lb_pair.lb_pair.contract.address.to_string(), + tokens_to_mint, + )?; + + //Adding liquidity + let liquidity_parameters = liquidity_parameters_generator( + &deployed_contracts, + ACTIVE_ID + 5994, + token_x, + token_y, + amount_x, + amount_y, + nb_bins_x, + nb_bins_y, + )?; + + lb_pair::add_liquidity( + &mut app, + addrs.batman().as_str(), + &lb_pair.lb_pair.contract, + liquidity_parameters, + )?; + + //generate random number + // let amount_y_out = Uint128::from(generate_random(1u128, DEPOSIT_AMOUNT - 1)); + let amount_y_out = Uint128::from(10000 * 1000_000u128); //1000 silk + // get swap_in for y + let (amount_x_in, _amount_y_out_left, _fee) = + lb_pair::query_swap_in(&app, &lb_pair.lb_pair.contract, amount_y_out, true)?; + + // mint the tokens + let tokens_to_mint = vec![(SBTC, amount_x_in)]; + mint_token_helper( + &mut app, + &deployed_contracts, + &addrs, + addrs.batman().into_string(), + tokens_to_mint.clone(), + )?; + // make a swap with amount_x_in + let token_x: &ContractInfo = &extract_contract_info(&deployed_contracts, SBTC)?; + lb_pair::swap_snip_20( + &mut app, + addrs.batman().as_str(), + &lb_pair.lb_pair.contract, + Some(addrs.batman().to_string()), + token_x, + amount_x_in, + )?; + + //generate random number + // let amount_y_out = Uint128::from(generate_random(1u128, DEPOSIT_AMOUNT - 1)); + let amount_x_out = Uint128::from(5 * 1000_000u128); //5_000_000 satoshi + // get swap_in for y + let (amount_y_in, _amount_x_out_left, _fee) = + lb_pair::query_swap_in(&app, &lb_pair.lb_pair.contract, amount_x_out, false)?; + + // mint the tokens + let tokens_to_mint = vec![(SILK, amount_y_in)]; + mint_token_helper( + &mut app, + &deployed_contracts, + &addrs, + addrs.batman().into_string(), + tokens_to_mint.clone(), + )?; + // make a swap with amount_x_in + let token_x: &ContractInfo = &extract_contract_info(&deployed_contracts, SILK)?; + lb_pair::swap_snip_20( + &mut app, + addrs.batman().as_str(), + &lb_pair.lb_pair.contract, + Some(addrs.batman().to_string()), + token_x, + amount_y_in, + )?; + + lb_pair::calculate_rewards(&mut app, addrs.admin().as_str(), &lb_pair.lb_pair.contract)?; + + let distribution = lb_pair::query_rewards_distribution(&app, &lb_pair.lb_pair.contract, None)?; + + println!("distribution: {:?}", distribution); + + Ok(()) +} diff --git a/contracts/liquidity_book/tests/src/multitests/test_helper.rs b/contracts/liquidity_book/tests/src/multitests/test_helper.rs index 1ae95dbd..739890f2 100644 --- a/contracts/liquidity_book/tests/src/multitests/test_helper.rs +++ b/contracts/liquidity_book/tests/src/multitests/test_helper.rs @@ -239,6 +239,7 @@ pub fn setup(bin_step: Option) -> Result<(App, Contract, DeployedContracts) addrs.joker(), 0, admin_contract.into(), + 100, )?; let lb_token_stored_code = app.store_code(LbToken::default().contract()); let lb_pair_stored_code = app.store_code(LbPair::default().contract()); @@ -434,6 +435,34 @@ pub fn liquidity_parameters_generator( amount_y: Uint128, nb_bins_x: u8, nb_bins_y: u8, +) -> StdResult { + liquidity_parameters_generator_custom( + // Assuming lbPair has methods to get tokenX and tokenY + // lbPair: &LBPair, + _deployed_contracts, + active_id, + token_x, + token_y, + amount_x, + amount_y, + nb_bins_x, + nb_bins_y, + DEFAULT_BIN_STEP, + ) +} + +pub fn liquidity_parameters_generator_custom( + // Assuming lbPair has methods to get tokenX and tokenY + // lbPair: &LBPair, + _deployed_contracts: &DeployedContracts, + active_id: u32, + token_x: ContractInfo, + token_y: ContractInfo, + amount_x: Uint128, + amount_y: Uint128, + nb_bins_x: u8, + nb_bins_y: u8, + bin_step: u16, ) -> StdResult { let total = get_total_bins(nb_bins_x, nb_bins_y); @@ -478,7 +507,7 @@ pub fn liquidity_parameters_generator( contract_addr: token_y.address, token_code_hash: token_y.code_hash, }, - bin_step: DEFAULT_BIN_STEP, + bin_step, amount_x, amount_y, amount_x_min: amount_x.multiply_ratio(90u128, 100u128), diff --git a/packages/multi_test/src/interfaces/lb_factory.rs b/packages/multi_test/src/interfaces/lb_factory.rs index 82535181..0f0bb3c6 100644 --- a/packages/multi_test/src/interfaces/lb_factory.rs +++ b/packages/multi_test/src/interfaces/lb_factory.rs @@ -22,6 +22,7 @@ pub fn init( fee_recipient: Addr, flash_loan_fee: u8, admin_auth: RawContract, + total_reward_bins: u32, ) -> StdResult { let lb_factory = Contract::from( match (lb_factory::InstantiateMsg { @@ -29,6 +30,7 @@ pub fn init( fee_recipient, flash_loan_fee, admin_auth, + total_reward_bins, } .test_init( LbFactory::default(), diff --git a/packages/multi_test/src/interfaces/lb_pair.rs b/packages/multi_test/src/interfaces/lb_pair.rs index 88204b5a..fd3d9043 100644 --- a/packages/multi_test/src/interfaces/lb_pair.rs +++ b/packages/multi_test/src/interfaces/lb_pair.rs @@ -6,7 +6,7 @@ use shade_protocol::{ tokens::TokenType, types::{ContractInstantiationInfo, StaticFeeParameters}, }, - liquidity_book::lb_pair::{LiquidityParameters, RemoveLiquidity}, + liquidity_book::lb_pair::{LiquidityParameters, RemoveLiquidity, RewardsDistribution}, multi_test::App, utils::{ asset::{Contract, RawContract}, @@ -32,6 +32,7 @@ pub fn init( entropy: String, protocol_fee_recipient: Addr, admin_auth: RawContract, + total_reward_bins: u32, ) -> StdResult { let lb_pair = Contract::from( match (lb_pair::InstantiateMsg { @@ -46,6 +47,7 @@ pub fn init( entropy, protocol_fee_recipient, admin_auth, + total_reward_bins, } .test_init( LbPair::default(), @@ -151,6 +153,18 @@ pub fn collect_protocol_fees(app: &mut App, sender: &str, lb_pair: &ContractInfo } } +pub fn calculate_rewards(app: &mut App, sender: &str, lb_pair: &ContractInfo) -> StdResult<()> { + match (lb_pair::ExecuteMsg::CalculateRewards {}.test_exec( + lb_pair, + app, + Addr::unchecked(sender), + &[], + )) { + Ok(_) => Ok(()), + Err(e) => return Err(StdError::generic_err(e.root_cause().to_string())), + } +} + pub fn set_static_fee_parameters( app: &mut App, sender: &str, @@ -317,6 +331,16 @@ pub fn query_active_id(app: &App, lb_pair: &ContractInfo) -> StdResult { Ok(active_id) } +pub fn query_rewards_distribution( + app: &App, + lb_pair: &ContractInfo, + epoch_id: Option, +) -> StdResult { + let res = lb_pair::QueryMsg::GetRewardsDistribution { epoch_id }.test_query(lb_pair, app)?; + let lb_pair::RewardsDistributionResponse { distribution } = res; + Ok(distribution) +} + pub fn query_bin(app: &App, lb_pair: &ContractInfo, id: u32) -> StdResult<(u128, u128)> { let res = lb_pair::QueryMsg::GetBin { id }.test_query(lb_pair, app)?; let lb_pair::BinResponse { diff --git a/packages/shade_protocol/src/contract_interfaces/liquidity_book/lb_factory.rs b/packages/shade_protocol/src/contract_interfaces/liquidity_book/lb_factory.rs index 88d95743..15d742ef 100644 --- a/packages/shade_protocol/src/contract_interfaces/liquidity_book/lb_factory.rs +++ b/packages/shade_protocol/src/contract_interfaces/liquidity_book/lb_factory.rs @@ -18,6 +18,7 @@ pub struct InstantiateMsg { pub owner: Option, pub fee_recipient: Addr, pub flash_loan_fee: u8, + pub total_reward_bins: u32, } impl InstantiateCallback for InstantiateMsg { const BLOCK_SIZE: usize = 256; diff --git a/packages/shade_protocol/src/contract_interfaces/liquidity_book/lb_pair.rs b/packages/shade_protocol/src/contract_interfaces/liquidity_book/lb_pair.rs index 4003be03..a5fff4fc 100644 --- a/packages/shade_protocol/src/contract_interfaces/liquidity_book/lb_pair.rs +++ b/packages/shade_protocol/src/contract_interfaces/liquidity_book/lb_pair.rs @@ -41,6 +41,7 @@ pub struct InstantiateMsg { pub entropy: String, pub protocol_fee_recipient: Addr, pub admin_auth: RawContract, + pub total_reward_bins: u32, } impl InstantiateCallback for InstantiateMsg { @@ -86,6 +87,16 @@ pub enum ExecuteMsg { max_volatility_accumulator: u32, }, ForceDecay {}, + CalculateRewards {}, + ResetRewardsEpoch { + distribution: Option, + }, +} + +#[cw_serde] +pub enum RewardsDistributionAlgorithm { + BaseRewards, + VolumeBasedRewards, } impl ExecuteMsg { @@ -194,6 +205,8 @@ pub enum QueryMsg { }, #[returns(TotalSupplyResponse)] TotalSupply { id: u32 }, + #[returns(RewardsDistributionResponse)] + GetRewardsDistribution { epoch_id: Option }, } impl Query for QueryMsg { const BLOCK_SIZE: usize = 256; @@ -351,6 +364,18 @@ pub struct TotalSupplyResponse { pub total_supply: Uint256, } +#[cw_serde] +pub struct RewardsDistributionResponse { + pub distribution: RewardsDistribution, +} + +#[cw_serde] +pub struct RewardsDistribution { + pub ids: Vec, + pub weightages: Vec, + pub denominator: u16, +} + #[cw_serde] pub struct LiquidityParameters { pub token_x: TokenType, diff --git a/packages/shade_protocol/src/utils/liquidity_book/bin_helper.rs b/packages/shade_protocol/src/utils/liquidity_book/bin_helper.rs index 6d5214e8..4eb6c47c 100644 --- a/packages/shade_protocol/src/utils/liquidity_book/bin_helper.rs +++ b/packages/shade_protocol/src/utils/liquidity_book/bin_helper.rs @@ -322,9 +322,8 @@ impl BinHelper { swap_for_y: bool, active_id: u32, amounts_in_left: Bytes32, + price: U256, ) -> Result<(Bytes32, Bytes32, Bytes32), BinError> { - let price = PriceHelper::get_price_from_id(active_id, bin_step)?; - let bin_reserve_out = bin_reserves.decode_alt(!swap_for_y); let max_amount_in = if swap_for_y { diff --git a/packages/shade_protocol/src/utils/liquidity_book/mod.rs b/packages/shade_protocol/src/utils/liquidity_book/mod.rs index fee4516a..0945be0c 100644 --- a/packages/shade_protocol/src/utils/liquidity_book/mod.rs +++ b/packages/shade_protocol/src/utils/liquidity_book/mod.rs @@ -1,5 +1,7 @@ //! Helper Libraries +use cosmwasm_std::Uint256; + pub mod bin_helper; pub mod constants; pub mod fee_helper; @@ -12,3 +14,16 @@ pub mod tokens; pub mod transfer; pub mod types; pub mod viewing_keys; + +pub fn ceil_div(a: Uint256, b: Uint256) -> Uint256 { + if b == Uint256::zero() { + panic!("Division by zero"); + } + let div = a / b; + let rem = a % b; + if rem == Uint256::zero() { + div + } else { + div + Uint256::one() + } +} From 1d8652a1170f0630d27ddacdfbaf0efc9f0ac235 Mon Sep 17 00:00:00 2001 From: itshaseebsaeed Date: Wed, 8 Nov 2023 15:34:57 +0500 Subject: [PATCH 2/5] added time based and volume based rewards --- .../liquidity_book/lb_factory/src/contract.rs | 4 +- .../liquidity_book/lb_factory/src/state.rs | 2 + .../liquidity_book/lb_pair/src/contract.rs | 308 ++++---- contracts/liquidity_book/lb_pair/src/error.rs | 3 + contracts/liquidity_book/lb_pair/src/state.rs | 12 +- .../tests/src/multitests/lb_factory.rs | 34 +- .../tests/src/multitests/lb_pair_fees.rs | 696 +++++++++++++++++- .../src/multitests/lb_pair_initial_state.rs | 2 +- .../tests/src/multitests/lb_pair_liquidity.rs | 2 +- .../tests/src/multitests/lb_pair_swap.rs | 2 +- .../src/multitests/lb_router_integration.rs | 2 +- .../multitests/lb_router_register_tokens.rs | 2 +- .../tests/src/multitests/lb_token.rs | 2 +- .../tests/src/multitests/test_helper.rs | 13 +- .../multi_test/src/interfaces/lb_factory.rs | 4 + packages/multi_test/src/interfaces/lb_pair.rs | 30 +- .../liquidity_book/lb_factory.rs | 3 +- .../liquidity_book/lb_pair.rs | 8 +- .../src/utils/liquidity_book/mod.rs | 10 +- 19 files changed, 949 insertions(+), 190 deletions(-) diff --git a/contracts/liquidity_book/lb_factory/src/contract.rs b/contracts/liquidity_book/lb_factory/src/contract.rs index 3587c780..ac6f35d7 100644 --- a/contracts/liquidity_book/lb_factory/src/contract.rs +++ b/contracts/liquidity_book/lb_factory/src/contract.rs @@ -82,6 +82,7 @@ pub fn instantiate( lb_token_implementation: ContractInstantiationInfo::default(), admin_auth: msg.admin_auth.into_valid(deps.api)?, total_reward_bins: msg.total_reward_bins, + rewards_distribution_algorithm: msg.rewards_distribution_algorithm, }; CONFIG.save(deps.storage, &state)?; @@ -390,7 +391,8 @@ fn try_create_lb_pair( entropy, protocol_fee_recipient: state.fee_recipient, admin_auth: state.admin_auth.into(), - total_reward_bins: state.total_reward_bins, + total_reward_bins: Some(state.total_reward_bins), + rewards_distribution_algorithm: state.rewards_distribution_algorithm, })?, code_hash: state.lb_pair_implementation.code_hash.clone(), funds: vec![], diff --git a/contracts/liquidity_book/lb_factory/src/state.rs b/contracts/liquidity_book/lb_factory/src/state.rs index fa3a8115..7204d621 100644 --- a/contracts/liquidity_book/lb_factory/src/state.rs +++ b/contracts/liquidity_book/lb_factory/src/state.rs @@ -5,6 +5,7 @@ use cosmwasm_std::{Addr, ContractInfo, Storage}; use cosmwasm_storage::{singleton, singleton_read, ReadonlySingleton, Singleton}; use pair_parameter_helper::PairParameters; use shade_protocol::{ + liquidity_book::lb_pair::RewardsDistributionAlgorithm, secret_storage_plus::{AppendStore, Item, Map}, Contract, }; @@ -61,6 +62,7 @@ pub struct State { pub lb_token_implementation: ContractInstantiationInfo, pub admin_auth: Contract, pub total_reward_bins: u32, + pub rewards_distribution_algorithm: RewardsDistributionAlgorithm, } pub fn ephemeral_storage_w(storage: &mut dyn Storage) -> Singleton { diff --git a/contracts/liquidity_book/lb_pair/src/contract.rs b/contracts/liquidity_book/lb_pair/src/contract.rs index 47825a5c..5fdc2570 100644 --- a/contracts/liquidity_book/lb_pair/src/contract.rs +++ b/contracts/liquidity_book/lb_pair/src/contract.rs @@ -39,8 +39,8 @@ use shade_protocol::{ lb_token::InstantiateMsg as LBTokenInstantiateMsg, }, lb_libraries::{ + approx_div, bin_helper::{self, BinHelper}, - ceil_div, constants::{self, MAX_FEE, PRECISION, SCALE_OFFSET}, fee_helper::{self, FeeHelper}, lb_token::state_structs::{LbPair, TokenAmount, TokenIdBalance}, @@ -77,6 +77,7 @@ use types::{Bytes32, LBPairInformation, MintArrays}; pub const INSTANTIATE_LP_TOKEN_REPLY_ID: u64 = 1u64; pub const MINT_REPLY_ID: u64 = 1u64; const LB_PAIR_CONTRACT_VERSION: u32 = 1; +const DEFAULT_REWARDS_BINS: u32 = 100; /////////////// INSTANTIATE /////////////// #[shd_entry_point] @@ -168,8 +169,13 @@ pub fn instantiate( } } - if msg.total_reward_bins > U24::MAX { - return Err(Error::InvalidInput {}); + match msg.total_reward_bins { + Some(t_r_b) => { + if { t_r_b == U24::MAX } { + return Err(Error::InvalidInput {}); + } + } + None => {} } let state = State { creator: info.sender.clone(), @@ -187,11 +193,11 @@ pub fn instantiate( viewing_key, protocol_fees_recipient: msg.protocol_fee_recipient, admin_auth: msg.admin_auth.into_valid(deps.api)?, - last_rewards_epoch: env.block.time, + last_swap_timestamp: env.block.time, rewards_epoch_id: 0, - total_rewards_bin: msg.total_reward_bins, + base_rewards_bins: msg.total_reward_bins, + toggle_distributions_algorithm: false, //TODO: set using the setter function and instantiate msg - rewards_distribution_algorithm: RewardsDistributionAlgorithm::VolumeBasedRewards, }; // deps.api @@ -207,6 +213,11 @@ pub fn instantiate( CONTRACT_STATUS.save(deps.storage, &ContractStatus::Active); BIN_TREE.save(deps.storage, &tree)?; FEE_MAP_TREE.save(deps.storage, 0, &tree)?; + REWARDS_STATS_STORE.save(deps.storage, 0, &RewardStats { + cumm_value: Uint256::zero(), + cumm_value_mul_bin_id: Uint256::zero(), + rewards_distribution_algorithm: msg.rewards_distribution_algorithm, + }); ephemeral_storage_w(deps.storage).save(&NextTokenKey { code_hash: msg.lb_token_implementation.code_hash, @@ -310,9 +321,10 @@ pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> R ), ExecuteMsg::ForceDecay {} => try_force_decay(deps, env, info), ExecuteMsg::CalculateRewards {} => try_calculate_rewards(deps, env, info), - ExecuteMsg::ResetRewardsEpoch { distribution } => { - try_reset_rewards_epoch(deps, env, info, distribution) - } + ExecuteMsg::ResetRewardsConfig { + distribution, + base_rewards_bins, + } => try_reset_rewards_config(deps, env, info, distribution, base_rewards_bins), } } @@ -402,15 +414,21 @@ fn try_swap( let mut params = state.pair_parameters; let bin_step = state.bin_step; - let reward_stats = REWARDS_STATS_STORE + let mut reward_stats = REWARDS_STATS_STORE .load(deps.storage, state.rewards_epoch_id) - .unwrap_or_default(); - let mut cum_fee_bin = reward_stats.cumm_fee_x_bin; - let mut cum_fee = reward_stats.cumm_fee; + .unwrap(); let mut active_id = params.get_active_id(); params = params.update_references(&env.block.time)?; + if reward_stats.rewards_distribution_algorithm == RewardsDistributionAlgorithm::TimeBasedRewards + { + let time_difference = + Uint256::from(env.block.time.seconds() - state.last_swap_timestamp.seconds()); + + reward_stats.cumm_value += time_difference; + reward_stats.cumm_value_mul_bin_id += time_difference * (Uint256::from(active_id)); + } loop { let bin_reserves = BIN_MAP @@ -433,86 +451,55 @@ fn try_swap( )?; if U256::from_le_bytes(amounts_in_with_fees) > U256::ZERO { - let fee_obj = FeeLog { - is_token_x: swap_for_y, - fee: Uint128::from(fees.decode_alt(swap_for_y)), - bin_id: active_id, - timestamp: env.block.time, - last_rewards_epoch_id: state.rewards_epoch_id, - }; - //TODO: check if appending is needed - FEE_APPEND_STORE.push(deps.storage, &fee_obj)?; - - if fee_obj.is_token_x { - let divisor: Uint512 = Uint512::from(2u32).pow(SCALE_OFFSET.into()); - - let fee_temp: Uint512 = - Uint512::from((fee_obj.fee.u128().rotate_left(SCALE_OFFSET.into()))); - let prod = (fee_temp * Uint512::from(price.u256_to_uint256())) - .checked_div(divisor) - .unwrap(); - let prod_fee_price = U256::from_str_prefixed(&(prod).to_string()) - .unwrap() - .u256_to_uint256(); - - cum_fee += prod_fee_price; - cum_fee_bin += prod_fee_price * (Uint256::from(fee_obj.bin_id)); - - if state.rewards_distribution_algorithm - == RewardsDistributionAlgorithm::VolumeBasedRewards - { - let mut fee_map_tree = FEE_MAP_TREE.update( - deps.storage, - state.rewards_epoch_id, - |mut fee_tree| -> Result<_> { - Ok(match fee_tree { - Some(mut t) => { - t.add(active_id); - t - } - None => panic!("Fee tree not initialized"), - }) - }, - ); - - FEE_MAP.update(deps.storage, active_id, |mut cumm_fee| -> Result<_> { - let updated_cumm_fee = match cumm_fee { - Some(f) => f + prod_fee_price, - None => prod_fee_price, - }; - Ok(updated_cumm_fee) - })?; - } - } else { - let fee = Uint256::from(fee_obj.fee.u128()); - cum_fee += fee; - cum_fee_bin += fee * (Uint256::from(fee_obj.bin_id)); - - if state.rewards_distribution_algorithm - == RewardsDistributionAlgorithm::VolumeBasedRewards - { - let mut fee_map_tree = FEE_MAP_TREE.update( - deps.storage, - state.rewards_epoch_id, - |mut fee_tree| -> Result<_> { - Ok(match fee_tree { - Some(mut t) => { - t.add(active_id); - t - } - None => panic!("Fee tree not initialized"), - }) - }, - ); - - FEE_MAP.update(deps.storage, active_id, |mut cumm_fee| -> Result<_> { - let updated_cumm_fee = match cumm_fee { - Some(f) => f + fee, - None => fee, - }; - Ok(updated_cumm_fee) - })?; - } + // let fee_obj = FeeLog { + // is_token_x: swap_for_y, + // fee: Uint128::from(fees.decode_alt(swap_for_y)), + // bin_id: active_id, + // timestamp: env.block.time, + // last_rewards_epoch_id: state.rewards_epoch_id, + // }; + // //TODO: check if appending is needed + // FEE_APPEND_STORE.push(deps.storage, &fee_obj)?; + + if reward_stats.rewards_distribution_algorithm + == RewardsDistributionAlgorithm::VolumeBasedRewards + { + let feeu128 = fees.decode_alt(swap_for_y); + let swap_value_uint256 = match swap_for_y { + true => U256x256Math::mul_shift_round_up( + U256::from(feeu128), + price, + SCALE_OFFSET, + )? + .u256_to_uint256(), + false => Uint256::from(feeu128), + }; + println!( + "swap_value_uint256: {:?}, id: {:?}", + swap_value_uint256, active_id + ); + reward_stats.cumm_value += swap_value_uint256; + let mut fee_map_tree = FEE_MAP_TREE.update( + deps.storage, + state.rewards_epoch_id, + |mut fee_tree| -> Result<_> { + Ok(match fee_tree { + Some(mut t) => { + t.add(active_id); + t + } + None => panic!("Fee tree not initialized"), + }) + }, + ); + + FEE_MAP.update(deps.storage, active_id, |mut cumm_fee| -> Result<_> { + let updated_cumm_fee = match cumm_fee { + Some(f) => f + swap_value_uint256, + None => swap_value_uint256, + }; + Ok(updated_cumm_fee) + })?; } amounts_left = amounts_left.sub(amounts_in_with_fees); @@ -529,8 +516,6 @@ fn try_swap( amounts_in_with_fees = amounts_in_with_fees.sub(p_fees); } - // println!("SWAP MADE {:?}", active_id); - BIN_MAP.save( deps.storage, active_id, @@ -553,10 +538,7 @@ fn try_swap( } } - REWARDS_STATS_STORE.save(deps.storage, state.rewards_epoch_id, &RewardStats { - cumm_fee: cum_fee, - cumm_fee_x_bin: cum_fee_bin, - })?; + REWARDS_STATS_STORE.save(deps.storage, state.rewards_epoch_id, &reward_stats)?; if amounts_out == [0u8; 32] { return Err(Error::InsufficientAmountOut); @@ -568,6 +550,7 @@ fn try_swap( params = oracle.update(&env.block.time, params, active_id)?; CONFIG.update(deps.storage, |mut state| -> Result<_> { + state.last_swap_timestamp = env.block.time; state.protocol_fees = protocol_fees; state.pair_parameters = PairParameters::set_active_id(params, active_id)?; state.reserves = reserves; @@ -1543,68 +1526,113 @@ fn try_calculate_rewards(deps: DepsMut, env: Env, info: MessageInfo) -> Result { - calculate_base_rewards_distribution(&state, reward_stats)? - } - RewardsDistributionAlgorithm::VolumeBasedRewards => { - calculate_volume_based_rewards_distribution(deps.as_ref(), &state, reward_stats)? + let distribution = if !reward_stats.cumm_value.is_zero() { + match reward_stats.rewards_distribution_algorithm { + RewardsDistributionAlgorithm::TimeBasedRewards => { + calculate_time_based_rewards_distribution(&env, &state, &reward_stats)? + } + RewardsDistributionAlgorithm::VolumeBasedRewards => { + calculate_volume_based_rewards_distribution(deps.as_ref(), &state, &reward_stats)? + } } + } else { + let rewards_bins = match state.base_rewards_bins { + Some(r_b) => r_b, + None => DEFAULT_REWARDS_BINS, + }; + calculate_default_distribution(rewards_bins, state.pair_parameters.get_active_id())? }; REWARDS_DISTRIBUTION.save(deps.storage, state.rewards_epoch_id, &distribution)?; - - state.rewards_epoch_id += state.rewards_epoch_id + 1; + state.rewards_epoch_id += 1; + let toggle = state.toggle_distributions_algorithm; + state.last_swap_timestamp = env.block.time; + state.toggle_distributions_algorithm = false; CONFIG.save(deps.storage, &state)?; - let tree: TreeUint24 = TreeUint24::new(); - FEE_MAP_TREE.save(deps.storage, state.rewards_epoch_id, &tree)?; + let mut distribution_algorithm = &reward_stats.rewards_distribution_algorithm; + if toggle { + distribution_algorithm = match reward_stats.rewards_distribution_algorithm { + RewardsDistributionAlgorithm::TimeBasedRewards => { + &RewardsDistributionAlgorithm::VolumeBasedRewards + } + RewardsDistributionAlgorithm::VolumeBasedRewards => { + &RewardsDistributionAlgorithm::TimeBasedRewards + } + }; + } + + REWARDS_STATS_STORE.save(deps.storage, state.rewards_epoch_id, &RewardStats { + cumm_value: Uint256::zero(), + cumm_value_mul_bin_id: Uint256::zero(), + rewards_distribution_algorithm: distribution_algorithm.clone(), + })?; + if distribution_algorithm == &RewardsDistributionAlgorithm::VolumeBasedRewards { + let tree: TreeUint24 = TreeUint24::new(); + FEE_MAP_TREE.save(deps.storage, state.rewards_epoch_id, &tree)?; + } Ok(Response::default()) } -fn calculate_base_rewards_distribution( +fn calculate_time_based_rewards_distribution( + env: &Env, state: &State, - reward_stats: RewardStats, + reward_stats: &RewardStats, ) -> Result { - let mut cum_fee_bin = reward_stats.cumm_fee_x_bin; - let mut cum_fee = reward_stats.cumm_fee; - let avg_bin = ceil_div(cum_fee_bin, cum_fee).uint256_to_u256().as_u32(); + let mut cumm_value_mul_bin = reward_stats.cumm_value_mul_bin_id; + let mut cumm_value = reward_stats.cumm_value; + + let active_id = state.pair_parameters.get_active_id(); + + let time_difference = + Uint256::from(env.block.time.seconds() - state.last_swap_timestamp.seconds()); + + cumm_value += time_difference; + cumm_value_mul_bin += time_difference * (Uint256::from(active_id)); + + let avg_bin = approx_div(cumm_value_mul_bin, cumm_value) + .uint256_to_u256() + .as_u32(); - //TODO: make it variable and also make a setter function - let half_total = state.total_rewards_bin / 2; + let rewards_bins = match state.base_rewards_bins { + Some(r_b) => r_b, + None => DEFAULT_REWARDS_BINS, + }; + + calculate_default_distribution(rewards_bins, avg_bin) +} + +fn calculate_default_distribution(rewards_bins: u32, avg_bin: u32) -> Result { + let half_total = rewards_bins / 2; let min_bin = avg_bin.saturating_sub(half_total) + 1; let max_bin = avg_bin.saturating_add(half_total); let difference = max_bin - min_bin + 1; - //TODO: unit test edge cases for different bin_ids and lengths let ids: Vec = (min_bin..=max_bin).collect(); let weightages = vec![BASIS_POINT_MAX as u16 / difference as u16; difference as usize]; - let distribution = RewardsDistribution { + Ok(RewardsDistribution { ids, weightages, denominator: BASIS_POINT_MAX as u16, - }; - - Ok(distribution) + }) } fn calculate_volume_based_rewards_distribution( deps: Deps, state: &State, - reward_stats: RewardStats, + reward_stats: &RewardStats, ) -> Result { - let mut cum_fee = reward_stats.cumm_fee; - //TODO: unit test edge cases for different bin_ids and lengths + let mut cum_fee = reward_stats.cumm_value; let mut ids: Vec = Vec::new(); let mut weightages: Vec = Vec::new(); let fee_tree: TreeUint24 = FEE_MAP_TREE.load(deps.storage, state.rewards_epoch_id)?; let mut id: u32 = 0; let basis_point_max: Uint256 = Uint256::from(BASIS_POINT_MAX); + let mut total_weight = 0; loop { id = fee_tree.find_first_left(id); @@ -1619,6 +1647,14 @@ fn calculate_volume_based_rewards_distribution( .uint256_to_u256() .as_u16(); weightages.push(weightage); + total_weight += weightage; + } + + let reminder = BASIS_POINT_MAX as u16 - total_weight; + + if reminder > 0 { + let len = weightages.len() - 1; + weightages[len] += reminder; } let distribution = RewardsDistribution { @@ -1631,11 +1667,13 @@ fn calculate_volume_based_rewards_distribution( } //Can only change the distribution algorithm at the start of next epoch -fn try_reset_rewards_epoch( +//Eventhough the distribution was changes mid epoch the effects of change will occur after the epoch. +fn try_reset_rewards_config( deps: DepsMut, env: Env, info: MessageInfo, rewards_distribution_algorithm: Option, + base_reward_bins: Option, ) -> Result { let mut state = CONFIG.load(deps.storage)?; validate_admin( @@ -1644,9 +1682,25 @@ fn try_reset_rewards_epoch( info.sender.to_string(), &state.admin_auth, )?; + let mut reward_stats = REWARDS_STATS_STORE.load(deps.storage, state.rewards_epoch_id)?; + //Eventhough the distribution was changes mid epoch the effects of change will occur after the epoch. match rewards_distribution_algorithm { - Some(distribution) => state.rewards_distribution_algorithm = distribution, + Some(distribution) => { + if reward_stats.rewards_distribution_algorithm != distribution { + state.toggle_distributions_algorithm = true; + } + } + None => {} + }; + + match base_reward_bins { + Some(b_r_b) => { + if b_r_b > U24::MAX { + return Err(Error::U24Overflow); + } + state.base_rewards_bins = Some(b_r_b) + } None => {} } diff --git a/contracts/liquidity_book/lb_pair/src/error.rs b/contracts/liquidity_book/lb_pair/src/error.rs index 52590dc9..1d5d6800 100644 --- a/contracts/liquidity_book/lb_pair/src/error.rs +++ b/contracts/liquidity_book/lb_pair/src/error.rs @@ -65,6 +65,9 @@ pub enum LBPairError { #[error("Not enough liquidity!")] OutOfLiquidity, + #[error("value greater than u24!")] + U24Overflow, + #[error("Token not supported!")] TokenNotSupported(), diff --git a/contracts/liquidity_book/lb_pair/src/state.rs b/contracts/liquidity_book/lb_pair/src/state.rs index 36311cee..e60089d6 100644 --- a/contracts/liquidity_book/lb_pair/src/state.rs +++ b/contracts/liquidity_book/lb_pair/src/state.rs @@ -35,10 +35,10 @@ pub const FEE_MAP_TREE: Map = Map::new("fee_tree"); / pub const FEE_MAP: Map = Map::new("fee_map"); //? #[cw_serde] -#[derive(Default)] pub struct RewardStats { - pub cumm_fee: Uint256, - pub cumm_fee_x_bin: Uint256, + pub cumm_value: Uint256, + pub cumm_value_mul_bin_id: Uint256, + pub rewards_distribution_algorithm: RewardsDistributionAlgorithm, } #[cw_serde] @@ -71,10 +71,10 @@ pub struct State { pub lb_token: ContractInfo, pub protocol_fees_recipient: Addr, pub admin_auth: Contract, - pub last_rewards_epoch: Timestamp, + pub last_swap_timestamp: Timestamp, pub rewards_epoch_id: u64, - pub total_rewards_bin: u32, - pub rewards_distribution_algorithm: RewardsDistributionAlgorithm, + pub base_rewards_bins: Option, + pub toggle_distributions_algorithm: bool, } pub fn ephemeral_storage_w(storage: &mut dyn Storage) -> Singleton { diff --git a/contracts/liquidity_book/tests/src/multitests/lb_factory.rs b/contracts/liquidity_book/tests/src/multitests/lb_factory.rs index 6af79155..dfa2099d 100644 --- a/contracts/liquidity_book/tests/src/multitests/lb_factory.rs +++ b/contracts/liquidity_book/tests/src/multitests/lb_factory.rs @@ -34,13 +34,14 @@ use shade_protocol::{ }, tokens::TokenType, }, + liquidity_book::lb_pair::RewardsDistributionAlgorithm, utils::MultiTestable, }; #[test] pub fn test_setup() -> Result<(), anyhow::Error> { let addrs = init_addrs(); - let (mut app, lb_factory, _deployed_contracts) = setup(None)?; + let (mut app, lb_factory, _deployed_contracts) = setup(None, None)?; //query fee recipient let fee_recipient = lb_factory::query_fee_recipient(&mut app, &lb_factory.clone().into())?; @@ -62,7 +63,7 @@ pub fn test_setup() -> Result<(), anyhow::Error> { #[test] pub fn test_set_lb_pair_implementation() -> Result<(), anyhow::Error> { let addrs = init_addrs(); - let (mut app, lb_factory, _deployed_contracts) = setup(None)?; + let (mut app, lb_factory, _deployed_contracts) = setup(None, None)?; let lb_pair_stored_code = app.store_code(LbPair::default().contract()); lb_factory::set_lb_pair_implementation( @@ -81,7 +82,7 @@ pub fn test_set_lb_pair_implementation() -> Result<(), anyhow::Error> { #[test] pub fn test_revert_set_lb_pair_implementation() -> Result<(), anyhow::Error> { let addrs = init_addrs(); - let (mut app, lb_factory, _deployed_contracts) = setup(None)?; + let (mut app, lb_factory, _deployed_contracts) = setup(None, None)?; let lb_pair_stored_code = app.store_code(LbPair::default().contract()); lb_factory::set_lb_pair_implementation( @@ -113,7 +114,7 @@ pub fn test_revert_set_lb_pair_implementation() -> Result<(), anyhow::Error> { #[test] pub fn test_set_lb_token_implementation() -> Result<(), anyhow::Error> { let addrs = init_addrs(); - let (mut app, lb_factory, _deployed_contracts) = setup(None)?; + let (mut app, lb_factory, _deployed_contracts) = setup(None, None)?; let lb_token_stored_code = app.store_code(LbToken::default().contract()); lb_factory::set_lb_token_implementation( &mut app, @@ -131,7 +132,7 @@ pub fn test_set_lb_token_implementation() -> Result<(), anyhow::Error> { #[test] pub fn test_create_lb_pair() -> Result<(), anyhow::Error> { let addrs = init_addrs(); - let (mut app, lb_factory, deployed_contracts) = setup(None)?; + let (mut app, lb_factory, deployed_contracts) = setup(None, None)?; // 3. Create an LBPair. @@ -219,7 +220,7 @@ pub fn test_create_lb_pair() -> Result<(), anyhow::Error> { #[test] pub fn test_create_lb_pair_factory_unlocked() -> Result<(), anyhow::Error> { let addrs = init_addrs(); - let (mut app, lb_factory, deployed_contracts) = setup(None)?; + let (mut app, lb_factory, deployed_contracts) = setup(None, None)?; let shd = extract_contract_info(&deployed_contracts, SHADE)?; let sscrt = extract_contract_info(&deployed_contracts, SSCRT)?; @@ -310,7 +311,7 @@ pub fn test_create_lb_pair_factory_unlocked() -> Result<(), anyhow::Error> { #[test] fn test_revert_create_lb_pair() -> Result<(), anyhow::Error> { let addrs = init_addrs(); - let (mut app, lb_factory, deployed_contracts) = setup(None)?; + let (mut app, lb_factory, deployed_contracts) = setup(None, None)?; let shd = extract_contract_info(&deployed_contracts, SHADE)?; let sscrt = extract_contract_info(&deployed_contracts, SSCRT)?; @@ -347,6 +348,7 @@ fn test_revert_create_lb_pair() -> Result<(), anyhow::Error> { 0, admin_contract.into(), 100, + Some(RewardsDistributionAlgorithm::TimeBasedRewards), )?; //can't create a pair if the preset is not set @@ -521,7 +523,7 @@ fn test_revert_create_lb_pair() -> Result<(), anyhow::Error> { #[test] fn test_fuzz_set_preset() -> Result<(), anyhow::Error> { let addrs = init_addrs(); - let (mut app, lb_factory, _deployed_contracts) = setup(None)?; + let (mut app, lb_factory, _deployed_contracts) = setup(None, None)?; let mut bin_step: u16 = generate_random(0, u16::MAX); let base_factor: u16 = generate_random(0, u16::MAX); let mut filter_period: u16 = generate_random(0, u16::MAX); @@ -596,7 +598,7 @@ fn test_fuzz_set_preset() -> Result<(), anyhow::Error> { #[test] fn test_remove_preset() -> Result<(), anyhow::Error> { let addrs = init_addrs(); - let (mut app, lb_factory, _deployed_contracts) = setup(None)?; + let (mut app, lb_factory, _deployed_contracts) = setup(None, None)?; // Set presets lb_factory::set_pair_preset( @@ -677,7 +679,7 @@ fn test_remove_preset() -> Result<(), anyhow::Error> { #[test] pub fn test_set_fees_parameters_on_pair() -> Result<(), anyhow::Error> { let addrs = init_addrs(); - let (mut app, lb_factory, deployed_contracts) = setup(None)?; + let (mut app, lb_factory, deployed_contracts) = setup(None, None)?; let sscrt = extract_contract_info(&deployed_contracts, SSCRT)?; let shd = extract_contract_info(&deployed_contracts, SHADE)?; @@ -799,7 +801,7 @@ pub fn test_set_fees_parameters_on_pair() -> Result<(), anyhow::Error> { #[test] pub fn test_set_fee_recipient() -> Result<(), anyhow::Error> { let addrs = init_addrs(); - let (mut app, lb_factory, _deployed_contracts) = setup(None)?; + let (mut app, lb_factory, _deployed_contracts) = setup(None, None)?; lb_factory::set_fee_recipient( &mut app, addrs.admin().as_str(), @@ -839,7 +841,7 @@ pub fn test_set_fee_recipient() -> Result<(), anyhow::Error> { #[test] pub fn test_fuzz_open_presets() -> Result<(), anyhow::Error> { let addrs = init_addrs(); // Initialize addresses - let (mut app, lb_factory, _deployed_contracts) = setup(None)?; // Setup + let (mut app, lb_factory, _deployed_contracts) = setup(None, None)?; // Setup let min_bin_step = lb_factory::query_min_bin_step(&mut app, &lb_factory.clone().into())?; let max_bin_step = u16::MAX; @@ -937,7 +939,7 @@ pub fn test_fuzz_open_presets() -> Result<(), anyhow::Error> { #[test] pub fn test_add_quote_asset() -> Result<(), anyhow::Error> { let addrs = init_addrs(); // Initialize addresses - let (mut app, lb_factory, mut deployed_contracts) = setup(None)?; // Setup + let (mut app, lb_factory, mut deployed_contracts) = setup(None, None)?; // Setup let num_quote_assets_before = lb_factory::query_number_of_quote_assets(&mut app, &lb_factory.clone().into())?; @@ -1024,7 +1026,7 @@ pub fn test_add_quote_asset() -> Result<(), anyhow::Error> { #[test] pub fn test_remove_quote_asset() -> Result<(), anyhow::Error> { let addrs = init_addrs(); // Initialize addresses - let (mut app, lb_factory, mut deployed_contracts) = setup(None)?; // Setup + let (mut app, lb_factory, mut deployed_contracts) = setup(None, None)?; // Setup //SSCRT and SHD already added as quote asset let num_quote_assets_before = @@ -1108,7 +1110,7 @@ pub fn test_remove_quote_asset() -> Result<(), anyhow::Error> { #[test] pub fn test_force_decay() -> Result<(), anyhow::Error> { let addrs = init_addrs(); // Initialize addresses - let (mut app, lb_factory, deployed_contracts) = setup(None)?; // Setup + let (mut app, lb_factory, deployed_contracts) = setup(None, None)?; // Setup let sscrt_info = extract_contract_info(&deployed_contracts, SSCRT)?; let sscrt = token_type_snip20_generator(&sscrt_info)?; @@ -1157,7 +1159,7 @@ pub fn test_force_decay() -> Result<(), anyhow::Error> { #[test] pub fn test_get_all_lb_pair() -> Result<(), anyhow::Error> { let addrs = init_addrs(); - let (mut app, lb_factory, deployed_contracts) = setup(None)?; + let (mut app, lb_factory, deployed_contracts) = setup(None, None)?; // 3. Create an LBPair. diff --git a/contracts/liquidity_book/tests/src/multitests/lb_pair_fees.rs b/contracts/liquidity_book/tests/src/multitests/lb_pair_fees.rs index 03b57115..2d9d8929 100644 --- a/contracts/liquidity_book/tests/src/multitests/lb_pair_fees.rs +++ b/contracts/liquidity_book/tests/src/multitests/lb_pair_fees.rs @@ -1,5 +1,3 @@ -use std::ops::Sub; - use crate::multitests::{lb_pair_liquidity::PRECISION, test_helper::*}; use super::test_helper::{ @@ -11,7 +9,7 @@ use super::test_helper::{ ID_ONE, }; use anyhow::Ok; -use cosmwasm_std::{ContractInfo, StdError, Uint128, Uint256}; +use cosmwasm_std::{ContractInfo, StdError, Timestamp, Uint128, Uint256}; use ethnum::U256; use shade_multi_test::interfaces::{ lb_factory, @@ -25,7 +23,7 @@ use shade_protocol::{ math::{encoded_sample::MASK_UINT20, u24::U24}, types::LBPairInformation, }, - liquidity_book::lb_pair::RemoveLiquidity, + liquidity_book::lb_pair::{RemoveLiquidity, RewardsDistributionAlgorithm}, multi_test::App, }; @@ -44,7 +42,7 @@ pub fn lb_pair_setup() -> Result< anyhow::Error, > { let addrs = init_addrs(); - let (mut app, lb_factory, deployed_contracts) = setup(None)?; + let (mut app, lb_factory, deployed_contracts) = setup(None, None)?; let silk = extract_contract_info(&deployed_contracts, SILK)?; let shade = extract_contract_info(&deployed_contracts, SHADE)?; @@ -2171,7 +2169,7 @@ pub fn test_revert_total_fee_exceeded() -> Result<(), anyhow::Error> { let addrs = init_addrs(); let bin_step = Uint128::from(generate_random(1u16, u16::MAX)); - let (mut app, lb_factory, deployed_contracts) = setup(Some(bin_step.u128() as u16))?; + let (mut app, lb_factory, deployed_contracts) = setup(Some(bin_step.u128() as u16), None)?; let silk = extract_contract_info(&deployed_contracts, SILK)?; let shade = extract_contract_info(&deployed_contracts, SHADE)?; let token_x = token_type_snip20_generator(&shade)?; @@ -2228,7 +2226,7 @@ pub fn test_revert_total_fee_exceeded() -> Result<(), anyhow::Error> { #[test] pub fn test_fuzz_swap_in_x_and_y_btc_silk() -> Result<(), anyhow::Error> { let addrs = init_addrs(); - let (mut app, lb_factory, deployed_contracts) = setup(None)?; + let (mut app, lb_factory, deployed_contracts) = setup(None, None)?; let btc = extract_contract_info(&deployed_contracts, SBTC)?; let silk = extract_contract_info(&deployed_contracts, SILK)?; @@ -2451,20 +2449,16 @@ pub fn test_fuzz_swap_in_x_and_y_btc_silk() -> Result<(), anyhow::Error> { } #[test] -pub fn test_fuzz_calculate_rewards() -> Result<(), anyhow::Error> { +pub fn test_fuzz_calculate_volume_based_rewards() -> Result<(), anyhow::Error> { let addrs = init_addrs(); - let (mut app, lb_factory, deployed_contracts) = setup(None)?; + let (mut app, lb_factory, deployed_contracts) = + setup(None, Some(RewardsDistributionAlgorithm::VolumeBasedRewards))?; let btc = extract_contract_info(&deployed_contracts, SBTC)?; let silk = extract_contract_info(&deployed_contracts, SILK)?; let token_x = token_type_snip20_generator(&btc)?; let token_y = token_type_snip20_generator(&silk)?; - //assuming the ratio of btc to silk 1:40000 - //Hence 1 usilk = 400 satoishi - // (1+DEFAULT_BIN_STEP/BASIS_POINT)^x = 400 - // x = 5994 - lb_factory::create_lb_pair( &mut app, addrs.admin().as_str(), @@ -2489,8 +2483,10 @@ pub fn test_fuzz_calculate_rewards() -> Result<(), anyhow::Error> { "viewing_key".to_owned(), )?; - let amount_x = Uint128::from(((10000u128) * 10000_0000) / 40000); // 25_000_000 satoshi - let amount_y = Uint128::from((10000u128) * 1000_000); // 10_000 silk + let deposit_ratio = (generate_random(1u128, DEPOSIT_AMOUNT)); + + let amount_x = Uint128::from(((deposit_ratio) * 10000_0000) / 40000); // 25_000_000 satoshi + let amount_y = Uint128::from((deposit_ratio) * 1000_000); // 10_000 silk let nb_bins_x = 10; let nb_bins_y = 10; @@ -2537,7 +2533,7 @@ pub fn test_fuzz_calculate_rewards() -> Result<(), anyhow::Error> { //generate random number // let amount_y_out = Uint128::from(generate_random(1u128, DEPOSIT_AMOUNT - 1)); - let amount_y_out = Uint128::from(10000 * 1000_000u128); //1000 silk + let amount_y_out = Uint128::from(generate_random(1u128, amount_y.u128() - 1)); // get swap_in for y let (amount_x_in, _amount_y_out_left, _fee) = lb_pair::query_swap_in(&app, &lb_pair.lb_pair.contract, amount_y_out, true)?; @@ -2564,8 +2560,7 @@ pub fn test_fuzz_calculate_rewards() -> Result<(), anyhow::Error> { //generate random number // let amount_y_out = Uint128::from(generate_random(1u128, DEPOSIT_AMOUNT - 1)); - let amount_x_out = Uint128::from(5 * 1000_000u128); //5_000_000 satoshi - // get swap_in for y + let amount_x_out = Uint128::from(generate_random(1u128, amount_x.u128() - 1)); // get swap_in for y let (amount_y_in, _amount_x_out_left, _fee) = lb_pair::query_swap_in(&app, &lb_pair.lb_pair.contract, amount_x_out, false)?; @@ -2591,9 +2586,668 @@ pub fn test_fuzz_calculate_rewards() -> Result<(), anyhow::Error> { lb_pair::calculate_rewards(&mut app, addrs.admin().as_str(), &lb_pair.lb_pair.contract)?; - let distribution = lb_pair::query_rewards_distribution(&app, &lb_pair.lb_pair.contract, None)?; + let _distribution = lb_pair::query_rewards_distribution(&app, &lb_pair.lb_pair.contract, None)?; + Ok(()) +} + +#[test] +pub fn test_calculate_volume_based_rewards() -> Result<(), anyhow::Error> { + let addrs = init_addrs(); + let (mut app, lb_factory, deployed_contracts) = + setup(None, Some(RewardsDistributionAlgorithm::VolumeBasedRewards))?; + + let btc = extract_contract_info(&deployed_contracts, SBTC)?; + let silk = extract_contract_info(&deployed_contracts, SILK)?; + let token_x = token_type_snip20_generator(&btc)?; + let token_y = token_type_snip20_generator(&silk)?; + + lb_factory::create_lb_pair( + &mut app, + addrs.admin().as_str(), + &lb_factory.clone().into(), + DEFAULT_BIN_STEP, + ACTIVE_ID + 5994, + token_x.clone(), + token_y.clone(), + "viewing_key".to_string(), + "entropy".to_string(), + )?; + let all_pairs = + lb_factory::query_all_lb_pairs(&mut app, &lb_factory.clone().into(), token_x, token_y)?; + let lb_pair = all_pairs[0].clone(); + + let lb_token = lb_pair::lb_token_query(&app, &lb_pair.lb_pair.contract)?; + + lb_token::set_viewing_key( + &mut app, + addrs.batman().as_str(), + &lb_token, + "viewing_key".to_owned(), + )?; + + let deposit_ratio = DEPOSIT_AMOUNT; + + let amount_x = Uint128::from(((deposit_ratio) * 10000_0000) / 40000); + let amount_y = Uint128::from((deposit_ratio) * 1000_000); + + let nb_bins_x = 10; + let nb_bins_y = 10; + + let token_x = extract_contract_info(&deployed_contracts, SBTC)?; + let token_y = extract_contract_info(&deployed_contracts, SILK)?; + + let tokens_to_mint = vec![(SBTC, amount_x), (SILK, amount_y)]; + + mint_token_helper( + &mut app, + &deployed_contracts, + &addrs, + addrs.batman().into_string(), + tokens_to_mint.clone(), + )?; + + increase_allowance_helper( + &mut app, + &deployed_contracts, + addrs.batman().into_string(), + lb_pair.lb_pair.contract.address.to_string(), + tokens_to_mint, + )?; + + //Adding liquidity + let liquidity_parameters = liquidity_parameters_generator( + &deployed_contracts, + ACTIVE_ID + 5994, + token_x, + token_y, + amount_x, + amount_y, + nb_bins_x, + nb_bins_y, + )?; + + lb_pair::add_liquidity( + &mut app, + addrs.batman().as_str(), + &lb_pair.lb_pair.contract, + liquidity_parameters, + )?; + + //generate random number + // let amount_y_out = Uint128::from(generate_random(1u128, DEPOSIT_AMOUNT - 1)); + let amount_y_out = amount_y.multiply_ratio(5u128, 10u128).u128(); + // get swap_in for y + let (amount_x_in, _amount_y_out_left, _fee) = + lb_pair::query_swap_in(&app, &lb_pair.lb_pair.contract, amount_y_out.into(), true)?; + + // mint the tokens + let tokens_to_mint = vec![(SBTC, amount_x_in)]; + mint_token_helper( + &mut app, + &deployed_contracts, + &addrs, + addrs.batman().into_string(), + tokens_to_mint.clone(), + )?; + // make a swap with amount_x_in + let token_x: &ContractInfo = &extract_contract_info(&deployed_contracts, SBTC)?; + lb_pair::swap_snip_20( + &mut app, + addrs.batman().as_str(), + &lb_pair.lb_pair.contract, + Some(addrs.batman().to_string()), + token_x, + amount_x_in, + )?; + + let timestamp = Timestamp::from_seconds(app.block_info().time.seconds() + 600); + + app.set_time(timestamp); + + //generate random number + // let amount_y_out = Uint128::from(generate_random(1u128, DEPOSIT_AMOUNT - 1)); + let amount_x_out = amount_x.multiply_ratio(5u128, 10u128).u128(); // get swap_in for y + let (amount_y_in, _amount_x_out_left, _fee) = + lb_pair::query_swap_in(&app, &lb_pair.lb_pair.contract, amount_x_out.into(), false)?; + + // mint the tokens + let tokens_to_mint = vec![(SILK, amount_y_in)]; + mint_token_helper( + &mut app, + &deployed_contracts, + &addrs, + addrs.batman().into_string(), + tokens_to_mint.clone(), + )?; + // make a swap with amount_x_in + let token_x: &ContractInfo = &extract_contract_info(&deployed_contracts, SILK)?; + lb_pair::swap_snip_20( + &mut app, + addrs.batman().as_str(), + &lb_pair.lb_pair.contract, + Some(addrs.batman().to_string()), + token_x, + amount_y_in, + )?; + + lb_pair::calculate_rewards(&mut app, addrs.admin().as_str(), &lb_pair.lb_pair.contract)?; + + let _distribution = lb_pair::query_rewards_distribution(&app, &lb_pair.lb_pair.contract, None)?; + // println!("Distribution {:?}", _distribution); + Ok(()) +} + +#[test] +pub fn test_calculate_time_based_rewards() -> Result<(), anyhow::Error> { + let addrs = init_addrs(); + let (mut app, lb_factory, deployed_contracts) = setup(None, None)?; + + let sscrt = extract_contract_info(&deployed_contracts, SSCRT)?; + let silk = extract_contract_info(&deployed_contracts, SILK)?; + let token_x = token_type_snip20_generator(&sscrt)?; + let token_y = token_type_snip20_generator(&silk)?; + + lb_factory::create_lb_pair( + &mut app, + addrs.admin().as_str(), + &lb_factory.clone().into(), + DEFAULT_BIN_STEP, + ACTIVE_ID, + token_x.clone(), + token_y.clone(), + "viewing_key".to_string(), + "entropy".to_string(), + )?; + let all_pairs = + lb_factory::query_all_lb_pairs(&mut app, &lb_factory.clone().into(), token_x, token_y)?; + let lb_pair = all_pairs[0].clone(); + + let lb_token = lb_pair::lb_token_query(&app, &lb_pair.lb_pair.contract)?; + + lb_token::set_viewing_key( + &mut app, + addrs.batman().as_str(), + &lb_token, + "viewing_key".to_owned(), + )?; + + let amount_x = Uint128::from(DEPOSIT_AMOUNT); // 25_000_000 satoshi + let amount_y = Uint128::from(DEPOSIT_AMOUNT); // 10_000 silk + + let nb_bins_x = 10; + let nb_bins_y = 10; + + let token_x = extract_contract_info(&deployed_contracts, SSCRT)?; + let token_y = extract_contract_info(&deployed_contracts, SILK)?; + + let tokens_to_mint = vec![(SSCRT, amount_x), (SILK, amount_y)]; + + mint_token_helper( + &mut app, + &deployed_contracts, + &addrs, + addrs.batman().into_string(), + tokens_to_mint.clone(), + )?; + + increase_allowance_helper( + &mut app, + &deployed_contracts, + addrs.batman().into_string(), + lb_pair.lb_pair.contract.address.to_string(), + tokens_to_mint, + )?; + + //Adding liquidity + let liquidity_parameters = liquidity_parameters_generator( + &deployed_contracts, + ACTIVE_ID, + token_x, + token_y, + amount_x, + amount_y, + nb_bins_x, + nb_bins_y, + )?; + + lb_pair::add_liquidity( + &mut app, + addrs.batman().as_str(), + &lb_pair.lb_pair.contract, + liquidity_parameters, + )?; + + let (_, bin_reserves_y) = lb_pair::query_bin(&app, &lb_pair.lb_pair.contract, ACTIVE_ID)?; + + let timestamp = Timestamp::from_seconds(app.block_info().time.seconds() + 3); + app.set_time(timestamp); + + //making a swap for token y hence the bin id moves to the right + let (amount_x_in, _amount_y_out_left, _fee) = lb_pair::query_swap_in( + &app, + &lb_pair.lb_pair.contract, + Uint128::from(bin_reserves_y + 1), + true, + )?; + + // mint the tokens + let tokens_to_mint = vec![(SSCRT, amount_x_in)]; + mint_token_helper( + &mut app, + &deployed_contracts, + &addrs, + addrs.batman().into_string(), + tokens_to_mint.clone(), + )?; + // make a swap with amount_x_in + let token_x: &ContractInfo = &extract_contract_info(&deployed_contracts, SSCRT)?; + lb_pair::swap_snip_20( + &mut app, + addrs.batman().as_str(), + &lb_pair.lb_pair.contract, + Some(addrs.batman().to_string()), + token_x, + amount_x_in, + )?; + + let active_id = lb_pair::query_active_id(&app, &lb_pair.lb_pair.contract)?; + + assert_eq!(active_id, ACTIVE_ID - 1); + + //making a swap for token y hence the bin id moves to the right + let timestamp = Timestamp::from_seconds(app.block_info().time.seconds() + 7); + app.set_time(timestamp); + + let (amount_x_in, _amount_y_out_left, _fee) = lb_pair::query_swap_in( + &app, + &lb_pair.lb_pair.contract, + Uint128::from(bin_reserves_y * 5), + true, + )?; + + // mint the tokens + let tokens_to_mint = vec![(SSCRT, amount_x_in)]; + mint_token_helper( + &mut app, + &deployed_contracts, + &addrs, + addrs.batman().into_string(), + tokens_to_mint.clone(), + )?; + // make a swap with amount_x_in + let token_x: &ContractInfo = &extract_contract_info(&deployed_contracts, SSCRT)?; + lb_pair::swap_snip_20( + &mut app, + addrs.batman().as_str(), + &lb_pair.lb_pair.contract, + Some(addrs.batman().to_string()), + token_x, + amount_x_in, + )?; + + let active_id = lb_pair::query_active_id(&app, &lb_pair.lb_pair.contract)?; + + assert_eq!(active_id, ACTIVE_ID - 1 - 5); + + let timestamp = Timestamp::from_seconds(app.block_info().time.seconds() + 43); + app.set_time(timestamp); + + lb_pair::calculate_rewards(&mut app, addrs.admin().as_str(), &lb_pair.lb_pair.contract)?; + + let _distribution = lb_pair::query_rewards_distribution(&app, &lb_pair.lb_pair.contract, None)?; + + Ok(()) +} + +#[test] +pub fn test_fuzz_calculate_time_based_rewards() -> Result<(), anyhow::Error> { + let addrs = init_addrs(); + let (mut app, lb_factory, deployed_contracts) = setup(None, None)?; + + let sscrt = extract_contract_info(&deployed_contracts, SSCRT)?; + let silk = extract_contract_info(&deployed_contracts, SILK)?; + let token_x = token_type_snip20_generator(&sscrt)?; + let token_y = token_type_snip20_generator(&silk)?; + + lb_factory::create_lb_pair( + &mut app, + addrs.admin().as_str(), + &lb_factory.clone().into(), + DEFAULT_BIN_STEP, + ACTIVE_ID, + token_x.clone(), + token_y.clone(), + "viewing_key".to_string(), + "entropy".to_string(), + )?; + let all_pairs = + lb_factory::query_all_lb_pairs(&mut app, &lb_factory.clone().into(), token_x, token_y)?; + let lb_pair = all_pairs[0].clone(); + + let lb_token = lb_pair::lb_token_query(&app, &lb_pair.lb_pair.contract)?; + + lb_token::set_viewing_key( + &mut app, + addrs.batman().as_str(), + &lb_token, + "viewing_key".to_owned(), + )?; + + let amount_x = Uint128::from(DEPOSIT_AMOUNT); // 25_000_000 satoshi + let amount_y = Uint128::from(DEPOSIT_AMOUNT); // 10_000 silk + + let nb_bins_x = 10; + let nb_bins_y = 10; + + let token_x = extract_contract_info(&deployed_contracts, SSCRT)?; + let token_y = extract_contract_info(&deployed_contracts, SILK)?; + + let tokens_to_mint = vec![(SSCRT, amount_x), (SILK, amount_y)]; + + mint_token_helper( + &mut app, + &deployed_contracts, + &addrs, + addrs.batman().into_string(), + tokens_to_mint.clone(), + )?; + + increase_allowance_helper( + &mut app, + &deployed_contracts, + addrs.batman().into_string(), + lb_pair.lb_pair.contract.address.to_string(), + tokens_to_mint, + )?; + + //Adding liquidity + let liquidity_parameters = liquidity_parameters_generator( + &deployed_contracts, + ACTIVE_ID, + token_x, + token_y, + amount_x, + amount_y, + nb_bins_x, + nb_bins_y, + )?; + + lb_pair::add_liquidity( + &mut app, + addrs.batman().as_str(), + &lb_pair.lb_pair.contract, + liquidity_parameters, + )?; + + let (_, bin_reserves_y) = lb_pair::query_bin(&app, &lb_pair.lb_pair.contract, ACTIVE_ID)?; + + let timestamp = Timestamp::from_seconds(app.block_info().time.seconds() + 3); + app.set_time(timestamp); + + //making a swap for token y hence the bin id moves to the right + let (amount_x_in, _amount_y_out_left, _fee) = lb_pair::query_swap_in( + &app, + &lb_pair.lb_pair.contract, + Uint128::from(bin_reserves_y + 1), + true, + )?; + + // mint the tokens + let tokens_to_mint = vec![(SSCRT, amount_x_in)]; + mint_token_helper( + &mut app, + &deployed_contracts, + &addrs, + addrs.batman().into_string(), + tokens_to_mint.clone(), + )?; + // make a swap with amount_x_in + let token_x: &ContractInfo = &extract_contract_info(&deployed_contracts, SSCRT)?; + lb_pair::swap_snip_20( + &mut app, + addrs.batman().as_str(), + &lb_pair.lb_pair.contract, + Some(addrs.batman().to_string()), + token_x, + amount_x_in, + )?; + + let active_id = lb_pair::query_active_id(&app, &lb_pair.lb_pair.contract)?; + assert_eq!(active_id, ACTIVE_ID - 1); + + //making a swap for token y hence the bin id moves to the right + let timestamp = Timestamp::from_seconds(app.block_info().time.seconds() + 7); + app.set_time(timestamp); + + let (amount_x_in, _amount_y_out_left, _fee) = lb_pair::query_swap_in( + &app, + &lb_pair.lb_pair.contract, + Uint128::from(bin_reserves_y * 5), + true, + )?; + + // mint the tokens + let tokens_to_mint = vec![(SSCRT, amount_x_in)]; + mint_token_helper( + &mut app, + &deployed_contracts, + &addrs, + addrs.batman().into_string(), + tokens_to_mint.clone(), + )?; + // make a swap with amount_x_in + let token_x: &ContractInfo = &extract_contract_info(&deployed_contracts, SSCRT)?; + lb_pair::swap_snip_20( + &mut app, + addrs.batman().as_str(), + &lb_pair.lb_pair.contract, + Some(addrs.batman().to_string()), + token_x, + amount_x_in, + )?; + + let active_id = lb_pair::query_active_id(&app, &lb_pair.lb_pair.contract)?; + assert_eq!(active_id, ACTIVE_ID - 1 - 5); + + let timestamp = Timestamp::from_seconds(app.block_info().time.seconds() + 43); + app.set_time(timestamp); + + lb_pair::calculate_rewards(&mut app, addrs.admin().as_str(), &lb_pair.lb_pair.contract)?; + + let _distribution = lb_pair::query_rewards_distribution(&app, &lb_pair.lb_pair.contract, None)?; + // println!("_distribution {:?}", _distribution); + Ok(()) +} + +#[test] +pub fn test_reset_rewards_config() -> Result<(), anyhow::Error> { + let addrs = init_addrs(); + let (mut app, lb_factory, deployed_contracts) = setup(None, None)?; + + let sscrt = extract_contract_info(&deployed_contracts, SSCRT)?; + let silk = extract_contract_info(&deployed_contracts, SILK)?; + let token_x = token_type_snip20_generator(&sscrt)?; + let token_y = token_type_snip20_generator(&silk)?; + + lb_factory::create_lb_pair( + &mut app, + addrs.admin().as_str(), + &lb_factory.clone().into(), + DEFAULT_BIN_STEP, + ACTIVE_ID, + token_x.clone(), + token_y.clone(), + "viewing_key".to_string(), + "entropy".to_string(), + )?; + let all_pairs = + lb_factory::query_all_lb_pairs(&mut app, &lb_factory.clone().into(), token_x, token_y)?; + let lb_pair = all_pairs[0].clone(); + + let lb_token = lb_pair::lb_token_query(&app, &lb_pair.lb_pair.contract)?; + + lb_token::set_viewing_key( + &mut app, + addrs.batman().as_str(), + &lb_token, + "viewing_key".to_owned(), + )?; + + let amount_x = Uint128::from(DEPOSIT_AMOUNT); // 25_000_000 satoshi + let amount_y = Uint128::from(DEPOSIT_AMOUNT); // 10_000 silk + + let nb_bins_x = 10; + let nb_bins_y = 10; + + let token_x = extract_contract_info(&deployed_contracts, SSCRT)?; + let token_y = extract_contract_info(&deployed_contracts, SILK)?; + + let tokens_to_mint = vec![(SSCRT, amount_x), (SILK, amount_y)]; + + mint_token_helper( + &mut app, + &deployed_contracts, + &addrs, + addrs.batman().into_string(), + tokens_to_mint.clone(), + )?; + + increase_allowance_helper( + &mut app, + &deployed_contracts, + addrs.batman().into_string(), + lb_pair.lb_pair.contract.address.to_string(), + tokens_to_mint, + )?; + + //Adding liquidity + let liquidity_parameters = liquidity_parameters_generator( + &deployed_contracts, + ACTIVE_ID, + token_x, + token_y, + amount_x, + amount_y, + nb_bins_x, + nb_bins_y, + )?; + + lb_pair::add_liquidity( + &mut app, + addrs.batman().as_str(), + &lb_pair.lb_pair.contract, + liquidity_parameters, + )?; + + lb_pair::reset_rewards_epoch( + &mut app, + addrs.admin().as_str(), + &lb_pair.lb_pair.contract, + Some(RewardsDistributionAlgorithm::VolumeBasedRewards), + None, + )?; + + let (_, bin_reserves_y) = lb_pair::query_bin(&app, &lb_pair.lb_pair.contract, ACTIVE_ID)?; + + let timestamp = Timestamp::from_seconds(app.block_info().time.seconds() + 3); + app.set_time(timestamp); + + //making a swap for token y hence the bin id moves to the right + let (amount_x_in, _amount_y_out_left, _fee) = lb_pair::query_swap_in( + &app, + &lb_pair.lb_pair.contract, + Uint128::from(bin_reserves_y + 1), + true, + )?; + + // mint the tokens + let tokens_to_mint = vec![(SSCRT, amount_x_in)]; + mint_token_helper( + &mut app, + &deployed_contracts, + &addrs, + addrs.batman().into_string(), + tokens_to_mint.clone(), + )?; + // make a swap with amount_x_in + let token_x: &ContractInfo = &extract_contract_info(&deployed_contracts, SSCRT)?; + lb_pair::swap_snip_20( + &mut app, + addrs.batman().as_str(), + &lb_pair.lb_pair.contract, + Some(addrs.batman().to_string()), + token_x, + amount_x_in, + )?; + + let active_id = lb_pair::query_active_id(&app, &lb_pair.lb_pair.contract)?; + + assert_eq!(active_id, ACTIVE_ID - 1); + + lb_pair::calculate_rewards(&mut app, addrs.admin().as_str(), &lb_pair.lb_pair.contract)?; + + let _distribution = lb_pair::query_rewards_distribution(&app, &lb_pair.lb_pair.contract, None)?; + //Eventhough the distribution was changes mid epoch the effects of change will occur after the epoch. + + assert!( + _distribution + .weightages + .iter() + .all(|&x| x == _distribution.weightages[0]) + ); + println!("_distribution {:?}", _distribution); + + //making a swap for token y hence the bin id moves to the right + let timestamp = Timestamp::from_seconds(app.block_info().time.seconds() + 7); + app.set_time(timestamp); + + let (amount_x_in, _amount_y_out_left, _fee) = lb_pair::query_swap_in( + &app, + &lb_pair.lb_pair.contract, + Uint128::from(bin_reserves_y * 5), + true, + )?; + + // mint the tokens + let tokens_to_mint = vec![(SSCRT, amount_x_in)]; + mint_token_helper( + &mut app, + &deployed_contracts, + &addrs, + addrs.batman().into_string(), + tokens_to_mint.clone(), + )?; + // make a swap with amount_x_in + let token_x: &ContractInfo = &extract_contract_info(&deployed_contracts, SSCRT)?; + lb_pair::swap_snip_20( + &mut app, + addrs.batman().as_str(), + &lb_pair.lb_pair.contract, + Some(addrs.batman().to_string()), + token_x, + amount_x_in, + )?; + + let active_id = lb_pair::query_active_id(&app, &lb_pair.lb_pair.contract)?; + + assert_eq!(active_id, ACTIVE_ID - 1 - 5); + + let timestamp = Timestamp::from_seconds(app.block_info().time.seconds() + 43); + app.set_time(timestamp); + + lb_pair::calculate_rewards(&mut app, addrs.admin().as_str(), &lb_pair.lb_pair.contract)?; + + let _distribution = lb_pair::query_rewards_distribution(&app, &lb_pair.lb_pair.contract, None)?; + //Eventhough the distribution was changes mid epoch the effects of change will occur after the epoch. + + assert!( + _distribution + .weightages + .iter() + .any(|&x| x != _distribution.weightages[0]) + ); - println!("distribution: {:?}", distribution); + // println!("_distribution {:?}", _distribution); Ok(()) } diff --git a/contracts/liquidity_book/tests/src/multitests/lb_pair_initial_state.rs b/contracts/liquidity_book/tests/src/multitests/lb_pair_initial_state.rs index 2d78652d..868fc612 100644 --- a/contracts/liquidity_book/tests/src/multitests/lb_pair_initial_state.rs +++ b/contracts/liquidity_book/tests/src/multitests/lb_pair_initial_state.rs @@ -28,7 +28,7 @@ use shade_protocol::{ pub fn lb_pair_setup() -> Result<(App, ContractInfo, DeployedContracts, LBPairInformation), anyhow::Error> { let addrs = init_addrs(); - let (mut app, lb_factory, deployed_contracts) = setup(None)?; + let (mut app, lb_factory, deployed_contracts) = setup(None, None)?; let shd = extract_contract_info(&deployed_contracts, SHADE)?; let sscrt = extract_contract_info(&deployed_contracts, SSCRT)?; diff --git a/contracts/liquidity_book/tests/src/multitests/lb_pair_liquidity.rs b/contracts/liquidity_book/tests/src/multitests/lb_pair_liquidity.rs index 48ed081b..1499f9ad 100644 --- a/contracts/liquidity_book/tests/src/multitests/lb_pair_liquidity.rs +++ b/contracts/liquidity_book/tests/src/multitests/lb_pair_liquidity.rs @@ -41,7 +41,7 @@ pub fn lb_pair_setup() -> Result< anyhow::Error, > { let addrs = init_addrs(); - let (mut app, lb_factory, deployed_contracts) = setup(None)?; + let (mut app, lb_factory, deployed_contracts) = setup(None, None)?; let silk = extract_contract_info(&deployed_contracts, SILK)?; let shade = extract_contract_info(&deployed_contracts, SHADE)?; diff --git a/contracts/liquidity_book/tests/src/multitests/lb_pair_swap.rs b/contracts/liquidity_book/tests/src/multitests/lb_pair_swap.rs index 7f094b8b..5046b546 100644 --- a/contracts/liquidity_book/tests/src/multitests/lb_pair_swap.rs +++ b/contracts/liquidity_book/tests/src/multitests/lb_pair_swap.rs @@ -34,7 +34,7 @@ pub fn lb_pair_setup() -> Result< anyhow::Error, > { let addrs = init_addrs(); - let (mut app, lb_factory, deployed_contracts) = setup(None)?; + let (mut app, lb_factory, deployed_contracts) = setup(None, None)?; let silk = extract_contract_info(&deployed_contracts, SILK)?; let shade = extract_contract_info(&deployed_contracts, SHADE)?; diff --git a/contracts/liquidity_book/tests/src/multitests/lb_router_integration.rs b/contracts/liquidity_book/tests/src/multitests/lb_router_integration.rs index ed3a2501..9ab964f8 100644 --- a/contracts/liquidity_book/tests/src/multitests/lb_router_integration.rs +++ b/contracts/liquidity_book/tests/src/multitests/lb_router_integration.rs @@ -42,7 +42,7 @@ const SWAP_AMOUNT: u128 = 1000; #[test] pub fn router_integration() -> Result<(), anyhow::Error> { let addrs = init_addrs(); - let (mut app, lb_factory, mut deployed_contracts) = setup(None)?; + let (mut app, lb_factory, mut deployed_contracts) = setup(None, None)?; //test the registered tokens diff --git a/contracts/liquidity_book/tests/src/multitests/lb_router_register_tokens.rs b/contracts/liquidity_book/tests/src/multitests/lb_router_register_tokens.rs index 29971331..54d5862a 100644 --- a/contracts/liquidity_book/tests/src/multitests/lb_router_register_tokens.rs +++ b/contracts/liquidity_book/tests/src/multitests/lb_router_register_tokens.rs @@ -12,7 +12,7 @@ use shade_multi_test::interfaces::{ #[test] pub fn router_registered_tokens() -> Result<(), anyhow::Error> { let addrs = init_addrs(); - let (mut app, _lb_factory, mut deployed_contracts) = setup(None)?; + let (mut app, _lb_factory, mut deployed_contracts) = setup(None, None)?; //intro app router::init(&mut app, addrs.admin().as_str(), &mut deployed_contracts)?; diff --git a/contracts/liquidity_book/tests/src/multitests/lb_token.rs b/contracts/liquidity_book/tests/src/multitests/lb_token.rs index da815115..8f1d79e3 100644 --- a/contracts/liquidity_book/tests/src/multitests/lb_token.rs +++ b/contracts/liquidity_book/tests/src/multitests/lb_token.rs @@ -39,7 +39,7 @@ pub fn init_setup() -> Result< anyhow::Error, > { let addrs = init_addrs(); - let (mut app, lb_factory, deployed_contracts) = setup(None)?; + let (mut app, lb_factory, deployed_contracts) = setup(None, None)?; let silk = extract_contract_info(&deployed_contracts, SILK)?; let shade = extract_contract_info(&deployed_contracts, SHADE)?; diff --git a/contracts/liquidity_book/tests/src/multitests/test_helper.rs b/contracts/liquidity_book/tests/src/multitests/test_helper.rs index 739890f2..418c5b6b 100644 --- a/contracts/liquidity_book/tests/src/multitests/test_helper.rs +++ b/contracts/liquidity_book/tests/src/multitests/test_helper.rs @@ -11,7 +11,7 @@ use shade_multi_test::{ }; use shade_protocol::{ lb_libraries::{constants::PRECISION, math::u24::U24, tokens::TokenType}, - liquidity_book::lb_pair::LiquidityParameters, + liquidity_book::lb_pair::{LiquidityParameters, RewardsDistributionAlgorithm}, multi_test::App, utils::{asset::Contract, cycle::parse_utc_datetime, MultiTestable}, }; @@ -127,7 +127,10 @@ pub fn assert_approx_eq_abs(a: Uint256, b: Uint256, delta: Uint256, error_messag } } -pub fn setup(bin_step: Option) -> Result<(App, Contract, DeployedContracts), anyhow::Error> { +pub fn setup( + bin_step: Option, + rewards_distribution_algorithm: Option, +) -> Result<(App, Contract, DeployedContracts), anyhow::Error> { // init snip-20's let mut app = App::default(); let addrs = init_addrs(); @@ -239,7 +242,11 @@ pub fn setup(bin_step: Option) -> Result<(App, Contract, DeployedContracts) addrs.joker(), 0, admin_contract.into(), - 100, + 10, + Some( + rewards_distribution_algorithm + .unwrap_or(RewardsDistributionAlgorithm::TimeBasedRewards), + ), )?; let lb_token_stored_code = app.store_code(LbToken::default().contract()); let lb_pair_stored_code = app.store_code(LbPair::default().contract()); diff --git a/packages/multi_test/src/interfaces/lb_factory.rs b/packages/multi_test/src/interfaces/lb_factory.rs index 0f0bb3c6..1ce72f36 100644 --- a/packages/multi_test/src/interfaces/lb_factory.rs +++ b/packages/multi_test/src/interfaces/lb_factory.rs @@ -6,6 +6,7 @@ use shade_protocol::{ tokens::TokenType, types::{ContractInstantiationInfo, LBPair, LBPairInformation}, }, + liquidity_book::lb_pair::RewardsDistributionAlgorithm, multi_test::App, utils::{ asset::{Contract, RawContract}, @@ -23,6 +24,7 @@ pub fn init( flash_loan_fee: u8, admin_auth: RawContract, total_reward_bins: u32, + rewards_distribution_algorithm: Option, ) -> StdResult { let lb_factory = Contract::from( match (lb_factory::InstantiateMsg { @@ -31,6 +33,8 @@ pub fn init( flash_loan_fee, admin_auth, total_reward_bins, + rewards_distribution_algorithm: rewards_distribution_algorithm + .unwrap_or(RewardsDistributionAlgorithm::TimeBasedRewards), } .test_init( LbFactory::default(), diff --git a/packages/multi_test/src/interfaces/lb_pair.rs b/packages/multi_test/src/interfaces/lb_pair.rs index fd3d9043..5e6ac141 100644 --- a/packages/multi_test/src/interfaces/lb_pair.rs +++ b/packages/multi_test/src/interfaces/lb_pair.rs @@ -6,7 +6,12 @@ use shade_protocol::{ tokens::TokenType, types::{ContractInstantiationInfo, StaticFeeParameters}, }, - liquidity_book::lb_pair::{LiquidityParameters, RemoveLiquidity, RewardsDistribution}, + liquidity_book::lb_pair::{ + LiquidityParameters, + RemoveLiquidity, + RewardsDistribution, + RewardsDistributionAlgorithm, + }, multi_test::App, utils::{ asset::{Contract, RawContract}, @@ -33,6 +38,7 @@ pub fn init( protocol_fee_recipient: Addr, admin_auth: RawContract, total_reward_bins: u32, + rewards_distribution_algorithm: Option, ) -> StdResult { let lb_pair = Contract::from( match (lb_pair::InstantiateMsg { @@ -47,7 +53,9 @@ pub fn init( entropy, protocol_fee_recipient, admin_auth, - total_reward_bins, + total_reward_bins: Some(total_reward_bins), + rewards_distribution_algorithm: rewards_distribution_algorithm + .unwrap_or(RewardsDistributionAlgorithm::TimeBasedRewards), } .test_init( LbPair::default(), @@ -165,6 +173,24 @@ pub fn calculate_rewards(app: &mut App, sender: &str, lb_pair: &ContractInfo) -> } } +pub fn reset_rewards_epoch( + app: &mut App, + sender: &str, + lb_pair: &ContractInfo, + distribution: Option, + base_rewards_bins: Option, +) -> StdResult<()> { + match (lb_pair::ExecuteMsg::ResetRewardsConfig { + distribution, + base_rewards_bins, + } + .test_exec(lb_pair, app, Addr::unchecked(sender), &[])) + { + Ok(_) => Ok(()), + Err(e) => return Err(StdError::generic_err(e.root_cause().to_string())), + } +} + pub fn set_static_fee_parameters( app: &mut App, sender: &str, diff --git a/packages/shade_protocol/src/contract_interfaces/liquidity_book/lb_factory.rs b/packages/shade_protocol/src/contract_interfaces/liquidity_book/lb_factory.rs index 15d742ef..e1599593 100644 --- a/packages/shade_protocol/src/contract_interfaces/liquidity_book/lb_factory.rs +++ b/packages/shade_protocol/src/contract_interfaces/liquidity_book/lb_factory.rs @@ -1,4 +1,4 @@ -use super::lb_pair; +use super::lb_pair::{self, RewardsDistributionAlgorithm}; use crate::utils::{ asset::RawContract, liquidity_book::{ @@ -19,6 +19,7 @@ pub struct InstantiateMsg { pub fee_recipient: Addr, pub flash_loan_fee: u8, pub total_reward_bins: u32, + pub rewards_distribution_algorithm: RewardsDistributionAlgorithm, } impl InstantiateCallback for InstantiateMsg { const BLOCK_SIZE: usize = 256; diff --git a/packages/shade_protocol/src/contract_interfaces/liquidity_book/lb_pair.rs b/packages/shade_protocol/src/contract_interfaces/liquidity_book/lb_pair.rs index a5fff4fc..61d8d0d2 100644 --- a/packages/shade_protocol/src/contract_interfaces/liquidity_book/lb_pair.rs +++ b/packages/shade_protocol/src/contract_interfaces/liquidity_book/lb_pair.rs @@ -41,7 +41,8 @@ pub struct InstantiateMsg { pub entropy: String, pub protocol_fee_recipient: Addr, pub admin_auth: RawContract, - pub total_reward_bins: u32, + pub total_reward_bins: Option, + pub rewards_distribution_algorithm: RewardsDistributionAlgorithm, } impl InstantiateCallback for InstantiateMsg { @@ -88,14 +89,15 @@ pub enum ExecuteMsg { }, ForceDecay {}, CalculateRewards {}, - ResetRewardsEpoch { + ResetRewardsConfig { distribution: Option, + base_rewards_bins: Option, }, } #[cw_serde] pub enum RewardsDistributionAlgorithm { - BaseRewards, + TimeBasedRewards, VolumeBasedRewards, } diff --git a/packages/shade_protocol/src/utils/liquidity_book/mod.rs b/packages/shade_protocol/src/utils/liquidity_book/mod.rs index 0945be0c..f081d336 100644 --- a/packages/shade_protocol/src/utils/liquidity_book/mod.rs +++ b/packages/shade_protocol/src/utils/liquidity_book/mod.rs @@ -15,15 +15,17 @@ pub mod transfer; pub mod types; pub mod viewing_keys; -pub fn ceil_div(a: Uint256, b: Uint256) -> Uint256 { +pub fn approx_div(a: Uint256, b: Uint256) -> Uint256 { if b == Uint256::zero() { panic!("Division by zero"); } let div = a / b; let rem = a % b; - if rem == Uint256::zero() { - div - } else { + if rem >= b / Uint256::from(2u128) { + // If so, we add one to the division result div + Uint256::one() + } else { + // If not, we return the division result as it is + div } } From fcf7ce36a7867da8297344dfed13ed421dc0084d Mon Sep 17 00:00:00 2001 From: itshaseebsaeed Date: Fri, 17 Nov 2023 18:53:08 +0500 Subject: [PATCH 3/5] adding changes --- .../tests/integration/query.rs | 2 + contracts/liquidity_book/lb_pair/src/lib.rs | 2 - .../lb_pair/src/unittest/handle_test.rs | 369 -------------- .../lb_pair/src/unittest/mod.rs | 5 - .../lb_pair/src/unittest/query_test.rs | 0 .../lb_pair/src/unittest/test_helper.rs | 471 ------------------ 6 files changed, 2 insertions(+), 847 deletions(-) delete mode 100644 contracts/liquidity_book/lb_pair/src/unittest/handle_test.rs delete mode 100644 contracts/liquidity_book/lb_pair/src/unittest/mod.rs delete mode 100644 contracts/liquidity_book/lb_pair/src/unittest/query_test.rs delete mode 100644 contracts/liquidity_book/lb_pair/src/unittest/test_helper.rs diff --git a/contracts/dao/treasury_manager/tests/integration/query.rs b/contracts/dao/treasury_manager/tests/integration/query.rs index 0531c588..367953a3 100644 --- a/contracts/dao/treasury_manager/tests/integration/query.rs +++ b/contracts/dao/treasury_manager/tests/integration/query.rs @@ -26,6 +26,7 @@ pub fn query() { .timestamp() as u64, ), chain_id: "chain_id".to_string(), + random: None, }); init_dao( &mut app, @@ -366,6 +367,7 @@ pub fn query() { .timestamp() as u64, ), chain_id: "chain_id".to_string(), + random: None, }); update_dao(&mut app, "admin", &contracts, "SSCRT", 1).unwrap(); update_dao(&mut app, "admin", &contracts, "SSCRT", 1).unwrap(); diff --git a/contracts/liquidity_book/lb_pair/src/lib.rs b/contracts/liquidity_book/lb_pair/src/lib.rs index c21dbade..fcd052a6 100644 --- a/contracts/liquidity_book/lb_pair/src/lib.rs +++ b/contracts/liquidity_book/lb_pair/src/lib.rs @@ -3,5 +3,3 @@ mod prelude; pub mod contract; pub mod state; -#[cfg(test)] -pub mod unittest; diff --git a/contracts/liquidity_book/lb_pair/src/unittest/handle_test.rs b/contracts/liquidity_book/lb_pair/src/unittest/handle_test.rs deleted file mode 100644 index 7a7cfa41..00000000 --- a/contracts/liquidity_book/lb_pair/src/unittest/handle_test.rs +++ /dev/null @@ -1,369 +0,0 @@ -use core::result::Result::Ok; -use std::str::FromStr; - -use cosmwasm_std::{Uint128, Uint256}; -use shade_multi_test::interfaces::{lb_pair, lb_token, snip20}; -use shade_protocol::contract_interfaces::liquidity_book::lb_token::QueryAnswer; - -use crate::unittest::test_helper::{ - assert_approx_eq_rel, - init_addrs, - init_lb_pair, - liquidity_parameters_helper, - lp_tokens_tempate_for_100_sscrts, - mint_increase_allowance_helper, - remove_liquidity_parameters_helper, -}; - -/*********************************************** - * Workflow of Init * - *********************************************** - * - * 1. lb-pair ---initializes---> lb-token - * - * 2. lb-pair ---register_receive + set_viewing_key---> Snip-20 (x2) - * - ***********************************************/ -#[test] -fn test_init() -> Result<(), anyhow::Error> { - // init snip-20 x2 - - let (app, lb_pair_contract_info, deployed_contracts) = init_lb_pair()?; - - let lb_token_info = lb_pair::lb_token_query(&app, &lb_pair_contract_info.clone().into())?; - - let contract_info_lb_token = lb_token::query_contract_info(&app, &lb_token_info)?; - - match contract_info_lb_token { - QueryAnswer::TokenContractInfo { curators, .. } => { - assert_eq!(curators[0], lb_pair_contract_info.address) - } - _ => (), - } - - let result = snip20::balance_query( - &app, - lb_pair_contract_info.address.as_str(), - &deployed_contracts, - "SHD", - "viewing_key".to_string(), - )?; - - assert_eq!(result, Uint128::zero()); - - Ok(()) -} - -/*********************************************** - * Workflow of Init * - *********************************************** - * - * 1. lb-pair ---transfer's itself---> Snip-20 - * - * 2. lb-pair ---mint tokens---> lb-token - * - ***********************************************/ -#[test] -fn test_add_liquidity() -> Result<(), anyhow::Error> { - let addrs = init_addrs(); - let (mut app, lb_pair_contract_info, deployed_contracts) = init_lb_pair()?; - let lb_token_info = lb_pair::lb_token_query(&app, &lb_pair_contract_info.clone().into())?; - - //add minter -> mint tokens -> set_vk -> check balance - mint_increase_allowance_helper( - &mut app, - &deployed_contracts, - &addrs, - &lb_pair_contract_info, - )?; - - //add_liquidity - let liquidity_parameters = liquidity_parameters_helper( - &deployed_contracts, - Uint128::from(100 * 1000_000u128), - Uint128::from(100 * 1000_000u128), - )?; - lb_pair::add_liquidity( - &mut app, - addrs.user1().as_str(), - &lb_pair_contract_info.clone().into(), - liquidity_parameters.clone(), - )?; - - // query lb-pair SHD balance for token_minted - let balance = snip20::balance_query( - &mut app, - lb_pair_contract_info.address.as_str(), - &deployed_contracts, - "SHD", - "viewing_key".to_owned(), - )?; - assert_eq!(balance, Uint128::from(99999996u128)); - - // query balance SCRT for token_minted - let balance = snip20::balance_query( - &mut app, - lb_pair_contract_info.address.as_str(), - &deployed_contracts, - "SSCRT", - "viewing_key".to_owned(), - )?; - assert_eq!(balance, Uint128::from(99999996u128)); - - //Checking share/tokens minted after add_liq - //p.s: values are calculated and are assumed to be correct - let log_shares_array = lp_tokens_tempate_for_100_sscrts()?; - - let mut i = 0; - for id in log_shares_array { - let liquidity = lb_token::query_id_balance(&app, &lb_token_info, id.0.to_string())?; - - match liquidity { - shade_protocol::liquidity_book::lb_token::QueryAnswer::IdTotalBalance { amount } => { - assert_eq!(&amount, id.1) - } - _ => (), - } - - let bin_reserves = lb_pair::bin_query(&app, &lb_pair_contract_info.clone().into(), id.0)?; - - assert_eq!( - bin_reserves.0, - Uint128::from( - (Uint128::from(100 * 1000000u128).u128() - * liquidity_parameters.distribution_x[i] as u128) - ) - .multiply_ratio(1u128, 1e18 as u128) - .u128() - ); - - i += 1; - } - Ok(()) -} - -/*********************************************** - * Workflow of Init * - *********************************************** - * - * 1. lb-pair ---transfer's itself---> Snip-20 - * - * 2. lb-pair ---mint tokens---> lb-token - * - ***********************************************/ -#[test] -fn test_remove_liquidity() -> Result<(), anyhow::Error> { - let addrs = init_addrs(); - let (mut app, lb_pair_contract_info, deployed_contracts) = init_lb_pair()?; - let lb_token_info = lb_pair::lb_token_query(&app, &lb_pair_contract_info.clone().into())?; - - //add minter -> mint tokens -> set_vk -> check balance - mint_increase_allowance_helper( - &mut app, - &deployed_contracts, - &addrs, - &lb_pair_contract_info, - )?; - - // query lb-pair balance - let balance = snip20::balance_query( - &mut app, - lb_pair_contract_info.address.as_str(), - &deployed_contracts, - "SHD", - "viewing_key".to_owned(), - )?; - assert_eq!(balance, Uint128::from(0u128)); - - //add_liquidity - let liquidity_parameters = liquidity_parameters_helper( - &deployed_contracts, - Uint128::from(100 * 1000000u128), - Uint128::from(100 * 1000000u128), - )?; - lb_pair::add_liquidity( - &mut app, - addrs.user1().as_str(), - &lb_pair_contract_info.clone().into(), - liquidity_parameters.clone(), - )?; - - // query lb-pair balance and user balance - let balance = snip20::balance_query( - &mut app, - lb_pair_contract_info.address.as_str(), - &deployed_contracts, - "SHD", - "viewing_key".to_owned(), - )?; - assert_eq!(balance, Uint128::from(99999996u128)); - - let balance = snip20::balance_query( - &mut app, - addrs.user1().as_str(), - &deployed_contracts, - "SHD", - "viewing_key".to_owned(), - )?; - assert_eq!(balance, Uint128::from(900_000_004u128)); - - //removes parital liquidity. - let percentage_removed = 50; - let (remove_liquidity_parameters, remove_liq_log) = - remove_liquidity_parameters_helper(&deployed_contracts, percentage_removed)?; - //removing 50% of the liquidity. - lb_pair::remove_liquidity( - &mut app, - addrs.user1().as_str(), - &lb_pair_contract_info.clone().into(), - remove_liquidity_parameters.clone(), - )?; - - // query lb-pair balance and user balance - let balance = snip20::balance_query( - &mut app, - lb_pair_contract_info.address.as_str(), - &deployed_contracts, - "SHD", - "viewing_key".to_owned(), - )?; - assert_approx_eq_rel(balance.u128(), 50_000_000u128, 100u128); - - let balance = snip20::balance_query( - &mut app, - addrs.user1().as_str(), - &deployed_contracts, - "SHD", - "viewing_key".to_owned(), - )?; - assert_approx_eq_rel(balance.u128(), 950_000_000u128, 100u128); - - //check user balance and lb-pair balance for SHD - // start: user-balance = 1000*10^6 ,lb-pair-balance = 0 - // add_liquidity: user-balance = 9000*10^6 ,lb-pair-balance = 100*10^6 - - let mut i = 0; - for (id, amt) in remove_liq_log { - let liquidity = lb_token::query_id_balance(&app, &lb_token_info, id.to_string())?; - - match liquidity { - shade_protocol::liquidity_book::lb_token::QueryAnswer::IdTotalBalance { amount } => { - assert_eq!(&amount, amt) - } - _ => (), - } - - //get bin_reserves - let bin_reserves = lb_pair::bin_query(&app, &lb_pair_contract_info.clone().into(), id)?; - - assert_eq!( - bin_reserves.0, - Uint128::from( - Uint128::from((100u8 - percentage_removed) as u128 * 1000000u128).u128() - * liquidity_parameters.distribution_x[i] as u128 - ) - .multiply_ratio(1u128, 1e18 as u128) - .u128() - ); - - i += 1; - } - - //remove all: - let (remove_liquidity_parameters, remove_liq_log) = - remove_liquidity_parameters_helper(&deployed_contracts, percentage_removed)?; - //removing 50% of the liquidity. - lb_pair::remove_liquidity( - &mut app, - addrs.user1().as_str(), - &lb_pair_contract_info.clone().into(), - remove_liquidity_parameters.clone(), - )?; - - // query lb-pair balance and user balance - let balance = snip20::balance_query( - &mut app, - lb_pair_contract_info.address.as_str(), - &deployed_contracts, - "SHD", - "viewing_key".to_owned(), - )?; - assert_approx_eq_rel(0u128, balance.u128(), 100u128); - - let balance = snip20::balance_query( - &mut app, - addrs.user1().as_str(), - &deployed_contracts, - "SHD", - "viewing_key".to_owned(), - )?; - assert_approx_eq_rel(balance.u128(), 1000_000_000u128, 100u128); - - Ok(()) -} - -#[test] -fn test_swap_liquidity() -> Result<(), anyhow::Error> { - let addrs = init_addrs(); - let (mut app, lb_pair_contract_info, deployed_contracts) = init_lb_pair()?; - let lb_token_info = lb_pair::lb_token_query(&app, &lb_pair_contract_info.clone().into())?; - - //add minter -> mint tokens -> set_vk -> check balance - mint_increase_allowance_helper( - &mut app, - &deployed_contracts, - &addrs, - &lb_pair_contract_info, - )?; - - // query lb-pair balance - let balance = snip20::balance_query( - &mut app, - lb_pair_contract_info.address.as_str(), - &deployed_contracts, - "SHD", - "viewing_key".to_owned(), - )?; - assert_eq!(balance, Uint128::from(0u128)); - - //add_liquidity 1:1 for SSCRT - SHD - let liquidity_parameters = liquidity_parameters_helper( - &deployed_contracts, - Uint128::from(100 * 1000000u128), - Uint128::from(100 * 1000000u128), - )?; - lb_pair::add_liquidity( - &mut app, - addrs.user1().as_str(), - &lb_pair_contract_info.clone().into(), - liquidity_parameters.clone(), - )?; - - let (amount_in, amount_out_left, fee) = lb_pair::swap_in_query( - &app, - &lb_pair_contract_info.clone().into(), - Uint128::from(10_000_000u128), - true, - )?; - - println!( - "amount_in {:?}, amount_out_left {:?}, fee {:?}", - amount_in, amount_out_left, fee - ); - - let (amount_out, amount_in_left, fee) = lb_pair::swap_out_query( - &app, - &lb_pair_contract_info.clone().into(), - Uint128::from(10_000_000u128), - true, - )?; - - println!( - "amount_out {:?}, amount_in_left {:?}, fee {:?}", - amount_out, amount_in_left, fee - ); - - //make swap and check balances - - Ok(()) -} diff --git a/contracts/liquidity_book/lb_pair/src/unittest/mod.rs b/contracts/liquidity_book/lb_pair/src/unittest/mod.rs deleted file mode 100644 index 1d579ef3..00000000 --- a/contracts/liquidity_book/lb_pair/src/unittest/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -#[cfg(test)] -pub mod handle_test; -#[cfg(test)] -pub mod query_test; -pub mod test_helper; diff --git a/contracts/liquidity_book/lb_pair/src/unittest/query_test.rs b/contracts/liquidity_book/lb_pair/src/unittest/query_test.rs deleted file mode 100644 index e69de29b..00000000 diff --git a/contracts/liquidity_book/lb_pair/src/unittest/test_helper.rs b/contracts/liquidity_book/lb_pair/src/unittest/test_helper.rs deleted file mode 100644 index f4fcfd7d..00000000 --- a/contracts/liquidity_book/lb_pair/src/unittest/test_helper.rs +++ /dev/null @@ -1,471 +0,0 @@ -use core::result::Result::Ok; -use std::{any::Any, str::FromStr}; - -use cosmwasm_std::{ - Addr, - BlockInfo, - ContractInfo, - StdError, - StdResult, - Timestamp, - Uint128, - Uint256, -}; -use serde::de::Error; -use shade_multi_test::{ - interfaces::{ - lb_pair, - snip20, - utils::{DeployedContracts, SupportedContracts}, - }, - multi::{admin::init_admin_auth, lb_token::LbToken}, -}; -use shade_protocol::{ - lb_libraries::{ - tokens::TokenType, - types::{ContractInstantiationInfo, StaticFeeParameters}, - }, - liquidity_book::lb_pair::{LiquidityParameters, RemoveLiquidity}, - multi_test::App, - utils::{asset::Contract, cycle::parse_utc_datetime, MultiTestable}, -}; - -use crate::error; - -pub struct Addrs { - addrs: Vec, - hashes: Vec, -} - -impl Addrs { - pub fn admin(&self) -> Addr { - self.addrs[0].clone() - } - - pub fn user1(&self) -> Addr { - self.addrs[1].clone() - } - - pub fn user2(&self) -> Addr { - self.addrs[2].clone() - } - - pub fn user3(&self) -> Addr { - self.addrs[3].clone() - } - - pub fn all(&self) -> Vec { - self.addrs.clone() - } - - pub fn a_hash(&self) -> String { - self.hashes[0].clone() - } - - pub fn b_hash(&self) -> String { - self.hashes[1].clone() - } - - pub fn c_hash(&self) -> String { - self.hashes[2].clone() - } - - pub fn _d_hash(&self) -> String { - self.hashes[3].clone() - } -} - -/// inits 3 addresses -pub fn init_addrs() -> Addrs { - let addr_strs = vec!["addr0", "addr1", "addr2", "addr3"]; - let hashes = vec![ - "addr0_hash".to_string(), - "addr1_hash".to_string(), - "addr2_hash".to_string(), - "addr3_hash".to_string(), - ]; - let mut addrs: Vec = vec![]; - for addr in addr_strs { - addrs.push(Addr::unchecked(addr.to_string())); - } - Addrs { addrs, hashes } -} - -pub fn init_lb_pair() -> Result<(App, Contract, DeployedContracts), anyhow::Error> { - let mut app = App::default(); - let addrs = init_addrs(); - let mut deployed_contracts = DeployedContracts::new(); - app.set_block(BlockInfo { - height: 1, - time: Timestamp::from_seconds( - parse_utc_datetime(&"1995-11-13T00:00:00.00Z".to_string()) - .unwrap() - .timestamp() as u64, - ), - chain_id: "chain_id".to_string(), - random: None, - }); - snip20::init( - &mut app, - addrs.admin().as_str(), - &mut deployed_contracts, - "SecretScrt", - "SSCRT", - 6, - Some(shade_protocol::snip20::InitConfig { - public_total_supply: Some(true), - enable_deposit: None, - enable_redeem: None, - enable_mint: Some(true), - enable_burn: None, - enable_transfer: Some(true), - }), - ) - .unwrap(); - let first_contract = deployed_contracts.iter().next().unwrap().1.clone(); - snip20::init( - &mut app, - addrs.admin().as_str(), - &mut deployed_contracts, - "Shade", - "SHD", - 8, - Some(shade_protocol::snip20::InitConfig { - public_total_supply: Some(true), - enable_deposit: None, - enable_redeem: None, - enable_mint: Some(true), - enable_burn: None, - enable_transfer: Some(true), - }), - ) - .unwrap(); - let second_contract = deployed_contracts.iter().next().unwrap().1.clone(); - let lb_token_stored_code = app.store_code(LbToken::default().contract()); - let admin_contract = init_admin_auth(&mut app, &addrs.admin()); - - let lb_pair = lb_pair::init( - &mut app, - addrs.admin().as_str(), - ContractInfo { - address: Addr::unchecked("factory_address"), - code_hash: "factory_code_hash".to_string(), - }, - TokenType::CustomToken { - contract_addr: first_contract.address, - token_code_hash: first_contract.code_hash, - }, - TokenType::CustomToken { - contract_addr: second_contract.address, - token_code_hash: second_contract.code_hash, - }, - 10, - StaticFeeParameters { - base_factor: 5000, - filter_period: 30, - decay_period: 600, - reduction_factor: 5000, - variable_fee_control: 40000, - protocol_share: 1000, - max_volatility_accumulator: 350000, - }, - 8388608, - ContractInstantiationInfo { - id: lb_token_stored_code.code_id, - code_hash: lb_token_stored_code.code_hash, - }, - "viewing_key".to_string(), - String::new(), - String::new(), - addrs.admin(), - admin_contract.into(), - 100, - )?; - - Ok((app, lb_pair, deployed_contracts)) -} - -pub fn extract_error_msg(error: &StdResult) -> String { - match error { - Ok(_response) => panic!("Expected error, but had Ok response"), - Err(err) => match err { - StdError::GenericErr { msg, .. } => msg.to_string(), - _ => panic!("Unexpected error result {:?}", err), - }, - } -} - -pub fn liquidity_parameters_helper( - deployed_contracts: &DeployedContracts, - amount_x: Uint128, - amount_y: Uint128, -) -> StdResult { - let array_x: Vec = vec![ - 0.16666666, 0.16666666, 0.16666666, 0.16666666, 0.16666666, 0.16666666, 0.0, 0.0, 0.0, 0.0, - 0.0, - ]; - - let distribution_y: Vec = array_x - .clone() - .into_iter() - .map(|el| (el * 1e18) as u64) - .collect(); - - let array_y: Vec = vec![ - 0.0, 0.0, 0.0, 0.0, 0.0, 0.16666666, 0.16666666, 0.16666666, 0.16666666, 0.16666666, - 0.16666666, - ]; - let distribution_x: Vec = array_y - .clone() - .into_iter() - .map(|el| (el * 1e18) as u64) - .collect(); - - let snip20_1 = deployed_contracts - .get(&SupportedContracts::Snip20("SSCRT".to_string())) - .unwrap() - .clone(); - let snip20_2 = deployed_contracts - .get(&SupportedContracts::Snip20("SHD".to_string())) - .unwrap() - .clone(); - - let liquidity_parameters = LiquidityParameters { - token_x: TokenType::CustomToken { - contract_addr: snip20_1.address, - token_code_hash: snip20_1.code_hash, - }, - token_y: TokenType::CustomToken { - contract_addr: snip20_2.address, - token_code_hash: snip20_2.code_hash, - }, - bin_step: 10, - amount_x, - amount_y, - amount_x_min: amount_x.multiply_ratio(90u128, 100u128), - amount_y_min: amount_y.multiply_ratio(90u128, 100u128), - active_id_desired: 8388608, - id_slippage: 15, - delta_ids: [-5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5].into(), - distribution_x, - distribution_y, - deadline: 99999999999, - }; - - Ok(liquidity_parameters) -} - -pub fn remove_liquidity_parameters_helper( - deployed_contracts: &DeployedContracts, - percentage: u8, -) -> StdResult<(RemoveLiquidity, Vec<(u32, Uint256)>)> { - let amount_x = Uint128::from(100_000_000u128); - let amount_y = Uint128::from(100_000_000u128); - - let log_shares_array = lp_tokens_tempate_for_100_sscrts()?; - - // Assuming log_shares_array is your original array - let mut divided_log_shares_array = Vec::new(); - - for (id, amount) in &log_shares_array { - let divided_amount = amount.multiply_ratio(percentage, 100u8); // Assuming Uint256 supports division - divided_log_shares_array.push((id.clone(), divided_amount)); - } - - // Now divided_log_shares_array contains the divided amounts - - // Separate IDs and amounts into two vectors - let ids: Vec = divided_log_shares_array.iter().map(|&(id, _)| id).collect(); - let amounts: Vec = divided_log_shares_array - .iter() - .map(|&(_, amount)| amount) - .collect(); - - let snip20_1 = deployed_contracts - .get(&SupportedContracts::Snip20("SSCRT".to_string())) - .unwrap() - .clone(); - let snip20_2 = deployed_contracts - .get(&SupportedContracts::Snip20("SHD".to_string())) - .unwrap() - .clone(); - - let liquidity_parameters = RemoveLiquidity { - token_x: TokenType::CustomToken { - contract_addr: snip20_1.address, - token_code_hash: snip20_1.code_hash, - }, - token_y: TokenType::CustomToken { - contract_addr: snip20_2.address, - token_code_hash: snip20_2.code_hash, - }, - bin_step: 10, - amount_x_min: amount_x.multiply_ratio(percentage - 1, 100u128), - amount_y_min: amount_y.multiply_ratio(percentage - 1, 100u128), - ids, - amounts, - deadline: 99999999999, - }; - - Ok((liquidity_parameters, divided_log_shares_array)) -} - -pub fn mint_increase_allowance_helper( - mut app: &mut App, - deployed_contracts: &DeployedContracts, - addrs: &Addrs, - lb_pair_contract_info: &Contract, -) -> StdResult<()> { - //adding minters and minting - - snip20::add_minters_exec( - &mut app, - addrs.admin().as_str(), - &deployed_contracts, - "SHD", - vec![addrs.admin().to_string()], - )?; - - // mint token for user1 - snip20::mint_exec( - &mut app, - addrs.admin().as_str(), - &deployed_contracts, - "SHD", - &vec![], - addrs.user1().into_string(), - Uint128::from(1_000_000_000u128), - )?; - - snip20::add_minters_exec( - &mut app, - addrs.admin().as_str(), - &deployed_contracts, - "SSCRT", - vec![addrs.admin().to_string()], - )?; - - // mint token for user1 - snip20::mint_exec( - &mut app, - addrs.admin().as_str(), - &deployed_contracts, - "SSCRT", - &vec![], - addrs.user1().into_string(), - Uint128::from(1_000_000_000u128), - )?; - - snip20::set_viewing_key_exec( - &mut app, - addrs.user1().as_str(), - &deployed_contracts, - "SHD", - "viewing_key".to_owned(), - )?; - - // query balance for token_minted - let balance = snip20::balance_query( - &mut app, - addrs.user1().as_str(), - &deployed_contracts, - "SHD", - "viewing_key".to_owned(), - )?; - - assert_eq!(balance, Uint128::from(1_000_000_000u128)); - - // setting allowance to snip20's - snip20::set_allowance_exec( - &mut app, - addrs.user1().as_str(), - &deployed_contracts, - "SHD", - lb_pair_contract_info.address.to_string(), - Uint128::MAX, - None, - )?; - snip20::set_allowance_exec( - &mut app, - addrs.user1().as_str(), - &deployed_contracts, - "SSCRT", - lb_pair_contract_info.address.to_string(), - Uint128::MAX, - None, - )?; - - Ok(()) -} - -pub fn lp_tokens_tempate_for_100_sscrts() -> StdResult<[(u32, Uint256); 11]> { - let log_shares_array: [(u32, Uint256); 11] = [ - ( - 8388603, - Uint256::from_str("5671372555160729777097267814946398569754525696")?, - ), - ( - 8388604, - Uint256::from_str("5671372555160729777097267814946398569754525696")?, - ), - ( - 8388605, - Uint256::from_str("5671372555160729777097267814946398569754525696")?, - ), - ( - 8388606, - Uint256::from_str("5671372555160729777097267814946398569754525696")?, - ), - ( - 8388607, - Uint256::from_str("5671372555160729777097267814946398569754525696")?, - ), - ( - 8388608, - Uint256::from_str("11342745110321459554194535629892797139509051392")?, - ), - ( - 8388609, - Uint256::from_str("5677043927715890506874365082761344968316680222")?, - ), - ( - 8388610, - Uint256::from_str("5682720971643606397381239447844106313273880236")?, - ), - ( - 8388611, - Uint256::from_str("5688403692615250003778620687291950419593054116")?, - ), - ( - 8388612, - Uint256::from_str("5694092096307865253782399307979242370015547170")?, - ), - ( - 8388613, - Uint256::from_str("5699786188404173119036181707287221612381479384")?, - ), - ]; - return Ok(log_shares_array); -} - -pub fn assert_approx_eq_rel(a: u128, b: u128, max_value_delta: u128) { - // If b is zero, a must also be zero. - if b == 0 { - assert_eq!(a, b, "Expected zero but got {}", a); - return; - } - - // Calculate the percent difference - let delta = if a > b { a - b } else { b - a }; - if delta > max_value_delta { - // Log the error (you could replace these println! statements with actual logging) - println!("Error: a ~= b not satisfied [uint]"); - println!(" Expected: {}", b); - println!(" Actual: {}", a); - println!(" Max Delta: {}", max_value_delta); - println!(" Delta: {}", delta); - - // Fail the assertion (you can replace this with custom error handling if desired) - panic!("Approximate equality check failed."); - } -} From 27509120486d67a407dfb821ebc4008263de2f69 Mon Sep 17 00:00:00 2001 From: itshaseebsaeed Date: Fri, 17 Nov 2023 21:19:59 +0500 Subject: [PATCH 4/5] fixed a test --- .../tests/src/multitests/lb_factory.rs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/contracts/liquidity_book/tests/src/multitests/lb_factory.rs b/contracts/liquidity_book/tests/src/multitests/lb_factory.rs index dfa2099d..c6b07ddd 100644 --- a/contracts/liquidity_book/tests/src/multitests/lb_factory.rs +++ b/contracts/liquidity_book/tests/src/multitests/lb_factory.rs @@ -948,8 +948,8 @@ pub fn test_add_quote_asset() -> Result<(), anyhow::Error> { &mut app, addrs.admin().as_str(), &mut deployed_contracts, - "sBTC", - "SBTC", + "SOMOS", + "SOMOS", 8, Some(shade_protocol::snip20::InitConfig { public_total_supply: Some(true), @@ -961,8 +961,8 @@ pub fn test_add_quote_asset() -> Result<(), anyhow::Error> { }), ) .unwrap(); - let sbtc = extract_contract_info(&deployed_contracts, "SBTC")?; - let new_token = token_type_snip20_generator(&sbtc)?; + let sosmo = extract_contract_info(&deployed_contracts, "SOMOS")?; + let new_token = token_type_snip20_generator(&sosmo)?; // Check if the new token is a quote asset let is_quote_asset = lb_factory::query_is_quote_asset(&mut app, &lb_factory.clone().into(), new_token.clone())?; @@ -988,13 +988,15 @@ pub fn test_add_quote_asset() -> Result<(), anyhow::Error> { num_quote_assets_before + 1, "test_add_quote_asset::3" ); + assert_eq!(num_quote_assets_after, 6, "test_add_quote_asset::4"); + assert_eq!(num_quote_assets_before, 5, "test_add_quote_asset::5"); let last_quote_asset = lb_factory::query_quote_asset_at_index( &mut app, &lb_factory.clone().into(), num_quote_assets_before, )?; - assert_eq!(last_quote_asset, new_token, "test_add_quote_asset::4"); + assert_eq!(last_quote_asset, new_token, "test_add_quote_asset::6"); // Try to add the same asset when not the owner let err = lb_factory::add_quote_asset( From 6173a25536b4ccecbfd02d7ea6c70988c61b1897 Mon Sep 17 00:00:00 2001 From: itshaseebsaeed Date: Thu, 30 Nov 2023 06:17:22 +0500 Subject: [PATCH 5/5] Unit-tests up and running --- .../liquidity_book/lb_factory/src/contract.rs | 4 +- .../liquidity_book/lb_pair/src/contract.rs | 43 +- contracts/liquidity_book/lb_pair/src/state.rs | 8 +- .../tests/src/multitests/lb_pair_fees.rs | 4 +- .../lb_libraries/math/u256x256_math.rs | 30 +- packages/shadeswap_shared/Cargo.toml | 34 - packages/shadeswap_shared/src/amm_pair.rs | 53 -- packages/shadeswap_shared/src/core/admin.rs | 37 - packages/shadeswap_shared/src/core/display.rs | 8 - .../shadeswap_shared/src/core/token_type.rs | 15 - packages/shadeswap_shared/src/helpers.rs | 47 -- packages/shadeswap_shared/src/lib.rs | 20 - packages/shadeswap_shared/src/msg/mod.rs | 594 ---------------- packages/shadeswap_shared/src/msg/staking.rs | 668 ------------------ 14 files changed, 51 insertions(+), 1514 deletions(-) delete mode 100644 packages/shadeswap_shared/Cargo.toml delete mode 100644 packages/shadeswap_shared/src/amm_pair.rs delete mode 100644 packages/shadeswap_shared/src/core/admin.rs delete mode 100644 packages/shadeswap_shared/src/core/display.rs delete mode 100644 packages/shadeswap_shared/src/core/token_type.rs delete mode 100644 packages/shadeswap_shared/src/helpers.rs delete mode 100644 packages/shadeswap_shared/src/lib.rs delete mode 100644 packages/shadeswap_shared/src/msg/mod.rs delete mode 100644 packages/shadeswap_shared/src/msg/staking.rs diff --git a/contracts/liquidity_book/lb_factory/src/contract.rs b/contracts/liquidity_book/lb_factory/src/contract.rs index c39f7d62..d57e2eb6 100644 --- a/contracts/liquidity_book/lb_factory/src/contract.rs +++ b/contracts/liquidity_book/lb_factory/src/contract.rs @@ -378,8 +378,8 @@ fn try_create_lb_pair( entropy, protocol_fee_recipient: config.fee_recipient, admin_auth: config.admin_auth.into(), - total_reward_bins: Some(state.total_reward_bins), - rewards_distribution_algorithm: state.rewards_distribution_algorithm, + total_reward_bins: Some(config.total_reward_bins), + rewards_distribution_algorithm: config.rewards_distribution_algorithm, })?, code_hash: config.lb_pair_implementation.code_hash.clone(), funds: vec![], diff --git a/contracts/liquidity_book/lb_pair/src/contract.rs b/contracts/liquidity_book/lb_pair/src/contract.rs index f981646c..82ebe4c7 100644 --- a/contracts/liquidity_book/lb_pair/src/contract.rs +++ b/contracts/liquidity_book/lb_pair/src/contract.rs @@ -29,11 +29,21 @@ use shade_protocol::{ Uint512, WasmMsg, }, - contract_interfaces::liquidity_book::{lb_pair::*, lb_token}, + contract_interfaces::{ + liquidity_book::{lb_pair::*, lb_token}, + swap::{ + amm_pair::{ + FeeInfo, + QueryMsgResponse::{GetPairInfo, SwapSimulation}, + }, + core::{Fee, TokenPair, TokenType}, + router::ExecuteMsgResponse, + }, + }, lb_libraries::{ approx_div, bin_helper::BinHelper, - constants::{MAX_FEE, SCALE_OFFSET}, + constants::{BASIS_POINT_MAX, MAX_FEE, SCALE_OFFSET}, fee_helper::FeeHelper, lb_token::state_structs::{LbPair, TokenAmount, TokenIdBalance}, math::{ @@ -44,22 +54,19 @@ use shade_protocol::{ u24::U24, u256x256_math::U256x256Math, uint256_to_u256::{ConvertU256, ConvertUint256}, - BASIS_POINT_MAX, }, oracle_helper::{Oracle, MAX_SAMPLE_LIFETIME}, pair_parameter_helper::PairParameters, price_helper::PriceHelper, - types::{Bytes32, MintArrays}, + types::{self, Bytes32, LBPairInformation, MintArrays}, viewing_keys::{register_receive, set_viewing_key_msg, ViewingKey}, }, snip20, utils::pad_handle_result, + Contract, BLOCK_SIZE, }; -use shadeswap_shared::router::ExecuteMsgResponse; use std::{collections::HashMap, ops::Sub}; -use tokens::TokenType; -use types::{Bytes32, LBPairInformation, MintArrays}; pub const INSTANTIATE_LP_TOKEN_REPLY_ID: u64 = 1u64; pub const MINT_REPLY_ID: u64 = 1u64; @@ -1786,11 +1793,9 @@ pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> Result { QueryMsg::GetLbToken {} => query_lb_token(deps), QueryMsg::GetTokens {} => query_tokens(deps), QueryMsg::SwapSimulation { offer, exclude_fee } => { - to_binary(&query_swap_simulation(deps, env, offer, exclude_fee)?).map_err(Error::CwErr) - } - QueryMsg::GetRewardsDistribution { epoch_id } => { - to_binary(&query_rewards_distribution(deps, epoch_id)?).map_err(Error::CwErr) + query_swap_simulation(deps, env, offer, exclude_fee) } + QueryMsg::GetRewardsDistribution { epoch_id } => query_rewards_distribution(deps, epoch_id), } } @@ -2412,7 +2417,7 @@ fn query_swap_out(deps: Deps, env: Env, amount_in: u128, swap_for_y: bool) -> Re if !BinHelper::is_empty(bin_reserves, !swap_for_y) { let price = PriceHelper::get_price_from_id(id, bin_step)?; - params = params.update_volatility_accumulator(id)?; + params = *params.update_volatility_accumulator(id)?; let (amounts_in_with_fees, amounts_out_of_bin, fees) = BinHelper::get_amounts( bin_reserves, @@ -2473,7 +2478,19 @@ fn query_total_supply(deps: Deps, id: u32) -> Result { let total_supply = _query_total_supply(deps, id, state.lb_token.code_hash, state.lb_token.address)? .u256_to_uint256(); - Ok(TotalSupplyResponse { total_supply }) + to_binary(&TotalSupplyResponse { total_supply }).map_err(Error::CwErr) +} + +fn query_rewards_distribution(deps: Deps, epoch_id: Option) -> Result { + let (epoch_id) = match epoch_id { + Some(id) => id, + None => CONFIG.load(deps.storage)?.rewards_epoch_id - 1, + }; + + to_binary(&RewardsDistributionResponse { + distribution: REWARDS_DISTRIBUTION.load(deps.storage, epoch_id)?, + }) + .map_err(Error::CwErr) } #[shd_entry_point] diff --git a/contracts/liquidity_book/lb_pair/src/state.rs b/contracts/liquidity_book/lb_pair/src/state.rs index 85674e84..50f12ce5 100644 --- a/contracts/liquidity_book/lb_pair/src/state.rs +++ b/contracts/liquidity_book/lb_pair/src/state.rs @@ -1,14 +1,10 @@ -use cosmwasm_schema::cw_serde; -use cosmwasm_std::{Addr, ContractInfo, Storage, Timestamp, Uint128, Uint256}; -use cosmwasm_storage::{singleton, singleton_read, ReadonlySingleton, Singleton}; -use pair_parameter_helper::PairParameters; //? use shade_protocol::{ - c_std::{Addr, ContractInfo, Storage}, + c_std::{Addr, ContractInfo, Storage, Timestamp, Uint128, Uint256}, cosmwasm_schema::cw_serde, lb_libraries::{ math::tree_math::TreeUint24, oracle_helper::Oracle, - pair_parameter_helper::PairParameters, + pair_parameter_helper::{self, PairParameters}, types::Bytes32, viewing_keys::ViewingKey, }, diff --git a/contracts/liquidity_book/tests/src/multitests/lb_pair_fees.rs b/contracts/liquidity_book/tests/src/multitests/lb_pair_fees.rs index 2d2e046d..78de0772 100644 --- a/contracts/liquidity_book/tests/src/multitests/lb_pair_fees.rs +++ b/contracts/liquidity_book/tests/src/multitests/lb_pair_fees.rs @@ -9,8 +9,8 @@ use super::test_helper::{ ID_ONE, }; use anyhow::Ok; -use cosmwasm_std::{ContractInfo, StdError, Timestamp, Uint128, Uint256}; use ethnum::U256; +use serial_test::serial; use shade_multi_test::interfaces::{ lb_factory, lb_pair, @@ -19,7 +19,7 @@ use shade_multi_test::interfaces::{ utils::DeployedContracts, }; use shade_protocol::{ - c_std::{ContractInfo, StdError, Uint128, Uint256}, + c_std::{ContractInfo, StdError, Timestamp, Uint128, Uint256}, lb_libraries::{ math::{encoded_sample::MASK_UINT20, u24::U24}, types::LBPairInformation, diff --git a/packages/shade_protocol/src/contract_interfaces/liquidity_book/lb_libraries/math/u256x256_math.rs b/packages/shade_protocol/src/contract_interfaces/liquidity_book/lb_libraries/math/u256x256_math.rs index 77d0704f..ad6fddfb 100644 --- a/packages/shade_protocol/src/contract_interfaces/liquidity_book/lb_libraries/math/u256x256_math.rs +++ b/packages/shade_protocol/src/contract_interfaces/liquidity_book/lb_libraries/math/u256x256_math.rs @@ -371,74 +371,74 @@ impl U256x256Math { // Compute remainder using mulmod. let remainder = mulmod(x, y, denominator); - println!("remainder: {:#?}", remainder); + // println!("remainder: {:#?}", remainder); // Subtract 256 bit number from 512 bit number. if remainder > prod0 { prod1 = prod1.wrapping_sub(U256::ONE) } prod0 = prod0.wrapping_sub(remainder); - println!("prod0 - remainder: {:#?}", prod0); + // println!("prod0 - remainder: {:#?}", prod0); // Factor powers of two out of denominator and compute largest power of two divisor of denominator. Always >= 1 // See https://cs.stackexchange.com/q/138556/92363 // Does not overflow because the denominator cannot be zero at this stage in the function let mut lpotdod = denominator & (!denominator + U256::ONE); - println!("lpotdod: {:#?}", lpotdod); + // println!("lpotdod: {:#?}", lpotdod); // Divide denominator by lpotdod. let denominator = denominator / lpotdod; - println!("denominator / lpotdod: {:#?}", denominator); + // println!("denominator / lpotdod: {:#?}", denominator); // Divide [prod1 prod0] by lpotdod. let prod0 = prod0 / lpotdod; - println!("prod0 / lpotdod: {:#?}", prod0); + // println!("prod0 / lpotdod: {:#?}", prod0); // Flip lpotdod such that it is 2^256 / lpotdod. If lpotdod is zero, then it becomes one match lpotdod { U256::ONE => lpotdod = U256::ONE, _ => lpotdod = (U256::ZERO.wrapping_sub(lpotdod) / lpotdod).wrapping_add(U256::ONE), } - println!("lpotdod: {:#?}", lpotdod); + // println!("lpotdod: {:#?}", lpotdod); // Shift in bits from prod1 into prod0 let prod0 = prod0 | (prod1.wrapping_mul(lpotdod)); - println!("prod0 bit-shifted: {:#?}", prod0); + // println!("prod0 bit-shifted: {:#?}", prod0); // Invert denominator mod 2^256. Now that denominator is an odd number, it has an inverse modulo 2^256 such // that denominator * inv = 1 mod 2^256. Compute the inverse by starting with a seed that is correct for // four bits. That is, denominator * inv = 1 mod 2^4 let mut inverse = U256::from(3u8).wrapping_mul(denominator).bitxor(2); - println!("inverse: {:#?}", inverse); + // println!("inverse: {:#?}", inverse); // Use the Newton-Raphson iteration to improve the precision. Thanks to Hensel's lifting lemma, this also works // in modular arithmetic, doubling the correct bits in each step inverse = inverse .wrapping_mul(U256::from(2u8).wrapping_sub(denominator.wrapping_mul(inverse))); // inverse mod 2^8 - println!("inverse: {:#?}", inverse); + // println!("inverse: {:#?}", inverse); inverse = inverse .wrapping_mul(U256::from(2u8).wrapping_sub(denominator.wrapping_mul(inverse))); // inverse mod 2^16 - println!("inverse: {:#?}", inverse); + // println!("inverse: {:#?}", inverse); inverse = inverse .wrapping_mul(U256::from(2u8).wrapping_sub(denominator.wrapping_mul(inverse))); // inverse mod 2^32 - println!("inverse: {:#?}", inverse); + // println!("inverse: {:#?}", inverse); inverse = inverse .wrapping_mul(U256::from(2u8).wrapping_sub(denominator.wrapping_mul(inverse))); // inverse mod 2^64 - println!("inverse: {:#?}", inverse); + // println!("inverse: {:#?}", inverse); inverse = inverse .wrapping_mul(U256::from(2u8).wrapping_sub(denominator.wrapping_mul(inverse))); // inverse mod 2^128 - println!("inverse: {:#?}", inverse); + // println!("inverse: {:#?}", inverse); inverse = inverse .wrapping_mul(U256::from(2u8).wrapping_sub(denominator.wrapping_mul(inverse))); // inverse mod 2^256 - println!("inverse: {:#?}", inverse); + // println!("inverse: {:#?}", inverse); // Because the division is now exact we can divide by multiplying with the modular inverse of denominator. // This will give us the correct result modulo 2^256. Since the preconditions guarantee that the outcome is // less than 2^256, this is the final result. We don't need to compute the high bits of the result and prod1 // is no longer required. result = prod0.wrapping_mul(inverse); - println!("result: {:#?}", result); + // println!("result: {:#?}", result); Ok(result) } diff --git a/packages/shadeswap_shared/Cargo.toml b/packages/shadeswap_shared/Cargo.toml deleted file mode 100644 index 7fa3c2f9..00000000 --- a/packages/shadeswap_shared/Cargo.toml +++ /dev/null @@ -1,34 +0,0 @@ -[package] -name = "shadeswap-shared" -version = "0.1.0" -authors = ["Tony "] -edition = "2021" - -exclude = [ - # Those files are rust-optimizer artifacts. You might want to commit them for convenience but they should not be part of the source code publication. - "contract.wasm", - "hash.txt", -] - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[lib] -crate-type = ["cdylib", "rlib"] - -[features] -default = [] -utils = [] -staking = [] - -[dependencies] -schemars = "0.8.11" -serde = { version = "1.0.114", default-features = false, features = ["derive"] } -cosmwasm-std = { package = "secret-cosmwasm-std", version = "=1.1.11", features = ["iterator"] } -cosmwasm-storage = { package = "secret-cosmwasm-storage", version = "=1.1.11", features = ["iterator"] } -cosmwasm-schema = { version = "1.1.8" } -subtle = { version = "2.2.3", default-features = false } -query-authentication = { git = "https://github.com/securesecrets/query-authentication", branch = "cosmwasm_v1_upgrade" } -shade-protocol = { version = "0.1.0", path = "../shade_protocol", features = ["snip20", "admin", "query_auth", "airdrop","liquidity_book"] } - -better-secret-math = { git = "https://github.com/securesecrets/better-secret-math" } -secret-storage-plus = { git = "https://github.com/securesecrets/secret-plus-utils" } diff --git a/packages/shadeswap_shared/src/amm_pair.rs b/packages/shadeswap_shared/src/amm_pair.rs deleted file mode 100644 index 31600c9a..00000000 --- a/packages/shadeswap_shared/src/amm_pair.rs +++ /dev/null @@ -1,53 +0,0 @@ -use cosmwasm_std::{ - Addr, Decimal256, Uint256 -}; -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; -use crate::core::{ContractLink, TokenPair, Fee, TokenType}; -use shade_protocol::Contract; - -/// Represents the address of an exchange and the pair that it manages -#[derive(Serialize, Deserialize, JsonSchema, Clone, PartialEq, Debug)] -pub struct AMMPair { - /// The pair that the contract manages. - pub pair: TokenPair, - /// Address of the contract that manages the exchange. - pub address: Addr, - /// Used to enable or disable the AMMPair - pub enabled: bool -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -pub struct StableParams { - pub a: Decimal256, - pub gamma1: Uint256, - pub gamma2: Uint256, - pub oracle: Contract, -} - -#[derive(Serialize, Deserialize, JsonSchema, PartialEq, Debug,Clone)] -pub struct AMMSettings { - pub lp_fee: Fee, - pub shade_dao_fee: Fee, - pub stable_lp_fee: Fee, - pub stable_shade_dao_fee: Fee, - pub shade_dao_address: ContractLink -} - -pub fn generate_pair_key(pair: &TokenPair) -> Vec { - let mut bytes: Vec<&[u8]> = Vec::new(); - - match &pair.0 { - TokenType::NativeToken { denom, ..} => bytes.push(denom.as_bytes()), - TokenType::CustomToken { contract_addr, .. } => bytes.push(contract_addr.as_bytes()) - } - - match &pair.1 { - TokenType::NativeToken { denom, .. } => bytes.push(denom.as_bytes()), - TokenType::CustomToken { contract_addr, .. } => bytes.push(contract_addr.as_bytes()) - } - - bytes.sort(); - - bytes.concat() -} \ No newline at end of file diff --git a/packages/shadeswap_shared/src/core/admin.rs b/packages/shadeswap_shared/src/core/admin.rs deleted file mode 100644 index a26ae8e5..00000000 --- a/packages/shadeswap_shared/src/core/admin.rs +++ /dev/null @@ -1,37 +0,0 @@ -// use cosmwasm_std::{ -// StdError, -// StdResult, -// Storage, Response, MessageInfo, Addr -// }; -// use cosmwasm_storage::{singleton, Singleton, singleton_read, ReadonlySingleton}; - -// pub static ADMIN: &[u8] =b"contract_pair_admin"; - -// pub fn admin_w(storage: &mut dyn Storage) -> Singleton { -// singleton(storage, ADMIN) -// } - -// pub fn admin_r(storage: & dyn Storage) -> ReadonlySingleton { -// singleton_read(storage, ADMIN) -// } - -// // pub fn apply_admin_guard( -// // caller: &Addr, -// // storage: &mut dyn Storage, -// // ) -> StdResult { -// // let admin_address = admin_r(storage).load()?; -// // if caller != &admin_address { -// // return Err(StdError::generic_err("Caller is not admin")) -// // } -// // return Ok(true) -// // } - -// pub fn set_admin_guard( -// storage: &mut dyn Storage, -// info: MessageInfo, -// admin: Addr -// ) -> StdResult{ -// apply_admin_guard(&info.sender, storage)?; -// admin_w(storage).save(&admin)?; -// Ok(Response::default()) -// } diff --git a/packages/shadeswap_shared/src/core/display.rs b/packages/shadeswap_shared/src/core/display.rs deleted file mode 100644 index 21a147d6..00000000 --- a/packages/shadeswap_shared/src/core/display.rs +++ /dev/null @@ -1,8 +0,0 @@ -use super::TokenPair; -use std::fmt::{Display, Formatter, Result}; - -impl Display for TokenPair { - fn fmt(&self, f: &mut Formatter) -> Result { - write!(f, "Token 1: {} \n Token 2: {}", self.0, self.1) - } -} diff --git a/packages/shadeswap_shared/src/core/token_type.rs b/packages/shadeswap_shared/src/core/token_type.rs deleted file mode 100644 index 03bd16ad..00000000 --- a/packages/shadeswap_shared/src/core/token_type.rs +++ /dev/null @@ -1,15 +0,0 @@ -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; -use shade_protocol::utils::liquidity_book::tokens::TokenType; - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -pub struct StableTokenData { - pub oracle_key: String, - pub decimals: u8, -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -pub struct StableTokenType { - pub token: TokenType, - pub stable_token_data: StableTokenData, -} diff --git a/packages/shadeswap_shared/src/helpers.rs b/packages/shadeswap_shared/src/helpers.rs deleted file mode 100644 index eb50b215..00000000 --- a/packages/shadeswap_shared/src/helpers.rs +++ /dev/null @@ -1,47 +0,0 @@ -use cosmwasm_std::{from_binary, Addr, Deps, QuerierWrapper, StdError, StdResult}; -use serde::de::DeserializeOwned; -use shade_protocol::{ - query_auth::{self, helpers::PermitAuthentication, QueryPermit}, - utils::Query, - Contract, -}; - -pub fn authenticate_permit( - deps: Deps, - permit: QueryPermit, - querier: &QuerierWrapper, - authenticator: Option, -) -> StdResult> { - let sender: Addr; - let revoked: bool; - match authenticator { - Some(a) => { - let res: query_auth::QueryAnswer = query_auth::QueryMsg::ValidatePermit { - permit: permit.clone(), - } - .query(querier, &a)?; - - match res { - query_auth::QueryAnswer::ValidatePermit { user, is_revoked } => { - sender = user; - revoked = is_revoked; - } - _ => return Err(StdError::generic_err("Wrong query response")), - } - - Ok(PermitAuthentication { - sender, - revoked, - data: from_binary(&permit.params.data)?, - }) - } - None => { - sender = permit.validate(deps.api, None)?.as_addr(None)?; - Ok(PermitAuthentication { - sender, - revoked: false, - data: from_binary(&permit.params.data)?, - }) - } - } -} diff --git a/packages/shadeswap_shared/src/lib.rs b/packages/shadeswap_shared/src/lib.rs deleted file mode 100644 index 0dde33a7..00000000 --- a/packages/shadeswap_shared/src/lib.rs +++ /dev/null @@ -1,20 +0,0 @@ -mod helpers; -pub mod msg; -pub use msg::*; -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; -pub mod core; -// Forward important libs to avoid constantly importing them in the cargo crates, could help reduce compile times -pub mod c_std { - pub use cosmwasm_std::*; -} -pub use shade_protocol::{admin, airdrop, query_auth, snip20, utils}; -pub const BLOCK_SIZE: usize = 256; -pub use helpers::*; -pub use serde; - -#[derive(Serialize, Deserialize, JsonSchema, Clone, Debug)] -pub struct Pagination { - pub start: u64, - pub limit: u8, -} diff --git a/packages/shadeswap_shared/src/msg/mod.rs b/packages/shadeswap_shared/src/msg/mod.rs deleted file mode 100644 index 56a93cab..00000000 --- a/packages/shadeswap_shared/src/msg/mod.rs +++ /dev/null @@ -1,594 +0,0 @@ -use crate::core::ContractInstantiationInfo; -use cosmwasm_std::{Addr, Binary, Uint128}; -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; -use shade_protocol::{ - utils::{ExecuteCallback, InstantiateCallback, Query}, - BLOCK_SIZE, -}; - -pub mod staking; - -pub mod router { - - use super::*; - use crate::core::TokenAmount; - use shade_protocol::{ - liquidity_book::lb_pair::SwapResult, - snip20::Snip20ReceiveMsg, - utils::liquidity_book::tokens::TokenType, - Contract, - }; - - #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] - #[serde(rename_all = "snake_case")] - pub enum ExecuteMsgResponse { - SwapResult { - amount_in: Uint128, - amount_out: Uint128, - }, - } - - #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] - #[serde(rename_all = "snake_case")] - pub enum InvokeMsg { - SwapTokensForExact { - path: Vec, - expected_return: Option, - recipient: Option, - }, - } - - #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] - pub struct InitMsg { - pub prng_seed: Binary, - pub entropy: Binary, - pub admin_auth: Contract, - pub airdrop_address: Option, - } - - #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] - pub struct Hop { - pub addr: String, - pub code_hash: String, - } - - #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] - #[serde(rename_all = "snake_case")] - pub enum ExecuteMsg { - // SNIP20 receiver interface - Receive(Snip20ReceiveMsg), - SwapTokensForExact { - /// The token type to swap from. - offer: TokenAmount, - expected_return: Option, - path: Vec, - recipient: Option, - padding: Option, - }, - RegisterSNIP20Token { - token_addr: String, - token_code_hash: String, - oracle_key: Option, - padding: Option, - }, - RecoverFunds { - token: TokenType, - amount: Uint128, - to: String, - msg: Option, - padding: Option, - }, - SetConfig { - admin_auth: Option, - padding: Option, - }, - } - - #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] - #[serde(rename_all = "snake_case")] - pub enum QueryMsg { - SwapSimulation { - offer: TokenAmount, - path: Vec, - exclude_fee: Option, - }, - GetConfig {}, - RegisteredTokens {}, - } - - #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] - #[serde(rename_all = "snake_case")] - pub enum QueryMsgResponse { - SwapSimulation { - total_fee_amount: Uint128, - lp_fee_amount: Uint128, - shade_dao_fee_amount: Uint128, - result: SwapResult, - price: String, - }, - GetConfig { - admin_auth: Contract, - airdrop_address: Option, - }, - RegisteredTokens { - tokens: Vec, - }, - } - - impl InstantiateCallback for InitMsg { - const BLOCK_SIZE: usize = BLOCK_SIZE; - } - - impl ExecuteCallback for ExecuteMsg { - const BLOCK_SIZE: usize = BLOCK_SIZE; - } - - impl Query for QueryMsg { - const BLOCK_SIZE: usize = BLOCK_SIZE; - } -} - -pub mod amm_pair { - use std::fmt::{Debug, Display}; - - use super::*; - use crate::{ - core::{ - ContractInstantiationInfo, - CustomFee, - Fee, - StableTokenData, - TokenAmount, - TokenPair, - TokenPairAmount, - }, - staking::StakingContractInstantiateInfo, - }; - use cosmwasm_std::{Addr, Decimal256, Uint256}; - use schemars::JsonSchema; - use serde::{Deserialize, Serialize}; - use shade_protocol::{ - liquidity_book::lb_pair::SwapResult, - snip20::Snip20ReceiveMsg, - utils::{asset::RawContract, liquidity_book::tokens::TokenType}, - Contract, - }; - - /// Represents the address of an exchange and the pair that it manages - #[derive(Serialize, Deserialize, JsonSchema, Clone, PartialEq, Debug)] - pub struct AMMPair { - /// The pair that the contract manages. - pub pair: TokenPair, - /// Address of the contract that manages the exchange. - pub address: Addr, - // Code hash of the AMM Pair - pub code_hash: String, - /// Used to enable or disable the AMMPair - pub enabled: bool, - } - - #[derive(Serialize, Deserialize, PartialEq, Debug, Clone, JsonSchema)] - #[serde(rename_all = "snake_case")] - pub enum ContractStatus { - Active, // allows all operations - FreezeAll, // blocks everything except admin-protected config changes - LpWithdrawOnly, // blocks everything except LP withdraws and admin-protected config changes - } - - impl Display for ContractStatus { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - Debug::fmt(self, f) - } - } - - #[derive(Clone, Debug, PartialEq, Deserialize, Serialize, JsonSchema)] - pub struct CustomIterationControls { - pub epsilon: Uint256, // assumed to have same decimals as SignedDecimal - pub max_iter_newton: u16, - pub max_iter_bisect: u16, - } - - #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] - pub struct StableParams { - pub a: Decimal256, - pub gamma1: Uint256, - pub gamma2: Uint256, - pub oracle: Contract, - pub min_trade_size_x_for_y: Decimal256, - pub min_trade_size_y_for_x: Decimal256, - pub max_price_impact_allowed: Decimal256, - pub custom_iteration_controls: Option, - } - - #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] - pub struct StablePairInfoResponse { - pub stable_params: StableParams, - pub stable_token0_data: StableTokenData, - pub stable_token1_data: StableTokenData, - //p is optional so that the PairInfo query can still return even when the calculation of p fails - pub p: Option, - } - - #[derive(Serialize, Deserialize, JsonSchema, PartialEq, Debug, Clone)] - pub struct AMMSettings { - pub lp_fee: Fee, - pub shade_dao_fee: Fee, - pub stable_lp_fee: Fee, - pub stable_shade_dao_fee: Fee, - pub shade_dao_address: Contract, - } - - pub fn generate_pair_key(pair: &TokenPair) -> Vec { - let mut bytes: Vec<&[u8]> = Vec::new(); - let mut values: Vec = Vec::new(); - - values.push(pair.0.unique_key()); - values.push(pair.1.unique_key()); - values.push(pair.2.to_string()); - values.sort(); - bytes.push(values[0].as_bytes()); - bytes.push(values[1].as_bytes()); - bytes.push(values[2].as_bytes()); - bytes.concat() - } - - #[derive(Serialize, Deserialize, PartialEq, Debug, JsonSchema, Clone)] - pub struct SwapInfo { - pub total_fee_amount: Uint128, - pub lp_fee_amount: Uint128, - pub shade_dao_fee_amount: Uint128, - pub result: SwapResult, - pub price: String, - pub new_input_pool: Uint128, - pub new_output_pool: Uint128, - pub index_of_input_token: u8, - pub index_of_output_token: u8, - } - - pub struct VirtualSwapResponse { - pub output: TokenPairAmount, - pub swap_info: Option, - } - - #[derive(Serialize, Deserialize, PartialEq, Clone, Debug, JsonSchema)] - pub struct FeeInfo { - pub shade_dao_address: Addr, - pub lp_fee: Fee, - pub shade_dao_fee: Fee, - pub stable_lp_fee: Fee, - pub stable_shade_dao_fee: Fee, - } - - #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] - pub struct InitMsg { - pub pair: TokenPair, - pub token0_oracle_key: Option, - pub token1_oracle_key: Option, - pub lp_token_contract: ContractInstantiationInfo, - // Leave none if initializing without factory - pub factory_info: Option, - pub prng_seed: Binary, - pub entropy: Binary, - pub admin_auth: Contract, - pub staking_contract: Option, - pub custom_fee: Option, - pub stable_params: Option, - pub lp_token_decimals: u8, - pub lp_token_custom_label: Option, - } - - #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] - #[serde(rename_all = "snake_case")] - pub enum ExecuteMsg { - AddLiquidityToAMMContract { - deposit: TokenPairAmount, - expected_return: Option, - staking: Option, - execute_sslp_virtual_swap: Option, - padding: Option, - }, - SwapTokens { - /// The token type to swap from. - offer: TokenAmount, - expected_return: Option, - to: Option, - padding: Option, - }, - // SNIP20 receiver interface - Receive(Snip20ReceiveMsg), - AddWhiteListAddress { - address: String, - padding: Option, - }, - RemoveWhitelistAddresses { - addresses: Vec, - padding: Option, - }, - SetConfig { - admin_auth: Option, - padding: Option, - }, - SetStableParams { - stable_params: Option, - padding: Option, - }, - SetCustomPairFee { - custom_fee: Option, - padding: Option, - }, - SetViewingKey { - viewing_key: String, - padding: Option, - }, - SetOracleKeyAndDecimals { - token: TokenType, - oracle_key: String, - padding: Option, - }, - SetStakingContract { - staking: RawContract, - padding: Option, - }, - SetContractStatus { - contract_status: ContractStatus, - padding: Option, - }, - } - - #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] - #[serde(rename_all = "snake_case")] - pub enum ExecuteMsgResponse { - SwapResult { - price: String, - amount_in: Uint128, - amount_out: Uint128, - total_fee_amount: Uint128, - lp_fee_amount: Uint128, - shade_dao_fee_amount: Uint128, - }, - } - - #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] - #[serde(rename_all = "snake_case")] - pub enum InvokeMsg { - SwapTokens { - expected_return: Option, - to: Option, - padding: Option, - }, - RemoveLiquidity { - /// If sender is removing LP for someone else, from should contain who that someone is - from: Option, - single_sided_withdraw_type: Option, //None means 50/50 balanced withdraw, and a value here tells which token to send the withdraw in - single_sided_expected_return: Option, //this field will be ignored on balanced withdraws - padding: Option, - }, - } - #[derive(Serialize, Deserialize, JsonSchema, Clone, Debug)] - #[serde(rename_all = "snake_case")] - pub enum QueryMsg { - GetConfig {}, - GetPairInfo {}, - GetWhiteListAddress {}, - GetTradeCount {}, - SwapSimulation { - offer: TokenAmount, - exclude_fee: Option, - }, - GetShadeDaoInfo {}, - GetEstimatedLiquidity { - deposit: TokenPairAmount, - sender: Addr, - execute_sslp_virtual_swap: Option, - }, - GetContractStatus {}, - } - - #[derive(Serialize, Deserialize, Debug, JsonSchema, PartialEq)] - #[serde(rename_all = "snake_case")] - pub enum QueryMsgResponse { - GetPairInfo { - liquidity_token: Contract, - factory: Option, - pair: TokenPair, - amount_0: Uint128, - amount_1: Uint128, - total_liquidity: Uint128, - contract_version: u32, - fee_info: FeeInfo, - stable_info: Option, - }, - GetWhiteListAddress { - addresses: Vec, - }, - GetTradeCount { - count: u64, - }, - GetClaimReward { - amount: Uint128, - }, - GetEstimatedPrice { - estimated_price: String, - }, - SwapSimulation { - total_fee_amount: Uint128, - lp_fee_amount: Uint128, - shade_dao_fee_amount: Uint128, - result: SwapResult, - price: String, - }, - GetShadeDaoInfo { - shade_dao_address: String, - shade_dao_fee: Fee, - lp_fee: Fee, - admin_auth: Contract, - }, - GetEstimatedLiquidity { - lp_token: Uint128, - tokens_returned: Option, - total_lp_token: Uint128, - }, - GetConfig { - factory_contract: Option, - lp_token: Contract, - staking_contract: Option, - pair: TokenPair, - custom_fee: Option, - }, - GetContractStatus { - contract_status: ContractStatus, - }, - } - - impl InstantiateCallback for InitMsg { - const BLOCK_SIZE: usize = BLOCK_SIZE; - } - - impl ExecuteCallback for ExecuteMsg { - const BLOCK_SIZE: usize = BLOCK_SIZE; - } - - impl Query for QueryMsg { - const BLOCK_SIZE: usize = BLOCK_SIZE; - } -} - -pub mod factory { - use super::*; - use crate::{ - amm_pair::{AMMPair, AMMSettings, StableParams}, - core::TokenPair, - staking::StakingContractInstantiateInfo, - Pagination, - }; - use schemars::JsonSchema; - use serde::{Deserialize, Serialize}; - use shade_protocol::Contract; - - #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] - pub struct InitMsg { - pub pair_contract: ContractInstantiationInfo, - pub amm_settings: AMMSettings, - pub lp_token_contract: ContractInstantiationInfo, - pub prng_seed: Binary, - pub api_key: String, - //Set the default authenticator for all permits on the contracts - pub authenticator: Option, - pub admin_auth: Contract, - } - - #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] - #[serde(rename_all = "snake_case")] - pub enum ExecuteMsg { - SetConfig { - pair_contract: Option, - lp_token_contract: Option, - amm_settings: Option, - api_key: Option, - admin_auth: Option, - padding: Option, - }, - CreateAMMPair { - pair: TokenPair, - entropy: Binary, - staking_contract: Option, - stable_params: Option, - token0_oracle_key: Option, - token1_oracle_key: Option, - lp_token_decimals: u8, - amm_pair_custom_label: Option, - lp_token_custom_label: Option, - padding: Option, - }, - AddAMMPairs { - amm_pairs: Vec, - padding: Option, - }, - } - - #[derive(Serialize, Deserialize, Debug, JsonSchema, PartialEq)] - #[serde(rename_all = "snake_case")] - pub enum QueryResponse { - ListAMMPairs { - amm_pairs: Vec, - }, - GetConfig { - pair_contract: ContractInstantiationInfo, - amm_settings: AMMSettings, - lp_token_contract: ContractInstantiationInfo, - authenticator: Option, - admin_auth: Contract, - }, - GetAMMPairAddress { - address: String, - }, - AuthorizeApiKey { - authorized: bool, - }, - } - - #[derive(Serialize, Deserialize, JsonSchema, Clone, Debug)] - #[serde(rename_all = "snake_case")] - pub enum QueryMsg { - // GetCount returns the current count as a json-encoded number - ListAMMPairs { pagination: Pagination }, - GetAMMPairAddress { pair: TokenPair }, - GetConfig {}, - AuthorizeApiKey { api_key: String }, - } - - impl InstantiateCallback for InitMsg { - const BLOCK_SIZE: usize = BLOCK_SIZE; - } - - impl ExecuteCallback for ExecuteMsg { - const BLOCK_SIZE: usize = BLOCK_SIZE; - } - - impl Query for QueryMsg { - const BLOCK_SIZE: usize = BLOCK_SIZE; - } -} - -pub mod lp_token { - - use shade_protocol::contract_interfaces::snip20::InitialBalance; - - use super::*; - - #[derive(Serialize, Deserialize, Clone, Debug, JsonSchema)] - pub struct InitConfig { - /// Indicates whether the total supply is public or should be kept secret. - /// default: False - pub public_total_supply: Option, - /// Indicates whether deposit functionality should be enabled - /// default: False - pub enable_deposit: Option, - /// Indicates whether redeem functionality should be enabled - /// default: False - pub enable_redeem: Option, - /// Indicates whether mint functionality should be enabled - /// default: False - pub enable_mint: Option, - /// Indicates whether burn functionality should be enabled - /// default: False - pub enable_burn: Option, - } - - #[derive(Serialize, Deserialize, Clone, Debug, JsonSchema)] - pub struct InstantiateMsg { - pub name: String, - pub admin: Option, - pub symbol: String, - pub decimals: u8, - pub initial_balances: Option>, - pub prng_seed: Binary, - pub config: Option, - pub supported_denoms: Option>, - } - - impl InstantiateCallback for InstantiateMsg { - const BLOCK_SIZE: usize = BLOCK_SIZE; - } -} diff --git a/packages/shadeswap_shared/src/msg/staking.rs b/packages/shadeswap_shared/src/msg/staking.rs deleted file mode 100644 index 9536d9e2..00000000 --- a/packages/shadeswap_shared/src/msg/staking.rs +++ /dev/null @@ -1,668 +0,0 @@ -use crate::{core::ContractInstantiationInfo, BLOCK_SIZE}; -use cosmwasm_schema::{cw_serde, QueryResponses}; -use cosmwasm_std::{Addr, Binary, Uint128, Uint256}; -use shade_protocol::{ - query_auth::QueryPermit, - snip20::Snip20ReceiveMsg, - utils::liquidity_book::tokens::TokenType, - utils::{asset::RawContract, ExecuteCallback, InstantiateCallback, Query}, - Contract, -}; -#[cfg(feature = "staking")] -pub use state::*; - -impl InstantiateCallback for InstantiateMsg { - const BLOCK_SIZE: usize = BLOCK_SIZE; -} - -impl ExecuteCallback for ExecuteMsg { - const BLOCK_SIZE: usize = BLOCK_SIZE; -} - -impl Query for QueryMsg { - const BLOCK_SIZE: usize = BLOCK_SIZE; -} - -#[cw_serde] -pub struct StakingContractInstantiateInfo { - pub staking_contract_info: ContractInstantiationInfo, - pub custom_label: Option, - pub first_reward_token: Option, - pub query_auth: Option, -} - -#[cw_serde] -pub struct InstantiateMsg { - pub amm_pair: String, - pub lp_token: RawContract, - pub admin_auth: RawContract, - pub query_auth: Option, - pub first_reward_token: Option, -} - -#[cw_serde] -pub enum ExecuteMsg { - ClaimRewards { - padding: Option, - }, - Unstake { - amount: Uint128, - remove_liquidity: Option, - padding: Option, - }, - Receive(Snip20ReceiveMsg), - UpdateRewardTokens(Vec), - CreateRewardTokens(Vec), - UpdateConfig { - admin_auth: Option, - query_auth: Option, - padding: Option, - }, - RecoverFunds { - token: TokenType, - amount: Uint128, - to: String, - msg: Option, - padding: Option, - }, -} - -#[cw_serde] -pub enum InvokeMsg { - /// From is used to determine the staker since this can be called by the AMMPair when auto staking. - Stake { - from: Option, - padding: Option, - }, -} - -#[cw_serde] -pub struct RewardTokenUpdate { - pub reward_token: RawContract, - pub index: u64, - pub valid_to: u64, -} - -#[cw_serde] -pub struct RewardTokenCreate { - pub reward_token: RawContract, - pub daily_reward_amount: Uint128, - pub valid_to: u64, -} - -#[allow(clippy::large_enum_variant)] -#[cw_serde] -#[derive(QueryResponses)] -pub enum QueryMsg { - #[returns(ConfigResponse)] - GetConfig {}, - #[returns(PermitQueryResponse)] - WithPermit { - permit: QueryPermit, - query: AuthQuery, - }, -} - -#[cw_serde] -pub struct QueryPermitData {} - -#[cw_serde] -pub enum AuthQuery { - GetStakerInfo {}, -} - -#[derive(PartialEq, Debug, Clone)] -pub struct ClaimRewardResponse { - pub token: Contract, - pub amount: Uint128, -} - -// RESPONSE TYPES - -#[cw_serde] -pub struct ConfigResponse { - pub lp_token: Contract, - pub amm_pair: Addr, - pub admin_auth: Contract, - pub query_auth: Option, - pub total_amount_staked: Uint128, - pub reward_tokens: Vec, -} - -#[cw_serde] -pub enum PermitQueryResponse { - StakerInfo { - /// Amount normally staked. - staked: Uint128, - /// Staked - total_staked: Uint128, - claimable_rewards: Vec, - }, -} - -#[cw_serde] -pub struct ClaimableRewardsResponse { - pub token: Contract, - pub amount: Uint128, -} - -#[cw_serde] -pub struct RewardTokenInfo { - pub token: Contract, - pub decimals: u8, - pub reward_per_second: Uint256, - pub reward_per_staked_token: Uint256, - pub valid_to: u64, - pub last_updated: u64, -} - -impl RewardTokenUpdate { - pub fn new(reward_token: impl Into, index: u64, valid_to: u64) -> Self { - Self { - reward_token: reward_token.into(), - index, - valid_to, - } - } -} - -impl RewardTokenCreate { - pub fn new( - reward_token: impl Into, - daily_reward_amount: Uint128, - valid_to: u64, - ) -> Self { - Self { - reward_token: reward_token.into(), - daily_reward_amount, - valid_to, - } - } -} - -#[cfg(feature = "staking")] -pub mod state { - use better_secret_math::{ - common::{bankers_round, exp10, muldiv}, - ud60x18::mul, - U256, - }; - use core::time; - use cosmwasm_schema::cw_serde; - use cosmwasm_std::{ - Addr, CosmosMsg, Decimal256, OverflowError, QuerierWrapper, StdError, StdResult, Storage, - Uint128, Uint256, - }; - use secret_storage_plus::{Bincode2, Item, ItemStorage, Map}; - use shade_protocol::{ - contract_interfaces::snip20::ExecuteMsg as Snip20ExecuteMsg, utils::ExecuteCallback, - Contract, - }; - use std::{cmp::min, collections::HashMap}; - - use super::{ - ClaimRewardResponse, ClaimableRewardsResponse, ConfigResponse, PermitQueryResponse, - RewardTokenInfo, - }; - - /// Manages the global state of the staking contract. - #[cw_serde] - pub struct Custodian { - pub lp_token: Contract, - pub amm_pair: Addr, - pub admin_auth: Contract, - pub query_auth: Option, - pub total_amount_staked: Uint128, - } - - impl ItemStorage for Custodian { - const ITEM: Item<'static, Self> = Item::new("custodian"); - } - - #[cw_serde] - pub struct RewardTokenSet(Vec); - - impl RewardTokenSet { - pub fn insert(&mut self, addr: &Addr) { - if !self.0.contains(addr) { - self.0.push(addr.clone()); - } - } - pub fn get(&self) -> &[Addr] { - self.0.as_slice() - } - } - - impl<'a> Custodian { - pub const STAKERS: Map<'static, &'a Addr, u128, Bincode2> = Map::new("stakers"); - pub const REWARD_TOKEN_INFO: Map<'static, &'a Addr, Vec> = - Map::new("reward_token_info"); - pub const REWARD_TOKENS: Item<'static, RewardTokenSet> = Item::new("reward_tokens"); - } - - impl Custodian { - pub fn require_lp_token(&self, addr: &Addr) -> StdResult<()> { - if self.lp_token.address.eq(addr) { - return Ok(()); - } - Err(StdError::generic_err(format!( - "Must stake the LP token {}. Attempted to stake {addr}.", - self.lp_token.address - ))) - } - pub fn save_staker(storage: &mut dyn Storage, staker: &Staker) -> StdResult<()> { - staker.save_rewards(storage)?; - Self::STAKERS.save(storage, &staker.addr, &staker.staked.u128()) - } - } - - impl Custodian { - pub fn store_empty_reward_set(&self, storage: &mut dyn Storage) -> StdResult<()> { - match Self::REWARD_TOKENS.may_load(storage)? { - Some(_) => Err(StdError::generic_err("Reward token storage already exists")), - None => Self::REWARD_TOKENS.save(storage, &RewardTokenSet(vec![])), - } - } - - pub fn update_reward_token( - &self, - now: u64, - storage: &mut dyn Storage, - token: &Contract, - index: u64, - valid_to: u64, - ) -> StdResult> { - if valid_to < now { - return Err(StdError::generic_err("valid_to cannot be in the past")); - } - let mut reward_configs = Self::REWARD_TOKEN_INFO.load(storage, &token.address)?; - match reward_configs.get_mut(index as usize) { - Some(info) => { - info.valid_to = valid_to; - } - None => return Err(StdError::generic_err("Invalid index")), - }; - Self::REWARD_TOKEN_INFO.save(storage, &token.address, &reward_configs)?; - Ok(reward_configs) - } - - pub fn create_reward_token( - &self, - storage: &mut dyn Storage, - now: u64, - token: &Contract, - daily_emission_amount: Uint128, - valid_to: u64, - decimals: u8, - ) -> StdResult> { - let mut reward_configs = - match Self::REWARD_TOKEN_INFO.may_load(storage, &token.address)? { - Some(rewards) => rewards, - None => vec![], - }; - let info = RewardTokenInfo::init_from_daily_rewards( - now, - token, - decimals, - daily_emission_amount, - valid_to, - )?; - match Self::REWARD_TOKENS.may_load(storage)? { - Some(mut tokens) => { - tokens.insert(&info.token.address); - Self::REWARD_TOKENS.save(storage, &tokens)?; - } - None => Self::REWARD_TOKENS - .save(storage, &RewardTokenSet(vec![info.token.address.clone()]))?, - }; - reward_configs.push(info); - Self::REWARD_TOKEN_INFO.save(storage, &token.address, &reward_configs)?; - Ok(reward_configs) - } - - pub fn to_config_response(self, storage: &dyn Storage) -> StdResult { - let tokens = Self::REWARD_TOKENS.load(storage)?; - let mut infos = Vec::with_capacity(tokens.0.len()); - for token in tokens.0 { - if let Some(reward_configs) = Self::REWARD_TOKEN_INFO.may_load(storage, &token)? { - for info in reward_configs { - let info = info.to_response()?; - infos.push(info); - } - } - } - Ok(ConfigResponse { - lp_token: self.lp_token, - amm_pair: self.amm_pair, - admin_auth: self.admin_auth, - query_auth: self.query_auth, - reward_tokens: infos, - total_amount_staked: self.total_amount_staked, - }) - } - - pub fn update_reward_per_token( - &self, - now: u64, - info: &mut RewardTokenInfo, - ) -> StdResult { - info.update_reward_per_token(now, self.total_amount_staked) - } - - pub fn may_load_staker(storage: &dyn Storage, user: &Addr) -> StdResult> { - if let Some(staked) = Self::STAKERS.may_load(storage, user)? { - Ok(Some(Staker { - addr: user.clone(), - staked: Uint128::new(staked), - claimable_rewards: HashMap::default(), - })) - } else { - Ok(None) - } - } - } - - impl RewardTokenInfo { - pub const SECONDS_IN_DAY: U256 = U256::new(24u128 * 3600u128); - pub const MAX_DECIMALS: U256 = exp10(18); - - pub fn normalize_amount(amount: impl Into) -> StdResult { - let amount: U256 = amount.into(); - amount - .checked_mul(Self::MAX_DECIMALS) - .ok_or_else(|| StdError::generic_err("Overflow")) - } - - pub fn denormalize_amount(amount: impl Into) -> StdResult { - let amount: U256 = amount.into(); - amount - .checked_div(Self::MAX_DECIMALS) - .ok_or_else(|| StdError::generic_err("Overflow")) - } - - pub fn init_from_daily_rewards( - now: u64, - token: &Contract, - decimals: u8, - daily_emission_amount: Uint128, - valid_to: u64, - ) -> StdResult { - let daily_emission_amount = Self::normalize_amount(daily_emission_amount)?; - Ok(Self { - token: token.clone(), - decimals, - reward_per_second: (daily_emission_amount / Self::SECONDS_IN_DAY).into(), - valid_to, - reward_per_staked_token: Uint256::zero(), - last_updated: now, - }) - } - - pub fn update_reward_per_second( - &mut self, - daily_emission_amount: Uint128, - ) -> StdResult<()> { - let daily_emission_amount = Self::normalize_amount(daily_emission_amount)?; - self.reward_per_second = (daily_emission_amount / Self::SECONDS_IN_DAY).into(); - Ok(()) - } - - /// Denormalizes the reward per second and reward per token stored. - pub fn to_response(&self) -> StdResult { - Ok(RewardTokenInfo { - token: self.token.clone(), - decimals: self.decimals, - reward_per_second: Self::denormalize_amount(self.reward_per_second)?.into(), - valid_to: self.valid_to, - reward_per_staked_token: Self::denormalize_amount(self.reward_per_staked_token)? - .into(), - last_updated: self.last_updated, - }) - } - - /// recalculates reward per staked token - pub fn update_reward_per_token( - &mut self, - now: u64, - total_staked: Uint128, - ) -> StdResult { - let min_time_rewards_applicable = min(now, self.valid_to); - if !total_staked.is_zero() && min_time_rewards_applicable > self.last_updated { - let time_since_updated = - U256::new((min_time_rewards_applicable - self.last_updated).into()); - let total_staked = U256::new(total_staked.u128()); - let rewards_since_updated = muldiv( - time_since_updated, - self.reward_per_second.into(), - total_staked, - )?; - self.reward_per_staked_token = self - .reward_per_staked_token - .checked_add(rewards_since_updated.into())?; - } - self.last_updated = min_time_rewards_applicable; - Ok(self.reward_per_staked_token) - } - } - - #[cw_serde] - pub struct Staker { - pub addr: Addr, - pub staked: Uint128, - pub claimable_rewards: HashMap>, - } - - impl Staker { - pub fn new(addr: &Addr) -> Self { - Self { - addr: addr.clone(), - staked: Uint128::zero(), - claimable_rewards: HashMap::default(), - } - } - } - - #[cw_serde] - pub struct ClaimableRewardsInfo { - pub info: RewardTokenInfo, - pub amount: Uint128, - pub last_reward_per_staked_token_paid: Uint256, - } - - impl ClaimableRewardsInfo { - pub fn new(info: &RewardTokenInfo) -> Self { - Self { - info: info.clone(), - amount: Uint128::zero(), - last_reward_per_staked_token_paid: Uint256::zero(), - } - } - } - - impl<'a> Staker { - pub const REWARDS: Map<'static, (&'a Addr, &'a Addr), Vec> = - Map::new("staker_rewards"); - } - - impl Staker { - pub fn get_rewards_key<'a>(&'a self, reward_token: &'a Addr) -> (&'a Addr, &'a Addr) { - (reward_token, &self.addr) - } - - pub fn total_staked(&self) -> Uint128 { - self.staked - } - - pub fn stake( - &mut self, - storage: &mut dyn Storage, - amount: impl Into + Copy, - ) -> StdResult { - let amount = amount.into(); - self.staked = self.staked.checked_add(amount)?; - Ok(amount) - } - - pub fn unstake( - &mut self, - storage: &mut dyn Storage, - amount: impl Into + Copy, - ) -> StdResult { - let amount = amount.into(); - self.staked = self.staked.checked_sub(amount)?; - Ok(amount) - } - - //called once per reward token type, loads data from storage into staker object, updating claimable amounts - pub fn update_claimable_rewards( - &mut self, - storage: &dyn Storage, - reward_infos: &Vec, - reward_token_address: Addr, - ) -> StdResult<()> { - let rewards_key = self.get_rewards_key(&reward_token_address); - let mut claimable_rewards = match Self::REWARDS.may_load(storage, rewards_key)? { - Some(data) => data, - None => vec![], - }; - - for i in claimable_rewards.len()..reward_infos.len() { - claimable_rewards.push(ClaimableRewardsInfo::new(&reward_infos[i])) - } - if claimable_rewards.len() != reward_infos.len() { - return Err(StdError::generic_err( - "Off by one error in reward list padding", - )); - } - for (reward_info, mut claimable_reward) in - reward_infos.into_iter().zip(claimable_rewards.into_iter()) - { - if reward_info.token.address != reward_token_address { - return Err(StdError::generic_err( - "Update claimable rewards bad reward token address", - )); - } - if reward_info.reward_per_staked_token - > claimable_reward.last_reward_per_staked_token_paid - { - let reward_per_staked_token_earned = reward_info.reward_per_staked_token - - claimable_reward.last_reward_per_staked_token_paid; - let normalized_amount_earned = - reward_per_staked_token_earned.checked_mul(self.total_staked().into())?; - let reward_amount_earned = - RewardTokenInfo::denormalize_amount(normalized_amount_earned)?; - claimable_reward.amount = claimable_reward - .amount - .checked_add(reward_amount_earned.into())?; - claimable_reward.last_reward_per_staked_token_paid = - reward_info.reward_per_staked_token; - } - - //load update claimable reward into object - match self.claimable_rewards.get_mut(&reward_info.token.address) { - Some(list) => list.push(claimable_reward.clone()), - None => { - let list = vec![claimable_reward.clone()]; - self.claimable_rewards - .insert(reward_info.token.address.clone(), list); - } - } - } - Ok(()) - } - - /// write data from staker object to contract storage - pub fn save_rewards(&self, storage: &mut dyn Storage) -> StdResult<()> { - for reward in &self.claimable_rewards { - let rewards_key = self.get_rewards_key(&reward.0); - Self::REWARDS.save(storage, rewards_key, &reward.1)?; - } - Ok(()) - } - - /// send all claimable rewards to staker and save 0 into claimable rewards storage - pub fn claim_and_save_rewards( - &self, - storage: &mut dyn Storage, - ) -> StdResult<(Vec, Vec)> { - let mut response_data = vec![]; - let mut msgs = vec![]; - for rewards in self.claimable_rewards.iter() { - let mut claimable_rewards = vec![]; - for reward in rewards.1 { - if !reward.amount.is_zero() { - response_data.push(ClaimRewardResponse { - token: reward.info.token.clone(), - amount: reward.amount, - }); - msgs.push( - Snip20ExecuteMsg::Send { - recipient: self.addr.to_string(), - recipient_code_hash: None, - amount: reward.amount, - msg: None, - memo: None, - padding: None, - } - .to_cosmos_msg(&reward.info.token, vec![])?, - ); - } - claimable_rewards.push(ClaimableRewardsInfo { - info: reward.info.clone(), - amount: Uint128::zero(), - last_reward_per_staked_token_paid: reward.last_reward_per_staked_token_paid, - }); - } - - let rewards_key = self.get_rewards_key(rewards.0); - Self::REWARDS.save(storage, rewards_key, &claimable_rewards)?; - } - Ok((msgs, response_data)) - } - - pub fn to_staker_info_response(self) -> StdResult { - let mut all_rewards = vec![]; - for (_, rewards) in &self.claimable_rewards { - all_rewards.append( - &mut rewards - .into_iter() - .map(|r| ClaimableRewardsResponse { - token: r.info.token.clone(), - amount: r.amount, - }) - .collect(), - ); - } - Ok(PermitQueryResponse::StakerInfo { - staked: self.staked, - total_staked: self.total_staked(), - claimable_rewards: all_rewards, - }) - } - } - - #[cfg(test)] - mod test { - use super::*; - - #[cfg(feature = "staking")] - #[test] - fn test_max_emission_rate_no_panic() { - let high_emissions = Uint128::new(exp10(77).as_u128()); - let info = RewardTokenInfo::init_from_daily_rewards( - 1u64, - &Contract::default(), - 18u8, - high_emissions, - 1u64, - ) - .unwrap(); - let info = info.to_response().unwrap(); - let rps: Uint128 = info.reward_per_second.try_into().unwrap(); - assert_eq!( - rps, - high_emissions / Uint128::new(RewardTokenInfo::SECONDS_IN_DAY.as_u128()) - ); - } - } -}