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

Feature/apollo update collateral factor #1302

Merged
merged 31 commits into from
Jan 24, 2025
Merged
Changes from 1 commit
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
7b03dd4
feat: setting up collateral factor and implementing it in borrow and …
CvijanCBS Dec 12, 2024
97738ff
Presto: publish Crop Receipt (#1294)
wer1st Dec 9, 2024
bd558e9
Presto: fix strings (#1296)
wer1st Dec 10, 2024
cda9044
cover with wip new fee events (#1297)
ZlayaMorda Dec 10, 2024
bd18a94
Presto: fix USA name (#1298)
wer1st Dec 10, 2024
b5e9551
Prepare release 4.5.2 (#1299)
vovac12 Dec 10, 2024
390df38
Presto: benchmarks (#1300)
wer1st Dec 11, 2024
eb508e5
fix: adjusting the default value for the collateral factor
CvijanCBS Dec 12, 2024
ccca556
Presto: minor fix flag
wer1st Dec 12, 2024
551f977
Change fee to small fee for vesting (#1304)
ZlayaMorda Dec 18, 2024
3581cb8
Random xor-fee remint (#1305)
vovac12 Dec 19, 2024
7c210f6
Prepare release 4.5.3 (#1306)
vovac12 Dec 20, 2024
f549480
Benchmark results of benchmarking-4.5.3 from master (#1308)
sorabot Dec 20, 2024
51fac0f
1171 xorless fee part 2 (#1290)
ZlayaMorda Dec 20, 2024
9af0b6b
aggregating total collateral amount in separate storage for easier tr…
CvijanCBS Dec 25, 2024
ba5fde9
migrations for the new approach with total collateral amount
CvijanCBS Dec 25, 2024
a62c756
test coverage of new features
CvijanCBS Dec 25, 2024
03436a1
updating typos in the lib of apollo pallet
CvijanCBS Dec 26, 2024
ba5956a
migration update
CvijanCBS Dec 26, 2024
012f168
Test coverage for updated migration
CvijanCBS Dec 26, 2024
67e03b2
minor update to migrations tests in apollo platform
CvijanCBS Dec 26, 2024
e1fe1a4
Moving runtime upgrade to empty struct (MigrateToV1) with OnRuntimeUp…
CvijanCBS Jan 23, 2025
57ed788
Presto KYC (#1309)
wer1st Jan 23, 2025
0efeb2f
fix: typo
CvijanCBS Jan 23, 2025
612281e
fix: changing migration checks for pre and post upgrade
CvijanCBS Jan 23, 2025
4b81165
Update const STORAGE_VERSION from 0 to 1
CvijanCBS Jan 24, 2025
54b66bc
Update multiplier for reading the old and new storage
CvijanCBS Jan 24, 2025
fbf090f
Fix TBC reserves distribution (#1311)
vovac12 Jan 24, 2025
eeea641
Prepare release 4.5.4 (#1312)
vovac12 Jan 24, 2025
0ba485f
feat: added apollo-platform migrations to the runtime migrations
CvijanCBS Jan 24, 2025
6f87210
Merge branch 'master' into feature/apollo-update-collateral-factor
CvijanCBS Jan 24, 2025
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
Prev Previous commit
Next Next commit
1171 xorless fee part 2 (#1290)
* add check for depth

* change assets in migration

* compute fee for order book and vested transfer

* update Carrgo.lock

* add set referrer

---------

Co-authored-by: Vladimir Stepanenko <vovac12@gmail.com>
2 people authored and CvijanCBS committed Jan 24, 2025
commit 51fac0f89a01e8ed1543ad20ca4a7ed9546ed6fc
12 changes: 0 additions & 12 deletions pallets/xor-fee/src/migrations.rs
Original file line number Diff line number Diff line change
@@ -82,10 +82,8 @@ pub mod add_white_listed_assets_for_xorless_fee {
where
T: Config,
{
// TODO: change assets
fn on_runtime_upgrade() -> Weight {
let assets: Vec<AssetIdOf<T>> = vec![
KXOR.into(),
ETH.into(),
KUSD.into(),
APOLLO_ASSET_ID.into(),
@@ -96,21 +94,11 @@ pub mod add_white_listed_assets_for_xorless_fee {
.into(), // LLD
PSWAP.into(),
DAI.into(),
AssetId32::from_bytes(hex!(
"002d4e9e03f192cc33b128319a049f353db98fbf4d98f717fd0b7f66a0462142"
))
.into(), // HMX
XSTUSD.into(),
AssetId32::from_bytes(hex!(
"0003b1dbee890acfb1b3bc12d1bb3b4295f52755423f84d1751b2545cebf000b"
))
.into(), //DOT
AssetId32::from_bytes(hex!(
"00f2f4fda40a4bf1fc3769d156fa695532eec31e265d75068524462c0b80f674"
))
.into(), //DEO
KSM.into(),
TBCD.into(),
AssetId32::from_bytes(hex!(
"00ab83f36ff0cbbdd12fd88a094818820eaf155c08c4159969f1fb21534c1eb0"
))
3 changes: 2 additions & 1 deletion runtime/src/lib.rs
Original file line number Diff line number Diff line change
@@ -376,7 +376,8 @@ pub struct BaseCallFilter;

impl Contains<RuntimeCall> for BaseCallFilter {
fn contains(call: &RuntimeCall) -> bool {
if call.swap_count() > 1 {
let depth_result = call.swap_count_and_depth(0);
if depth_result.swap_count > 1 || depth_result.depth > 1 {
return false;
}
if matches!(
447 changes: 383 additions & 64 deletions runtime/src/xor_fee_impls.rs
Original file line number Diff line number Diff line change
@@ -45,6 +45,18 @@ use sp_runtime::traits::Zero;
use sp_runtime::FixedU128;
use vested_rewards::vesting_currencies::VestingSchedule;

#[derive(Debug, PartialEq)]
pub struct CallDepth {
pub swap_count: u32,
pub depth: u32,
}

impl From<(u32, u32)> for CallDepth {
fn from((swap_count, depth): (u32, u32)) -> Self {
CallDepth { swap_count, depth }
}
}

impl RuntimeCall {
#[cfg(feature = "wip")] // EVM bridge
pub fn withdraw_evm_fee(&self, who: &AccountId) -> DispatchResult {
@@ -91,20 +103,49 @@ impl RuntimeCall {
Ok(())
}

pub fn swap_count(&self) -> u32 {
/// `vested_transfer` may be called only through `xorless_call` or manually
/// so for other extrinsics depth is 2 or more
pub fn swap_count_and_depth(&self, depth: u32) -> CallDepth {
match self {
Self::Multisig(pallet_multisig::Call::as_multi_threshold_1 { call, .. })
| Self::Multisig(pallet_multisig::Call::as_multi { call, .. })
| Self::Utility(UtilityCall::as_derivative { call, .. }) => call.swap_count(),
| Self::Utility(UtilityCall::as_derivative { call, .. }) => {
call.swap_count_and_depth(depth.saturating_add(2))
}
Self::Utility(UtilityCall::batch { calls })
| Self::Utility(UtilityCall::batch_all { calls })
| Self::Utility(UtilityCall::force_batch { calls }) => {
calls.iter().map(|call| call.swap_count()).sum()
}
| Self::Utility(UtilityCall::force_batch { calls }) => calls
.iter()
.map(|call| call.swap_count_and_depth(depth.saturating_add(2)))
.fold(
CallDepth {
swap_count: 0,
depth: 0,
},
|acc, call_depth| CallDepth {
swap_count: acc.swap_count.saturating_add(call_depth.swap_count),
depth: acc.depth.max(call_depth.depth),
},
),
Self::LiquidityProxy(liquidity_proxy::Call::swap { .. })
| Self::LiquidityProxy(liquidity_proxy::Call::swap_transfer { .. })
| Self::LiquidityProxy(liquidity_proxy::Call::swap_transfer_batch { .. }) => 1,
_ => 0,
| Self::LiquidityProxy(liquidity_proxy::Call::swap_transfer_batch { .. }) => {
CallDepth {
depth: 0,
swap_count: 1,
}
}
Self::XorFee(xor_fee::Call::xorless_call { call, .. }) => {
call.swap_count_and_depth(depth.saturating_add(1))
}
Self::VestedRewards(vested_rewards::Call::vested_transfer { .. }) => CallDepth {
depth,
swap_count: 0,
},
_ => CallDepth {
depth: 0,
swap_count: 0,
},
}
}

@@ -227,20 +268,29 @@ impl xor_fee::ApplyCustomFees<RuntimeCall, AccountId> for CustomFees {
fn compute_fee(call: &RuntimeCall) -> Option<(Balance, CustomFeeDetails)> {
let mut fee = Self::base_fee(call)?;

let details = match call {
RuntimeCall::OrderBook(order_book::Call::place_limit_order { lifespan, .. }) => {
CustomFeeDetails::LimitOrderLifetime(*lifespan)
}
RuntimeCall::VestedRewards(vested_rewards::Call::vested_transfer {
schedule, ..
}) => {
// claim fee = SMALL_FEE
let whole_claims_fee = SMALL_FEE.saturating_mul(schedule.claims_count() as Balance);
let fee_without_claims = fee;
fee = fee.saturating_add(whole_claims_fee);
CustomFeeDetails::VestedTransferClaims((fee, fee_without_claims))
let mut compute_details = |call: &RuntimeCall| -> CustomFeeDetails {
match call {
RuntimeCall::OrderBook(order_book::Call::place_limit_order {
lifespan, ..
}) => CustomFeeDetails::LimitOrderLifetime(*lifespan),
RuntimeCall::VestedRewards(vested_rewards::Call::vested_transfer {
schedule,
..
}) => {
// claim fee = SMALL_FEE
let whole_claims_fee =
SMALL_FEE.saturating_mul(schedule.claims_count() as Balance);
let fee_without_claims = fee;
fee = fee.saturating_add(whole_claims_fee);
CustomFeeDetails::VestedTransferClaims((fee, fee_without_claims))
}
_ => CustomFeeDetails::Regular(fee),
}
_ => CustomFeeDetails::Regular(fee),
};

let details = match call {
RuntimeCall::XorFee(xor_fee::Call::xorless_call { call, .. }) => compute_details(call),
call => compute_details(call),
};

Some((fee, details))
@@ -407,13 +457,19 @@ impl xor_fee::ApplyCustomFees<RuntimeCall, AccountId> for CustomFees {
}

fn get_fee_source(who: &AccountId, call: &RuntimeCall, _fee: Balance) -> AccountId {
match call {
RuntimeCall::Referrals(referrals::Call::set_referrer { .. })
if Referrals::can_set_referrer(who) =>
{
ReferralsReservesAcc::get()
let fee_source = |call: &RuntimeCall| -> AccountId {
match call {
RuntimeCall::Referrals(referrals::Call::set_referrer { .. })
if Referrals::can_set_referrer(who) =>
{
ReferralsReservesAcc::get()
}
_ => who.clone(),
}
_ => who.clone(),
};
match call {
RuntimeCall::XorFee(xor_fee::Call::xorless_call { call, .. }) => fee_source(call),
call => fee_source(call),
}
}
}
@@ -435,7 +491,6 @@ impl xor_fee::WithdrawFee<Runtime> for WithdrawFee {
DispatchError,
> {
match call {
// TODO: remake for xorless
RuntimeCall::Referrals(referrals::Call::set_referrer { referrer })
// Fee source should be set to referrer by `get_fee_source` method, if not
// it means that user can't set referrer
@@ -444,38 +499,48 @@ impl xor_fee::WithdrawFee<Runtime> for WithdrawFee {
Referrals::withdraw_fee(referrer, fee)?;
}
#[allow(unused_variables)] // Xorless fee
RuntimeCall::XorFee(xor_fee::Call::xorless_call {call: _, asset_id}) => {
RuntimeCall::XorFee(xor_fee::Call::xorless_call {call, asset_id}) => {
#[cfg(feature = "wip")] // Xorless fee
match *asset_id {
None => {},
Some(asset_id) if XorFee::whitelist_tokens().contains(&asset_id) => {
let asset_fee = FixedWrapper::from(
PriceTools::get_average_price(
&GetXorAssetId::get(),
&asset_id,
PriceVariant::Buy)?
) * fee;
let asset_fee = asset_fee.into_balance();
if asset_fee.lt(&MinimalFeeInAsset::get()) {
return Err(xor_fee::Error::<Runtime>::FeeCalculationFailed.into())
};
return Ok((
fee_source.clone(),
Some(Tokens::withdraw(
asset_id,
fee_source,
asset_fee,
).map(|_| {
NegativeImbalanceOf::<Runtime>::new(asset_fee)
})?),
Some(asset_id),
))
match call.as_ref() {
RuntimeCall::Referrals(referrals::Call::set_referrer { referrer })
// Fee source should be set to referrer by `get_fee_source` method, if not
// it means that user can't set referrer
if Referrals::can_set_referrer(who) =>
{
Referrals::withdraw_fee(referrer, fee)?;
}
_ => {
match *asset_id {
None => {},
Some(asset_id) if XorFee::whitelist_tokens().contains(&asset_id) => {
let asset_fee = FixedWrapper::from(
PriceTools::get_average_price(
&GetXorAssetId::get(),
&asset_id,
PriceVariant::Buy)?
) * fee;
let asset_fee = asset_fee.into_balance();
if asset_fee.lt(&MinimalFeeInAsset::get()) {
return Err(xor_fee::Error::<Runtime>::FeeCalculationFailed.into())
};
return Ok((
fee_source.clone(),
Some(Tokens::withdraw(
asset_id,
fee_source,
asset_fee,
).map(|_| {
NegativeImbalanceOf::<Runtime>::new(asset_fee)
})?),
Some(asset_id),
))
}
_ => { return Err(xor_fee::Error::<Runtime>::AssetNotFound.into()) }
}
}
_ => { return Err(xor_fee::Error::<Runtime>::AssetNotFound.into()) }
}
}
_ => {
}
_ => {}
}
#[cfg(feature = "wip")] // EVM bridge
call.withdraw_evm_fee_nested(who)?;
@@ -518,10 +583,14 @@ mod tests {
use pallet_utility::Call as UtilityCall;
use sp_core::H256;
use sp_runtime::AccountId32;

use common::{balance, VAL, XOR};

use crate::{xor_fee_impls::CustomFees, *};
use vested_rewards::vesting_currencies::{LinearVestingSchedule, VestingScheduleVariant};

use crate::{
xor_fee_impls::{CallDepth, CustomFeeDetails, CustomFees},
*,
};
use common::OrderBookId;
use common::{balance, PriceVariant, VAL, XOR};
use xor_fee::ApplyCustomFees;

#[test]
@@ -563,7 +632,63 @@ mod tests {
amount: balance!(100),
});

assert_eq!(call.swap_count(), 0);
assert_eq!(
call.swap_count_and_depth(0),
CallDepth {
swap_count: 0,
depth: 0,
}
);

let schedule = VestingScheduleVariant::LinearVestingSchedule(LinearVestingSchedule {
asset_id: DOT,
start: 0u32,
period: 10u32,
period_count: 2u32,
per_period: 10,
remainder_amount: 0,
});
let call = RuntimeCall::VestedRewards(vested_rewards::Call::vested_transfer {
dest: From::from([1; 32]),
schedule: schedule.clone(),
});

assert_eq!(
call.swap_count_and_depth(0),
CallDepth {
swap_count: 0,
depth: 0,
}
);
}

#[test]
fn xorless_call_vesting_should_pass() {
let schedule = VestingScheduleVariant::LinearVestingSchedule(LinearVestingSchedule {
asset_id: DOT,
start: 0u32,
period: 10u32,
period_count: 2u32,
per_period: 10,
remainder_amount: 0,
});
let call = RuntimeCall::XorFee(xor_fee::Call::xorless_call {
call: Box::new(RuntimeCall::VestedRewards(
vested_rewards::Call::vested_transfer {
dest: From::from([1; 32]),
schedule: schedule.clone(),
},
)),
asset_id: None,
});

assert_eq!(
call.swap_count_and_depth(0),
CallDepth {
swap_count: 0,
depth: 1,
}
);
}

#[test]
@@ -588,8 +713,93 @@ mod tests {
});
let call_batch_all = RuntimeCall::Utility(UtilityCall::batch_all { calls: batch_calls });

assert_eq!(call_batch.swap_count(), 0);
assert_eq!(call_batch_all.swap_count(), 0);
assert_eq!(
call_batch.swap_count_and_depth(0),
CallDepth {
swap_count: 0,
depth: 0,
}
);
assert_eq!(
call_batch_all.swap_count_and_depth(0),
CallDepth {
swap_count: 0,
depth: 0,
}
);
}

#[test]
fn regular_batch_should_not_pass_for_vesting() {
let schedule = VestingScheduleVariant::LinearVestingSchedule(LinearVestingSchedule {
asset_id: DOT,
start: 0u32,
period: 10u32,
period_count: 2u32,
per_period: 10,
remainder_amount: 0,
});
let call = RuntimeCall::VestedRewards(vested_rewards::Call::vested_transfer {
dest: From::from([1; 32]),
schedule: schedule.clone(),
});
let batch_calls = vec![
call,
assets::Call::transfer {
asset_id: GetBaseAssetId::get(),
to: From::from([1; 32]),
amount: balance!(100),
}
.into(),
];

let call_batch = RuntimeCall::Utility(UtilityCall::batch {
calls: batch_calls.clone(),
});
let call_batch_all = RuntimeCall::Utility(UtilityCall::batch_all { calls: batch_calls });

assert_eq!(
call_batch.swap_count_and_depth(0),
CallDepth {
swap_count: 0,
depth: 2,
}
);
assert_eq!(
call_batch_all.swap_count_and_depth(0),
CallDepth {
swap_count: 0,
depth: 2,
}
);
}

#[test]
fn no_direct_call_not_work_for_vesting() {
let schedule = VestingScheduleVariant::LinearVestingSchedule(LinearVestingSchedule {
asset_id: DOT,
start: 0u32,
period: 10u32,
period_count: 2u32,
per_period: 10,
remainder_amount: 0,
});
let call = Box::new(RuntimeCall::VestedRewards(
vested_rewards::Call::vested_transfer {
dest: From::from([1; 32]),
schedule: schedule.clone(),
},
));

let utility_call = RuntimeCall::Utility(UtilityCall::as_derivative { index: 0, call });

assert_eq!(
utility_call.swap_count_and_depth(0),
CallDepth {
depth: 2,
swap_count: 0
}
);
}

fn test_swap_in_batch(call: RuntimeCall) {
@@ -608,8 +818,20 @@ mod tests {
});
let call_batch_all = RuntimeCall::Utility(UtilityCall::batch_all { calls: batch_calls });

assert_eq!(call_batch.swap_count(), 1);
assert_eq!(call_batch_all.swap_count(), 1);
assert_eq!(
call_batch.swap_count_and_depth(0),
CallDepth {
swap_count: 1,
depth: 0,
}
);
assert_eq!(
call_batch_all.swap_count_and_depth(0),
CallDepth {
swap_count: 1,
depth: 0,
}
);

assert!(crate::BaseCallFilter::contains(&call_batch));
assert!(crate::BaseCallFilter::contains(&call_batch_all));
@@ -651,4 +873,101 @@ mod tests {
.into(),
);
}

#[test]
fn compute_fee_works_fine() {
// compute fee works fine for vested transfer

let schedule = VestingScheduleVariant::LinearVestingSchedule(LinearVestingSchedule {
asset_id: DOT,
start: 0u32,
period: 10u32,
period_count: 2u32,
per_period: 10,
remainder_amount: 0,
});

let fee = 3 * SMALL_FEE;
let fee_without_claims = SMALL_FEE;

let vesting_call = RuntimeCall::VestedRewards(vested_rewards::Call::vested_transfer {
dest: From::from([1; 32]),
schedule,
});
let xorless_call_vesting = RuntimeCall::XorFee(xor_fee::Call::xorless_call {
call: Box::new(vesting_call.clone()),
asset_id: None,
});
assert_eq!(
CustomFees::compute_fee(&xorless_call_vesting),
Some((
fee,
CustomFeeDetails::VestedTransferClaims((fee, fee_without_claims))
))
);
assert_eq!(
CustomFees::compute_fee(&vesting_call),
Some((
fee,
CustomFeeDetails::VestedTransferClaims((fee, fee_without_claims))
))
);

// compute fee works fine for order book

let order_book_id = OrderBookId {
dex_id: common::DEXId::Polkaswap.into(),
base: VAL.into(),
quote: XOR.into(),
};
let order_call = RuntimeCall::OrderBook(order_book::Call::place_limit_order {
order_book_id,
price: balance!(11),
amount: balance!(100),
side: PriceVariant::Sell,
lifespan: None,
});
let xorless_call = RuntimeCall::XorFee(xor_fee::Call::xorless_call {
call: Box::new(order_call.clone()),
asset_id: None,
});
assert_eq!(
CustomFees::compute_fee(&xorless_call),
Some((SMALL_FEE, CustomFeeDetails::LimitOrderLifetime(None)))
);
assert_eq!(
CustomFees::compute_fee(&order_call),
Some((SMALL_FEE, CustomFeeDetails::LimitOrderLifetime(None)))
);

// compute fee works fine for Some predefined fee

let transfer_call = RuntimeCall::Assets(assets::Call::transfer {
asset_id: GetBaseAssetId::get(),
to: From::from([1; 32]),
amount: balance!(100),
});
let xorless_call = RuntimeCall::XorFee(xor_fee::Call::xorless_call {
call: Box::new(transfer_call.clone()),
asset_id: None,
});
assert_eq!(
CustomFees::compute_fee(&transfer_call),
Some((SMALL_FEE, CustomFeeDetails::Regular(SMALL_FEE)))
);
assert_eq!(
CustomFees::compute_fee(&xorless_call),
Some((SMALL_FEE, CustomFeeDetails::Regular(SMALL_FEE)))
);

// compute fee works fine for others

let set_call = RuntimeCall::Timestamp(pallet_timestamp::Call::set { now: 1_u64 });
let xorless_call = RuntimeCall::XorFee(xor_fee::Call::xorless_call {
call: Box::new(set_call.clone()),
asset_id: None,
});
assert_eq!(CustomFees::compute_fee(&set_call), None);
assert_eq!(CustomFees::compute_fee(&xorless_call), None);
}
}