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

Implement swap fees #676

Merged
merged 21 commits into from
Jul 12, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
573478e
Add tests for trading with fees
maltekliemann Jun 15, 2022
410b285
Ensure swap fee values are valid
maltekliemann Jun 15, 2022
746aeb6
Add `swap_fee` parameter to CPMM market creation
maltekliemann Jun 15, 2022
9c72392
Fix errors
maltekliemann Jun 15, 2022
33397b8
Add tests for single-asset liquidity functions with fees
maltekliemann Jun 15, 2022
dd54465
Extend pool parameter test
maltekliemann Jun 20, 2022
6ae9439
Add new error for high swap fees
maltekliemann Jun 20, 2022
9b84d5c
Apply suggestions from code review
maltekliemann Jun 20, 2022
f9b4df1
Reorganize tests
maltekliemann Jun 20, 2022
a7f535a
Merge branch 'main' into swap-fees
maltekliemann Jun 20, 2022
c3631a4
Merge branch 'main' into swap-fees
maltekliemann Jun 22, 2022
2545269
Fix `get_spot_price` and extend tests
maltekliemann Jun 22, 2022
1037174
Merge branch 'main' into swap-fees
maltekliemann Jun 27, 2022
ff628df
Add notes to changelog
maltekliemann Jun 27, 2022
ebe224b
Merge branch 'main' into swap-fees
maltekliemann Jun 29, 2022
08ca08a
Add sanity tests
maltekliemann Jul 3, 2022
e78bc95
Merge branch 'swap-fees' of github.com:zeitgeistpm/zeitgeist into swa…
maltekliemann Jul 4, 2022
94b4ec5
Merge branch 'main' into swap-fees
maltekliemann Jul 7, 2022
a753c29
Update zrml/swaps/src/lib.rs
maltekliemann Jul 8, 2022
eeb6aff
Constrain swap fees to legal values in fuzz tests
maltekliemann Jul 8, 2022
50637b9
Merge branch 'swap-fees' of github.com:zeitgeistpm/zeitgeist into swa…
maltekliemann Jul 8, 2022
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
18 changes: 14 additions & 4 deletions docs/changelog_for_devs.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
# v0.3.4

- Implemented swap fees for CPMM pools. This means that the following extrinsics
now have a (non-optional) `swap_fee` parameter:

- `create_cpmm_market_and_deploy_assets`
- `deploy_swap_pool_and_additional_liquidity`
- `deploy_swap_pool_for_market`

Furthermore, there's a maximum swap fee, specified by the `swaps` pallet's
on-chain constant `MaxSwapFee`.

- Changed the `weights` parameter of `deploy_swap_pool_and_additional_liquidity`
and `deploy_swap_pool_for_market` to be a vector whose length is equal to the
number of outcome tokens (one item shorter than before). The `weights` now
Expand All @@ -12,16 +22,16 @@
# v0.3.3

- Introduced `MarketStatus::Closed`. Markets are automatically transitioned into
this state when the market ends, and the `Event::MarketClosed` is
emitted. Trading is not allowed on markets that are closed.
this state when the market ends, and the `Event::MarketClosed` is emitted.
Trading is not allowed on markets that are closed.

- Introduced `PoolStatus::Closed`; the pool of a market is closed when the
market is closed. The `Event::PoolClosed` is emitted when this happens.

- Replace `PoolStatus::Stale` with `PoolStatus::Clean`. This state signals that
the corresponding market was resolved and the losing assets deleted from the
pool. The `Event::PoolCleanedUp` is emitted when the pool transitions into this
state.
pool. The `Event::PoolCleanedUp` is emitted when the pool transitions into
this state.

- Simplify `create_cpmm_market_and_deploy_assets`,
`deploy_swap_pool_and_additional_liquidity` and `deploy_swap_pool_for_market`
Expand Down
1 change: 1 addition & 0 deletions primitives/src/constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ parameter_types! {
pub const MaxAssets: u16 = MaxCategories::get() + 1;
pub const MaxInRatio: Balance = (BASE / 3) + 1;
pub const MaxOutRatio: Balance = (BASE / 3) + 1;
pub const MaxSwapFee: Balance = BASE / 10; // 10%
Chralt98 marked this conversation as resolved.
Show resolved Hide resolved
pub const MaxTotalWeight: Balance = 50 * BASE;
pub const MaxWeight: Balance = 50 * BASE;
pub const MinLiquidity: Balance = 100 * BASE;
Expand Down
1 change: 1 addition & 0 deletions runtime/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -984,6 +984,7 @@ impl zrml_swaps::Config for Runtime {
type MaxAssets = MaxAssets;
type MaxInRatio = MaxInRatio;
type MaxOutRatio = MaxOutRatio;
type MaxSwapFee = MaxSwapFee;
type MaxTotalWeight = MaxTotalWeight;
type MaxWeight = MaxWeight;
type MinLiquidity = MinLiquidity;
Expand Down
5 changes: 3 additions & 2 deletions zrml/prediction-markets/src/benchmarks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ use frame_system::RawOrigin;
use orml_traits::MultiCurrency;
use sp_runtime::traits::{One, SaturatedConversion, Zero};
use zeitgeist_primitives::{
constants::{MinLiquidity, MinWeight, BASE},
constants::{MaxSwapFee, MinLiquidity, MinWeight, BASE},
traits::DisputeApi,
types::{
Asset, MarketCreation, MarketDisputeMechanism, MarketPeriod, MarketStatus, MarketType,
Expand Down Expand Up @@ -310,13 +310,14 @@ benchmarks! {
MarketType::Categorical(a.saturated_into()),
ScoringRule::CPMM
)?;
let max_swap_fee: BalanceOf::<T> = MaxSwapFee::get().saturated_into();
let min_liquidity: BalanceOf::<T> = MinLiquidity::get().saturated_into();
let _ = Call::<T>::buy_complete_set { market_id, amount: min_liquidity }
.dispatch_bypass_filter(RawOrigin::Signed(caller.clone()).into())?;

let weight_len: usize = MaxRuntimeUsize::from(a).into();
let weights = vec![MinWeight::get(); weight_len];
}: _(RawOrigin::Signed(caller), market_id, min_liquidity, weights)
}: _(RawOrigin::Signed(caller), market_id, max_swap_fee, min_liquidity, weights)

dispute {
let a in 0..(T::MaxDisputes::get() - 1) as u32;
Expand Down
11 changes: 9 additions & 2 deletions zrml/prediction-markets/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -398,6 +398,7 @@ mod pallet {
/// * `metadata`: A hash pointer to the metadata of the market.
/// * `market_type`: The type of the market.
/// * `dispute_mechanism`: The market dispute mechanism.
/// * `swap_fee`: The swap fee, specified as fixed-point ratio (0.1 equals 10% fee)
maltekliemann marked this conversation as resolved.
Show resolved Hide resolved
/// * `amount`: The amount of each token to add to the pool.
/// * `weights`: The relative denormalized weight of each asset price.
#[pallet::weight(
Expand All @@ -420,6 +421,7 @@ mod pallet {
metadata: MultiHash,
market_type: MarketType,
dispute_mechanism: MarketDisputeMechanism<T::AccountId>,
#[pallet::compact] swap_fee: BalanceOf<T>,
#[pallet::compact] amount: BalanceOf<T>,
weights: Vec<u128>,
) -> DispatchResultWithPostInfo {
Expand Down Expand Up @@ -447,6 +449,7 @@ mod pallet {
let deploy_and_populate_weight = Self::deploy_swap_pool_and_additional_liquidity(
origin,
market_id,
swap_fee,
amount,
weights.clone(),
)?
Expand Down Expand Up @@ -562,6 +565,7 @@ mod pallet {
/// # Arguments
///
/// * `market_id`: The id of the market.
/// * `swap_fee`: The swap fee, specified as fixed-point ratio (0.1 equals 10% fee)
maltekliemann marked this conversation as resolved.
Show resolved Hide resolved
/// * `amount`: The amount of each token to add to the pool.
/// * `weights`: The relative denormalized weight of each outcome asset. The sum of the
/// weights must be less or equal to _half_ of the `MaxTotalWeight` constant of the
Expand All @@ -581,6 +585,7 @@ mod pallet {
pub fn deploy_swap_pool_and_additional_liquidity(
origin: OriginFor<T>,
#[pallet::compact] market_id: MarketIdOf<T>,
#[pallet::compact] swap_fee: BalanceOf<T>,
#[pallet::compact] amount: BalanceOf<T>,
weights: Vec<u128>,
) -> DispatchResultWithPostInfo {
Expand All @@ -589,7 +594,7 @@ mod pallet {
.actual_weight
.unwrap_or_else(|| T::WeightInfo::buy_complete_set(T::MaxCategories::get().into()));
let weights_len = weights.len();
Self::deploy_swap_pool_for_market(origin, market_id, amount, weights)?;
Self::deploy_swap_pool_for_market(origin, market_id, swap_fee, amount, weights)?;
Ok(Some(weight_bcs.saturating_add(T::WeightInfo::deploy_swap_pool_for_market(
weights_len.saturated_into(),
)))
Expand All @@ -603,6 +608,7 @@ mod pallet {
/// # Arguments
///
/// * `market_id`: The id of the market.
/// * `swap_fee`: The swap fee, specified as fixed-point ratio (0.1 equals 10% fee)
maltekliemann marked this conversation as resolved.
Show resolved Hide resolved
/// * `amount`: The amount of each token to add to the pool.
/// * `weights`: The relative denormalized weight of each outcome asset. The sum of the
/// weights must be less or equal to _half_ of the `MaxTotalWeight` constant of the
Expand All @@ -614,6 +620,7 @@ mod pallet {
pub fn deploy_swap_pool_for_market(
origin: OriginFor<T>,
#[pallet::compact] market_id: MarketIdOf<T>,
#[pallet::compact] swap_fee: BalanceOf<T>,
#[pallet::compact] amount: BalanceOf<T>,
mut weights: Vec<u128>,
) -> DispatchResult {
Expand All @@ -635,7 +642,7 @@ mod pallet {
base_asset,
market_id,
ScoringRule::CPMM,
Some(Zero::zero()),
Some(swap_fee),
Some(amount),
Some(weights),
)?;
Expand Down
9 changes: 5 additions & 4 deletions zrml/prediction-markets/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,10 @@ use zeitgeist_primitives::{
AuthorizedPalletId, BalanceFractionalDecimals, BlockHashCount, CourtCaseDuration,
CourtPalletId, DisputeFactor, ExistentialDeposit, ExistentialDeposits, ExitFee,
GetNativeCurrencyId, LiquidityMiningPalletId, MaxAssets, MaxCategories, MaxDisputes,
MaxInRatio, MaxMarketPeriod, MaxOutRatio, MaxReserves, MaxSubsidyPeriod, MaxTotalWeight,
MaxWeight, MinAssets, MinCategories, MinLiquidity, MinSubsidy, MinSubsidyPeriod, MinWeight,
MinimumPeriod, PmPalletId, ReportingPeriod, SimpleDisputesPalletId, StakeWeight,
SwapsPalletId, BASE, CENT,
MaxInRatio, MaxMarketPeriod, MaxOutRatio, MaxReserves, MaxSubsidyPeriod, MaxSwapFee,
MaxTotalWeight, MaxWeight, MinAssets, MinCategories, MinLiquidity, MinSubsidy,
MinSubsidyPeriod, MinWeight, MinimumPeriod, PmPalletId, ReportingPeriod,
SimpleDisputesPalletId, StakeWeight, SwapsPalletId, BASE, CENT,
},
types::{
AccountIdTest, Amount, Asset, Balance, BasicCurrencyAdapter, BlockNumber, BlockTest,
Expand Down Expand Up @@ -242,6 +242,7 @@ impl zrml_swaps::Config for Runtime {
type MaxAssets = MaxAssets;
type MaxInRatio = MaxInRatio;
type MaxOutRatio = MaxOutRatio;
type MaxSwapFee = MaxSwapFee;
type MaxTotalWeight = MaxTotalWeight;
type MaxWeight = MaxWeight;
type MinAssets = MinAssets;
Expand Down
71 changes: 59 additions & 12 deletions zrml/prediction-markets/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -388,6 +388,7 @@ fn admin_destroy_market_correctly_cleans_up_accounts() {
gen_metadata(50),
MarketType::Categorical(3),
MarketDisputeMechanism::SimpleDisputes,
<Runtime as zrml_swaps::Config>::MaxSwapFee::get(),
<Runtime as zrml_swaps::Config>::MinLiquidity::get(),
vec![<Runtime as zrml_swaps::Config>::MinWeight::get(); 3],
));
Expand Down Expand Up @@ -783,6 +784,7 @@ fn on_market_close_successfully_auto_closes_market_with_blocks() {
gen_metadata(50),
MarketType::Categorical(category_count),
MarketDisputeMechanism::SimpleDisputes,
0,
<Runtime as zrml_swaps::Config>::MinLiquidity::get(),
vec![<Runtime as zrml_swaps::Config>::MinWeight::get(); category_count.into()],
));
Expand Down Expand Up @@ -817,6 +819,7 @@ fn on_market_close_successfully_auto_closes_market_with_timestamps() {
gen_metadata(50),
MarketType::Categorical(category_count),
MarketDisputeMechanism::SimpleDisputes,
0,
<Runtime as zrml_swaps::Config>::MinLiquidity::get(),
vec![<Runtime as zrml_swaps::Config>::MinWeight::get(); category_count.into()],
));
Expand Down Expand Up @@ -859,6 +862,7 @@ fn on_market_close_successfully_auto_closes_multiple_markets_after_stall() {
gen_metadata(50),
MarketType::Categorical(category_count),
MarketDisputeMechanism::SimpleDisputes,
0,
<Runtime as zrml_swaps::Config>::MinLiquidity::get(),
vec![<Runtime as zrml_swaps::Config>::MinWeight::get(); category_count.into()],
));
Expand All @@ -869,6 +873,7 @@ fn on_market_close_successfully_auto_closes_multiple_markets_after_stall() {
gen_metadata(50),
MarketType::Categorical(category_count),
MarketDisputeMechanism::SimpleDisputes,
0,
<Runtime as zrml_swaps::Config>::MinLiquidity::get(),
vec![<Runtime as zrml_swaps::Config>::MinWeight::get(); category_count.into()],
));
Expand Down Expand Up @@ -904,6 +909,7 @@ fn market_close_manager_skips_the_genesis_block_with_timestamp_zero() {
gen_metadata(50),
MarketType::Categorical(category_count),
MarketDisputeMechanism::SimpleDisputes,
123,
<Runtime as zrml_swaps::Config>::MinLiquidity::get(),
vec![<Runtime as zrml_swaps::Config>::MinWeight::get(); category_count.into()],
));
Expand Down Expand Up @@ -1134,6 +1140,7 @@ fn it_allows_to_deploy_a_pool() {
assert_ok!(PredictionMarkets::deploy_swap_pool_for_market(
Origin::signed(BOB),
0,
<Runtime as zrml_swaps::Config>::MaxSwapFee::get(),
<Runtime as zrml_swaps::Config>::MinLiquidity::get(),
vec![<Runtime as zrml_swaps::Config>::MinWeight::get(); 2],
));
Expand All @@ -1152,13 +1159,15 @@ fn deploy_swap_pool_for_market_fails_if_market_has_a_pool() {
assert_ok!(PredictionMarkets::deploy_swap_pool_for_market(
Origin::signed(BOB),
0,
<Runtime as zrml_swaps::Config>::MaxSwapFee::get(),
<Runtime as zrml_swaps::Config>::MinLiquidity::get(),
vec![<Runtime as zrml_swaps::Config>::MinWeight::get(); 2],
));
assert_noop!(
PredictionMarkets::deploy_swap_pool_for_market(
Origin::signed(BOB),
0,
<Runtime as zrml_swaps::Config>::MaxSwapFee::get(),
<Runtime as zrml_swaps::Config>::MinLiquidity::get(),
vec![<Runtime as zrml_swaps::Config>::MinWeight::get(); 2],
),
Expand All @@ -1181,6 +1190,7 @@ fn it_does_not_allow_to_deploy_a_pool_on_pending_advised_market() {
PredictionMarkets::deploy_swap_pool_for_market(
Origin::signed(BOB),
0,
<Runtime as zrml_swaps::Config>::MaxSwapFee::get(),
<Runtime as zrml_swaps::Config>::MinLiquidity::get(),
vec![<Runtime as zrml_swaps::Config>::MinWeight::get(); 2],
),
Expand Down Expand Up @@ -1725,15 +1735,19 @@ fn it_allows_to_redeem_shares() {
}

#[test]
fn create_market_and_deploy_assets_results_in_expected_balances() {
fn create_market_and_deploy_assets_results_in_expected_balances_and_pool_params() {
let oracle = ALICE;
let period = MarketPeriod::Block(0..42);
let metadata = gen_metadata(42);
let category_count = 4;
let assets = MarketType::Categorical(category_count);
let market_type = MarketType::Categorical(category_count);
let swap_fee = <Runtime as zrml_swaps::Config>::MaxSwapFee::get();
let amount = 123 * BASE;
let pool_id = 0;
let weights = vec![<Runtime as zrml_swaps::Config>::MinWeight::get(); category_count.into()];
let weight = <Runtime as zrml_swaps::Config>::MinWeight::get();
let weights = vec![weight; category_count.into()];
let base_asset_weight = (category_count as u128) * weight;
let total_weight = 2 * base_asset_weight;

// Execute the combined convenience function
ExtBuilder::default().build().execute_with(|| {
Expand All @@ -1742,11 +1756,13 @@ fn create_market_and_deploy_assets_results_in_expected_balances() {
oracle,
period,
metadata,
assets,
market_type,
MarketDisputeMechanism::SimpleDisputes,
swap_fee,
amount,
weights,
));
let market_id = 0;

let pool_account = Swaps::pool_account_id(pool_id);
assert_eq!(Tokens::free_balance(Asset::CategoricalOutcome(0, 0), &ALICE), 0);
Expand All @@ -1759,6 +1775,29 @@ fn create_market_and_deploy_assets_results_in_expected_balances() {
assert_eq!(Tokens::free_balance(Asset::CategoricalOutcome(0, 2), &pool_account), amount);
assert_eq!(Tokens::free_balance(Asset::CategoricalOutcome(0, 3), &pool_account), amount);
assert_eq!(System::account(&pool_account).data.free, amount);

let pool = Pools::<Runtime>::get(0).unwrap();
let assets_expected = vec![
Asset::CategoricalOutcome(market_id, 0),
Asset::CategoricalOutcome(market_id, 1),
Asset::CategoricalOutcome(market_id, 2),
Asset::CategoricalOutcome(market_id, 3),
Asset::Ztg,
];
assert_eq!(pool.assets, assets_expected);
assert_eq!(pool.base_asset, Asset::Ztg);
assert_eq!(pool.market_id, market_id);
assert_eq!(pool.scoring_rule, ScoringRule::CPMM);
assert_eq!(pool.swap_fee, Some(swap_fee));
Chralt98 marked this conversation as resolved.
Show resolved Hide resolved
assert_eq!(pool.total_subsidy, None);
assert_eq!(pool.total_subsidy, None);
assert_eq!(pool.total_weight, Some(total_weight));
let pool_weights = pool.weights.unwrap();
assert_eq!(pool_weights[&Asset::CategoricalOutcome(market_id, 0)], weight);
assert_eq!(pool_weights[&Asset::CategoricalOutcome(market_id, 1)], weight);
assert_eq!(pool_weights[&Asset::CategoricalOutcome(market_id, 2)], weight);
assert_eq!(pool_weights[&Asset::CategoricalOutcome(market_id, 3)], weight);
assert_eq!(pool_weights[&Asset::Ztg], base_asset_weight);
});
}

Expand Down Expand Up @@ -2299,6 +2338,7 @@ fn deploy_swap_pool_correctly_sets_weight_of_base_asset() {
gen_metadata(50),
MarketType::Categorical(3),
MarketDisputeMechanism::SimpleDisputes,
1,
<Runtime as zrml_swaps::Config>::MinLiquidity::get(),
weights,
));
Expand All @@ -2325,13 +2365,17 @@ fn deploy_swap_pool_for_market_returns_error_if_weights_is_too_short() {
MarketDisputeMechanism::SimpleDisputes,
ScoringRule::CPMM
));
let _ = Balances::set_balance(Origin::root(), ALICE, 246 * BASE, 0);
assert_ok!(PredictionMarkets::buy_complete_set(Origin::signed(ALICE), 0, 123 * BASE));
let amount = 123 * BASE;
assert_ok!(Balances::set_balance(Origin::root(), ALICE, 2 * amount, 0));
assert_ok!(PredictionMarkets::buy_complete_set(Origin::signed(ALICE), 0, amount));
// Attempt to create a pool with four weights; but we need five instead (base asset not
// counted).
assert_noop!(
PredictionMarkets::deploy_swap_pool_for_market(
Origin::signed(ALICE),
0,
123 * BASE,
1,
amount,
vec![
<Runtime as zrml_swaps::Config>::MinWeight::get();
(category_count - 1).into()
Expand All @@ -2356,15 +2400,17 @@ fn deploy_swap_pool_for_market_returns_error_if_weights_is_too_long() {
MarketDisputeMechanism::SimpleDisputes,
ScoringRule::CPMM
));
let _ = Balances::set_balance(Origin::root(), ALICE, 246 * BASE, 0);
assert_ok!(PredictionMarkets::buy_complete_set(Origin::signed(ALICE), 0, 123 * BASE));
// Attempt to create a pool with seven weights; but we need six instead (five for the
// outcome tokens, one for the base asset).
let amount = 123 * BASE;
assert_ok!(Balances::set_balance(Origin::root(), ALICE, 2 * amount, 0));
assert_ok!(PredictionMarkets::buy_complete_set(Origin::signed(ALICE), 0, amount));
// Attempt to create a pool with six weights; but we need five instead (base asset not
// counted).
assert_noop!(
PredictionMarkets::deploy_swap_pool_for_market(
Origin::signed(ALICE),
0,
123 * BASE,
<Runtime as zrml_swaps::Config>::MaxSwapFee::get(),
amount,
vec![
<Runtime as zrml_swaps::Config>::MinWeight::get();
(category_count + 1).into()
Expand Down Expand Up @@ -2661,6 +2707,7 @@ fn deploy_swap_pool(market: Market<u128, u64, u64>, market_id: u128) -> Dispatch
PredictionMarkets::deploy_swap_pool_for_market(
Origin::signed(FRED),
0,
<Runtime as zrml_swaps::Config>::MaxSwapFee::get(),
<Runtime as zrml_swaps::Config>::MinLiquidity::get(),
vec![<Runtime as zrml_swaps::Config>::MinWeight::get(); outcome_assets_len],
)
Expand Down
Loading