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: OTC fees #863

Merged
merged 18 commits into from
Jul 16, 2024
Merged
12 changes: 6 additions & 6 deletions Cargo.lock

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

2 changes: 1 addition & 1 deletion pallets/otc-settlements/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = 'pallet-otc-settlements'
version = '1.0.0'
version = '1.0.1'
description = 'A pallet with offchain worker closing OTC arbs'
authors = ['GalacticCouncil']
edition = '2021'
Expand Down
19 changes: 16 additions & 3 deletions pallets/otc-settlements/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,10 @@ impl<T: Config> Pallet<T> {
/// - `amount`: Amount necessary to close the arb.
/// - `route`: The route we trade against. Required for the fee calculation.
pub fn settle_otc(otc_id: OrderId, amount: Balance, route: Vec<Trade<AssetIdOf<T>>>) -> DispatchResult {
log::debug!(
target: "offchain_worker::settle_otc",
"calling settle_otc(): otc_id: {:?} amount: {:?} route: {:?}", otc_id, amount, route);

let pallet_acc = Self::account_id();

let otc = <pallet_otc::Orders<T>>::get(otc_id).ok_or(Error::<T>::OrderNotFound)?;
Expand All @@ -315,8 +319,7 @@ impl<T: Config> Pallet<T> {
<T as Config>::Currency::mint_into(asset_a, &pallet_acc, amount)?;

// get initial otc price
let otc_price =
FixedU128::checked_from_rational(otc.amount_out, otc.amount_in).ok_or(ArithmeticError::Overflow)?;
let otc_price = Self::otc_price(&otc)?;

// Router trade is disabled in the benchmarks, so disable this one as well.
// Without disabling it, the requirements for the extrinsic cannot be met (e.g. profit).
Expand Down Expand Up @@ -456,7 +459,7 @@ impl<T: Config> Pallet<T> {

let mut list = vec![];
for (otc_id, otc) in <pallet_otc::Orders<T>>::iter() {
let otc_price = FixedU128::checked_from_rational(otc.amount_out, otc.amount_in);
let otc_price = Self::otc_price(&otc).ok();

let route = T::Router::get_route(AssetPair {
// To get the correct price, we need to switch the assets, otherwise
Expand Down Expand Up @@ -594,4 +597,14 @@ impl<T: Config> Pallet<T> {
}
None
}

/// Calculates the price (asset_out/asset_in) after subtracting the OTC fee from the amount_out.
fn otc_price(otc: &Order<T::AccountId, T::AssetId>) -> Result<FixedU128, DispatchError> {
let fee = pallet_otc::Pallet::<T>::calculate_fee(otc.amount_out);
Ok(FixedU128::checked_from_rational(
otc.amount_out.checked_sub(fee).ok_or(ArithmeticError::Overflow)?,
otc.amount_in,
)
.ok_or(ArithmeticError::Overflow)?)
}
}
3 changes: 3 additions & 0 deletions pallets/otc-settlements/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ parameter_types! {
pub MinProfitLimit: Balance = 10_000_000_000_000;
pub PricePrecision: FixedU128 = FixedU128::from_rational(1, 1_000_000);
pub MinProfitPercentage: Perbill = Perbill::from_rational(1u32, 100_000_u32); // 0.001%
pub OtcFee: Permill = Permill::from_percent(1u32);
}

parameter_type_with_key! {
Expand Down Expand Up @@ -107,6 +108,8 @@ impl pallet_otc::Config for Test {
type RuntimeEvent = RuntimeEvent;
type ExistentialDeposits = ExistentialDeposits;
type ExistentialDepositMultiplier = ExistentialDepositMultiplier;
type Fee = OtcFee;
type FeeReceiver = TreasuryAccount;
type WeightInfo = ();
}

Expand Down
40 changes: 19 additions & 21 deletions pallets/otc-settlements/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@ fn profit_should_be_transferred_to_treasury_when_zero_initial_pallet_balance() {
HDX,
DAI,
100_000 * ONE,
201_000 * ONE,
205_000 * ONE,
true,
));

Expand Down Expand Up @@ -226,7 +226,7 @@ fn profit_should_be_transferred_to_treasury_when_nonzero_initial_pallet_balance(
HDX,
DAI,
100_000 * ONE,
201_000 * ONE,
205_000 * ONE,
true,
));

Expand Down Expand Up @@ -258,7 +258,7 @@ fn existing_arb_opportunity_should_trigger_trade() {
HDX, // otc asset_in
DAI, // otc asset_out
100_000 * ONE,
201_000 * ONE,
205_000 * ONE,
true,
));

Expand Down Expand Up @@ -294,7 +294,7 @@ fn existing_arb_opportunity_should_trigger_trade() {

expect_last_events(vec![Event::Executed {
asset_id: HDX,
profit: 2_067_802_207_347,
profit: 17_736_110_470_326,
}
.into()]);
});
Expand All @@ -308,10 +308,8 @@ fn existing_arb_opportunity_should_trigger_trade_when_otc_is_not_partially_filla
RuntimeOrigin::signed(ALICE),
HDX, // otc asset_in
DAI, // otc asset_out
// we got following 2 values from the logs of
// existing_arb_opportunity_should_trigger_trade test
828_170_776_368_178,
1_664_623_260_500_037,
100_000 * ONE,
300_000 * ONE,
false, // not partially fillable
));

Expand Down Expand Up @@ -347,7 +345,7 @@ fn existing_arb_opportunity_should_trigger_trade_when_otc_is_not_partially_filla

expect_last_events(vec![Event::Executed {
asset_id: HDX,
profit: 2_067_802_207_347,
profit: 2_732_618_471_117_260,
}
.into()]);
});
Expand All @@ -365,7 +363,7 @@ fn existing_arb_opportunity_of_insufficient_asset_should_trigger_trade() {
BTC, // otc asset_in
HDX, // otc asset_out
200_000 * ONE,
101_000 * ONE,
105_000 * ONE,
true,
));

Expand Down Expand Up @@ -401,7 +399,7 @@ fn existing_arb_opportunity_of_insufficient_asset_should_trigger_trade() {

expect_last_events(vec![Event::Executed {
asset_id: BTC,
profit: 16_419_654_005_878,
profit: 245_338_363_920_576,
}
.into()]);
});
Expand All @@ -416,15 +414,15 @@ fn multiple_arb_opportunities_should_trigger_trades() {
HDX, // otc asset_in
DAI, // otc asset_out
100_000 * ONE,
201_000 * ONE,
205_000 * ONE,
true,
));
assert_ok!(OTC::place_order(
RuntimeOrigin::signed(ALICE),
DOT, // otc asset_in
KSM, // otc asset_out
100_000 * ONE,
101_000 * ONE,
102_000 * ONE,
true,
));

Expand All @@ -433,12 +431,12 @@ fn multiple_arb_opportunities_should_trigger_trades() {
expect_events(vec![
Event::Executed {
asset_id: HDX,
profit: 2_067_802_207_347,
profit: 17736110470326,
}
.into(),
Event::Executed {
asset_id: DOT,
profit: 12_345_450_557_916,
profit: 11860096179879,
}
.into(),
]);
Expand Down Expand Up @@ -476,7 +474,7 @@ fn trade_should_be_triggered_when_arb_opportunity_appears() {
<OtcSettlements as Hooks<BlockNumberFor<Test>>>::offchain_worker(System::block_number());

// make a trade to move the price and create an arb opportunity
assert_ok!(Omnipool::sell(RuntimeOrigin::signed(ALICE), HDX, DAI, 1_000 * ONE, ONE,));
assert_ok!(Omnipool::sell(RuntimeOrigin::signed(ALICE), HDX, DAI, 3_000 * ONE, ONE,));

System::set_block_number(System::block_number() + 1);

Expand Down Expand Up @@ -510,7 +508,7 @@ fn trade_should_be_triggered_when_arb_opportunity_appears() {

expect_last_events(vec![Event::Executed {
asset_id: HDX,
profit: 2_981_065_139_674,
profit: 5_173_145_606_735,
}
.into()]);
});
Expand Down Expand Up @@ -619,7 +617,7 @@ fn test_offchain_worker_unsigned_transaction_submission() {
HDX, // otc asset_in
DAI, // otc asset_out
100_000 * ONE,
201_000 * ONE,
205_000 * ONE,
true,
));

Expand All @@ -641,7 +639,7 @@ fn test_offchain_worker_unsigned_transaction_submission() {
tx.call,
crate::mock::RuntimeCall::OtcSettlements(crate::Call::settle_otc_order {
otc_id: 0,
amount: 828_170_776_368_178,
amount: 2_413_749_694_825_193,
route,
})
);
Expand All @@ -657,7 +655,7 @@ fn test_offchain_worker_signed_transaction_submission() {
HDX, // otc asset_in
DAI, // otc asset_out
100_000 * ONE,
201_000 * ONE,
205_000 * ONE,
true,
));

Expand All @@ -671,7 +669,7 @@ fn test_offchain_worker_signed_transaction_submission() {
assert_ok!(OtcSettlements::settle_otc_order(
RuntimeOrigin::signed(ALICE),
otc_id,
828_170_776_368_178,
2_413_749_694_825_193,
route,
));
})
Expand Down
9 changes: 6 additions & 3 deletions pallets/otc-settlements/src/weights.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
// This file is part of HydraDX.

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

// Licensed under the Apache License, Version 2.0 (the "License");
Expand All @@ -15,7 +18,7 @@
//! Autogenerated weights for `pallet_otc_settlements`
//!
//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0
//! DATE: 2024-06-20, STEPS: `10`, REPEAT: `30`, LOW RANGE: `[]`, HIGH RANGE: `[]`
//! DATE: 2024-07-12, STEPS: `10`, REPEAT: `30`, LOW RANGE: `[]`, HIGH RANGE: `[]`
//! WORST CASE MAP SIZE: `1000000`
//! HOSTNAME: `bench-bot`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz`
//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("dev")`, DB CACHE: `1024`
Expand Down Expand Up @@ -61,8 +64,8 @@ impl WeightInfo for () {
// Proof Size summary in bytes:
// Measured: `747`
// Estimated: `6196`
// Minimum execution time: 100_657_000 picoseconds.
Weight::from_parts(101_597_000, 6196)
// Minimum execution time: 97_577_000 picoseconds.
Weight::from_parts(98_741_000, 6196)
.saturating_add(RocksDbWeight::get().reads(4_u64))
.saturating_add(RocksDbWeight::get().writes(1_u64))
}
Expand Down
2 changes: 1 addition & 1 deletion pallets/otc/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = 'pallet-otc'
version = '1.1.5'
version = '2.0.0'
description = 'A pallet for trustless over-the-counter trading'
authors = ['GalacticCouncil']
edition = '2021'
Expand Down
3 changes: 2 additions & 1 deletion pallets/otc/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
## General description
This pallet provides basic over-the-counter (OTC) trading functionality.
It allows anyone to `place_order` by specifying a pair of assets (in and out), their respective amounts, and
whether the order is partially fillable. The order price is static and calculated as `amount_out / amount_in`.
whether the order is partially fillable. Fee is applied to all trades and is deducted from the `amount_out`.
Because of the fee, the order price is static and calculated as `(amount_out - fee) / amount_in`.

## Notes
The pallet implements a minimum order size as an alternative to storage fees. The amounts of an open order cannot
Expand Down
Loading