Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

feat: liquidity mining - use underlying asset's oracle for bonds distribution #762

Merged
merged 6 commits into from
Feb 22, 2024
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Cargo.lock

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

155 changes: 141 additions & 14 deletions integration-tests/src/omnipool_liquidity_mining.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,17 @@ use warehouse_liquidity_mining::{
};

use orml_traits::MultiCurrency;
use primitives::AssetId;
use primitives::{constants::currency::UNITS, AssetId};
use sp_runtime::{
traits::{One, Zero},
FixedPointNumber, FixedU128, Permill, Perquintill,
};
use xcm_emulator::TestExt;

use hydradx_runtime::{Balance, RuntimeOrigin};
use hydradx_runtime::{AssetRegistry, Balance, Bonds, RuntimeOrigin};
use pallet_asset_registry::AssetType;
use pretty_assertions::assert_eq;
use primitives::constants::time::unix_time::MONTH;

#[macro_export]
macro_rules! assert_nft_owner {
Expand Down Expand Up @@ -113,7 +115,7 @@ fn create_yield_farm_should_work_when_asset_is_in_omnipool() {
init_omnipool();

set_relaychain_block_number(100);
create_global_farm();
create_global_farm(None);

set_relaychain_block_number(200);
assert_ok!(hydradx_runtime::OmnipoolLiquidityMining::create_yield_farm(
Expand Down Expand Up @@ -152,7 +154,7 @@ fn deposit_shares_should_work_when_yield_farm_exists() {
//NOTE: necessary to get oracle price.
hydradx_run_to_block(100);
set_relaychain_block_number(100);
create_global_farm();
create_global_farm(None);

set_relaychain_block_number(200);
create_yield_farm(global_farm_id, ETH);
Expand Down Expand Up @@ -223,8 +225,8 @@ fn redeposit_shares_multiple_times_should_work_when_shares_already_deposited() {
//NOTE: necessary to get oracle price.
hydradx_run_to_block(100);
set_relaychain_block_number(100);
create_global_farm();
create_global_farm();
create_global_farm(None);
create_global_farm(None);

set_relaychain_block_number(200);
create_yield_farm(global_farm_1_id, ETH);
Expand Down Expand Up @@ -314,8 +316,8 @@ fn claim_rewards_should_work_when_rewards_are_accumulated_for_deposit() {
//NOTE: necessary to get oracle price.
hydradx_run_to_block(100);
set_relaychain_block_number(100);
create_global_farm();
create_global_farm();
create_global_farm(None);
create_global_farm(None);

set_relaychain_block_number(200);
create_yield_farm(global_farm_1_id, ETH);
Expand Down Expand Up @@ -419,8 +421,8 @@ fn withdraw_shares_should_work_when_deposit_exists() {
//NOTE: necessary to get oracle price.
hydradx_run_to_block(100);
set_relaychain_block_number(100);
create_global_farm();
create_global_farm();
create_global_farm(None);
create_global_farm(None);

set_relaychain_block_number(200);
create_yield_farm(global_farm_1_id, ETH);
Expand Down Expand Up @@ -595,7 +597,7 @@ fn init_omnipool() {
));
}

fn create_global_farm() {
fn create_global_farm(rewards_currency: Option<AssetId>) {
let total_rewards = 1_000_000 * UNITS;

assert_ok!(hydradx_runtime::Balances::force_set_balance(
Expand All @@ -609,7 +611,7 @@ fn create_global_farm() {
total_rewards,
1_000_000,
10,
HDX,
rewards_currency.unwrap_or(HDX),
Treasury::account_id(),
Perquintill::from_parts(570_776_255_707),
1_000,
Expand Down Expand Up @@ -688,7 +690,7 @@ fn position_should_be_valued_correctly_when_oracle_is_used() {
//NOTE: necessary to get oracle price.
hydradx_run_to_block(100);
set_relaychain_block_number(100);
create_global_farm();
create_global_farm(None);

set_relaychain_block_number(200);
create_yield_farm(global_farm_id, ETH);
Expand Down Expand Up @@ -767,7 +769,7 @@ fn price_adjustment_from_oracle_should_be_saved_in_global_farm_when_oracle_is_av
//NOTE: necessary to get oracle price.
hydradx_run_to_block(100);
set_relaychain_block_number(100);
create_global_farm();
create_global_farm(None);

set_relaychain_block_number(200);
create_yield_farm(global_farm_1_id, ETH);
Expand Down Expand Up @@ -814,3 +816,128 @@ fn price_adjustment_from_oracle_should_be_saved_in_global_farm_when_oracle_is_av
);
});
}

#[test]
fn liquidity_mining_should_work_when_farm_distribute_bonds() {
TestNet::reset();

Hydra::execute_with(|| {
let global_farm_1_id = 1;
let yield_farm_1_id = 2;

//Arrange
init_omnipool();
seed_lm_pot();
//necessary for oracle to have a price.
do_lrna_hdx_trade();

//Create bodns
assert_ok!(hydradx_runtime::Balances::force_set_balance(
hydradx_runtime::RuntimeOrigin::root(),
Treasury::account_id(),
2_000_000 * UNITS,
));

let maturity = NOW + MONTH;
let bond_id = AssetRegistry::next_asset_id().unwrap();
assert_ok!(Bonds::issue(
RuntimeOrigin::signed(Treasury::account_id()),
HDX,
2_000_000 * UNITS,
maturity
));
assert_eq!(AssetRegistry::assets(bond_id).unwrap().asset_type, AssetType::Bond);
//NOTE: make bond sufficient because treasury account is whitelisted. In this case farm
//would have to pay ED for receiving insufficicient bods and farm's account has no balance.
assert_ok!(AssetRegistry::update(
hydradx_runtime::RuntimeOrigin::root(),
bond_id,
None,
None,
None,
None,
Some(true),
None,
None,
None,
));

// farm's rewards in test are less than ED.
assert_ok!(hydradx_runtime::Currencies::transfer(
hydradx_runtime::RuntimeOrigin::signed(Treasury::account_id()),
CHARLIE.into(),
bond_id,
2 * UNITS,
));

//NOTE: necessary to get oracle price.
hydradx_run_to_block(100);
set_relaychain_block_number(100);
create_global_farm(Some(bond_id));

set_relaychain_block_number(200);
create_yield_farm(global_farm_1_id, ETH);

set_relaychain_block_number(300);

assert_ok!(hydradx_runtime::Currencies::update_balance(
hydradx_runtime::RuntimeOrigin::root(),
CHARLIE.into(),
ETH,
10_000 * UNITS as i128,
));

let position_id = omnipool_add_liquidity(CHARLIE.into(), ETH, 1_000 * UNITS);
assert_nft_owner!(
hydradx_runtime::OmnipoolCollectionId::get(),
position_id,
CHARLIE.into()
);

set_relaychain_block_number(400);
let deposit_id = 1;
assert_ok!(hydradx_runtime::OmnipoolLiquidityMining::deposit_shares(
RuntimeOrigin::signed(CHARLIE.into()),
global_farm_1_id,
yield_farm_1_id,
position_id
));

let charlie_bonds_balance_0 = hydradx_runtime::Currencies::free_balance(bond_id, &CHARLIE.into());
set_relaychain_block_number(600);
assert_ok!(hydradx_runtime::OmnipoolLiquidityMining::claim_rewards(
RuntimeOrigin::signed(CHARLIE.into()),
deposit_id,
yield_farm_1_id
));

//Assert
let expected_claimed_amount = 393_607_131_u128;
assert_eq!(
hydradx_runtime::Currencies::free_balance(bond_id, &CHARLIE.into()),
charlie_bonds_balance_0 + expected_claimed_amount
);

set_relaychain_block_number(700);
let charlie_bonds_balance_0 = hydradx_runtime::Currencies::free_balance(bond_id, &CHARLIE.into());
assert_ok!(hydradx_runtime::OmnipoolLiquidityMining::withdraw_shares(
RuntimeOrigin::signed(CHARLIE.into()),
deposit_id,
yield_farm_1_id
));

let expected_claimed_amount = 229_243_713_u128;
assert_eq!(
hydradx_runtime::Currencies::free_balance(bond_id, &CHARLIE.into()),
charlie_bonds_balance_0 + expected_claimed_amount
);

// NOTE: make sure oracle's price adjustment was used.
let global_farm = hydradx_runtime::OmnipoolWarehouseLM::global_farm(global_farm_1_id).unwrap();
let price_adjustment = DefaultPriceAdjustment::get(&global_farm).unwrap();
assert_eq!(
price_adjustment,
FixedU128::from_inner(830_817_151_946_084_689_817_u128)
);
});
}
12 changes: 11 additions & 1 deletion pallets/bonds/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ use frame_support::{
};
use frame_system::{ensure_signed, pallet_prelude::OriginFor};
use sp_core::MaxEncodedLen;
use sp_std::vec::Vec;
use sp_std::{mem, vec::Vec};

use hydradx_traits::{
registry::{Create, Inspect},
Expand Down Expand Up @@ -185,6 +185,8 @@ pub mod pallet {
AssetNotFound,
/// Generated name is not valid.
InvalidBondName,
/// Bond's name parsing was now successful
FailToParseName,
}

#[pallet::call]
Expand Down Expand Up @@ -326,4 +328,12 @@ impl<T: Config> Pallet<T> {

buf
}

pub fn parse_bond_name(name: Vec<u8>) -> Result<AssetId, Error<T>> {
Ok(AssetId::from_le_bytes(
name[..mem::size_of::<AssetId>()]
.try_into()
.map_err(|_| Error::<T>::FailToParseName)?,
))
}
}
2 changes: 2 additions & 0 deletions pallets/bonds/src/tests/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
mod issue;
pub mod mock;
mod redeem;
#[allow(clippy::module_inception)]
mod tests;
37 changes: 37 additions & 0 deletions pallets/bonds/src/tests/tests.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// This file is part of HydraDX.

// Copyright (C) 2020-2022 Intergalactic, Limited (GIB).
// SPDX-License-Identifier: Apache-2.0

// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use crate::tests::mock::*;
use crate::*;

#[test]
fn parse_name_should_work() {
let n = Pallet::<Test>::bond_name(0_u32, 1689844300000_u64);
assert_eq!(Pallet::<Test>::parse_bond_name(n), Ok(0_u32));

let n = Pallet::<Test>::bond_name(u32::max_value(), 1689844300000_u64);
assert_eq!(Pallet::<Test>::parse_bond_name(n), Ok(u32::max_value()));

let n = Pallet::<Test>::bond_name(1, 1689844300000_u64);
assert_eq!(Pallet::<Test>::parse_bond_name(n), Ok(1));

let n = Pallet::<Test>::bond_name(13_124, 1689844300000_u64);
assert_eq!(Pallet::<Test>::parse_bond_name(n), Ok(13_124));

let n = Pallet::<Test>::bond_name(789_970_979, 1689844300000_u64);
assert_eq!(Pallet::<Test>::parse_bond_name(n), Ok(789_970_979));
}
5 changes: 5 additions & 0 deletions runtime/adapters/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ pallet-currencies = { workspace = true }
pallet-stableswap = { workspace = true }
pallet-referrals = { workspace = true }
pallet-lbp = { workspace = true }
pallet-asset-registry = { workspace = true }
pallet-bonds = { workspace = true }

# Substrate dependencies
frame-support = { workspace = true }
Expand Down Expand Up @@ -87,4 +89,7 @@ std = [
"cumulus-pallet-parachain-system/std",
"polkadot-parachain/std",
"orml-tokens/std",
"pallet-asset-registry/std",
"pallet-omnipool-liquidity-mining/std",
"pallet-bonds/std",
]
24 changes: 21 additions & 3 deletions runtime/adapters/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@

#![cfg_attr(not(feature = "std"), no_std)]

use codec::FullCodec;
use codec::{EncodeLike, FullCodec};
use cumulus_primitives_core::relay_chain::Hash;
use frame_support::{
sp_runtime::{
Expand Down Expand Up @@ -629,14 +629,32 @@ impl<Runtime, LMInstance> PriceAdjustment<GlobalFarmData<Runtime, LMInstance>>
where
Runtime: warehouse_liquidity_mining::Config<LMInstance>
+ pallet_ema_oracle::Config
+ pallet_omnipool_liquidity_mining::Config,
+ pallet_omnipool_liquidity_mining::Config
+ pallet_asset_registry::Config
+ pallet_bonds::Config,
u32: EncodeLike<<Runtime as pallet_asset_registry::Config>::AssetId>,
{
type Error = DispatchError;
type PriceAdjustment = FixedU128;

fn get(global_farm: &GlobalFarmData<Runtime, LMInstance>) -> Result<Self::PriceAdjustment, Self::Error> {
use pallet_asset_registry::AssetType;

let asset_detail = pallet_asset_registry::Assets::<Runtime>::get(global_farm.reward_currency.into())
.ok_or(pallet_omnipool_liquidity_mining::Error::<Runtime>::PriceAdjustmentNotAvailable)?;

let reward_currency_id = if asset_detail.asset_type == AssetType::Bond {
let name = asset_detail
.name
.ok_or(pallet_omnipool_liquidity_mining::Error::<Runtime>::PriceAdjustmentNotAvailable)?;

pallet_bonds::Pallet::<Runtime>::parse_bond_name(name.into())?
} else {
global_farm.reward_currency.into()
};

let (price, _) = pallet_ema_oracle::Pallet::<Runtime>::get_price(
global_farm.reward_currency.into(),
reward_currency_id,
global_farm.incentivized_asset.into(), //LRNA
OraclePeriod::TenMinutes,
OMNIPOOL_SOURCE,
Expand Down
Loading