Skip to content

Commit

Permalink
Implement multi-market combinatorial betting tests (#1377)
Browse files Browse the repository at this point in the history
* .

* Add more tests

* .

* Detailed testing

* .

* Add tests for `InvalidAmountKeep`

* Clippy fixes
  • Loading branch information
maltekliemann authored Oct 14, 2024
1 parent 0b0e607 commit e586f66
Show file tree
Hide file tree
Showing 6 changed files with 301 additions and 5 deletions.
1 change: 1 addition & 0 deletions primitives/src/constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ pub const BLOCKS_PER_HOUR: BlockNumber = BLOCKS_PER_MINUTE * 60; // 300
// Definitions for currency
pub const DECIMALS: u8 = 10;
pub const BASE: u128 = 10u128.pow(DECIMALS as u32);
pub const DIME: Balance = BASE / 10; // 1_000_000_000
pub const CENT: Balance = BASE / 100; // 100_000_000
pub const MILLI: Balance = CENT / 10; // 10_000_000
pub const MICRO: Balance = MILLI / 1000; // 10_000
Expand Down
3 changes: 3 additions & 0 deletions primitives/src/constants/base_multiples.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ pub const _80: u128 = 80 * _1;
pub const _99: u128 = 99 * _1;
pub const _100: u128 = 100 * _1;
pub const _101: u128 = 101 * _1;
pub const _300: u128 = 300 * _1;
pub const _321: u128 = 321 * _1;
pub const _400: u128 = 400 * _1;
pub const _444: u128 = 444 * _1;
pub const _500: u128 = 500 * _1;
pub const _777: u128 = 777 * _1;
Expand Down
13 changes: 12 additions & 1 deletion zrml/neo-swaps/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,10 @@ mod pallet {
/// The buy/sell/keep partition specified is empty, or contains overlaps or assets that don't
/// belong to the market.
InvalidPartition,

/// The `amount_keep` parameter must be zero if `keep` is empty and less than `amount_buy`
/// if `keep` is not empty.
InvalidAmountKeep,
}

#[derive(Decode, Encode, Eq, PartialEq, PalletError, RuntimeDebug, TypeInfo)]
Expand Down Expand Up @@ -1134,6 +1138,13 @@ mod pallet {
ensure!(amount_buy != Zero::zero(), Error::<T>::ZeroAmount);
let market = T::MarketCommons::market(&market_id)?;
ensure!(market.status == MarketStatus::Active, Error::<T>::MarketNotActive);

if keep.is_empty() {
ensure!(amount_keep.is_zero(), Error::<T>::InvalidAmountKeep);
} else {
ensure!(amount_keep < amount_buy, Error::<T>::InvalidAmountKeep);
}

Self::try_mutate_pool(&market_id, |pool| {
// Ensure that `buy` and `sell` partition are disjoint and only contain assets from
// the market.
Expand Down Expand Up @@ -1185,7 +1196,7 @@ mod pallet {
}

for &asset in keep.iter() {
T::MultiCurrency::transfer(asset, &pool.account_id, &who, amount_keep)?;
T::MultiCurrency::transfer(asset, &who, &pool.account_id, amount_keep)?;
pool.increase_reserve(&asset, &amount_keep)?;
}

Expand Down
2 changes: 1 addition & 1 deletion zrml/neo-swaps/src/macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ macro_rules! assert_pool_state {
.fold(0u128, |acc, node| acc + node.fees + node.lazy_fees);
assert_eq!(actual_total_fees, $total_fees);
let invariant = actual_spot_prices.iter().sum::<u128>();
assert_approx!(invariant, _1, 1);
assert_approx!(invariant, _1, 2);
};
}

Expand Down
126 changes: 125 additions & 1 deletion zrml/neo-swaps/src/tests/combo_buy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,6 @@ fn combo_buy_works() {
let buy = vec![pool.assets()[0]];
let sell = pool.assets_complement(&buy);
assert_ok!(AssetManager::deposit(BASE_ASSET, &BOB, amount_in));
println!("{}", AssetManager::free_balance(BASE_ASSET, &BOB));
// Deposit some stuff in the pool account to check that the pools `reserves` fields tracks
// the reserve correctly.
assert_ok!(AssetManager::deposit(sell[0], &pool.account_id, _100));
Expand Down Expand Up @@ -103,6 +102,131 @@ fn combo_buy_works() {
});
}

#[test_case(
333 * _1,
vec![10 * CENT, 30 * CENT, 25 * CENT, 13 * CENT, 22 * CENT],
vec![0, 2],
vec![3],
vec![1, 4],
102_040_816_327,
236_865_613_849,
100_000_000_001,
vec![3193134386152, 1841186221785, 1867994157274, 2950568636818, 2289732472863],
vec![1_099_260_911, 2_799_569_315, 2_748_152_277, 1_300_000_000, 2_053_017_497],
1_020_408_163
)]
#[test_case(
_100,
vec![80 * CENT, 5 * CENT, 5 * CENT, 5 * CENT, 5 * CENT],
vec![4],
vec![1, 2, 3],
vec![0],
336_734_693_877,
1_131_842_030_026,
329_999_999_999,
vec![404_487_147_360, _100, _100, _100, 198_157_969_973],
vec![2_976_802_957, 5 * CENT, 5 * CENT, 5 * CENT, 5_523_197_043],
3_367_346_939
)]
#[test_case(
1000 * _1,
vec![1_250_000_000; 8],
vec![0, 2, 5, 6, 7],
vec![],
vec![1, 3, 4],
5_102_040_816_326,
6_576_234_413_776,
5_000_000_000_000,
vec![
8_423_765_586_224,
1500 * _1,
8_423_765_586_224,
1500 * _1,
1500 * _1,
8_423_765_586_224,
8_423_765_586_224,
8_423_765_586_224,
],
vec![
1_734_834_957,
441_941_738,
1_734_834_957,
441_941_738,
441_941_738,
1_734_834_957,
1_734_834_957,
1_734_834_957,
],
51_020_408_163
)]
fn combo_buy_works_multi_market(
liquidity: u128,
spot_prices: Vec<u128>,
buy_indices: Vec<u16>,
keep_indices: Vec<u16>,
sell_indices: Vec<u16>,
amount_in: u128,
expected_amount_out_buy: u128,
expected_amount_out_keep: u128,
expected_reserves: Vec<u128>,
expected_spot_prices: Vec<u128>,
expected_fees: u128,
) {
ExtBuilder::default().build().execute_with(|| {
let asset_count = spot_prices.len() as u16;
let swap_fee = CENT;
let market_id = create_market_and_deploy_pool(
ALICE,
BASE_ASSET,
MarketType::Categorical(asset_count),
liquidity,
spot_prices.clone(),
swap_fee,
);
let sentinel = 123_456_789;
assert_ok!(AssetManager::deposit(BASE_ASSET, &BOB, amount_in + sentinel));

let pool = Pools::<Runtime>::get(market_id).unwrap();
let expected_liquidity = pool.liquidity_parameter;

let buy: Vec<_> =
buy_indices.iter().map(|&i| Asset::CategoricalOutcome(market_id, i)).collect();
let keep: Vec<_> =
keep_indices.iter().map(|&i| Asset::CategoricalOutcome(market_id, i)).collect();
let sell: Vec<_> =
sell_indices.iter().map(|&i| Asset::CategoricalOutcome(market_id, i)).collect();
assert_ok!(NeoSwaps::combo_buy(
RuntimeOrigin::signed(BOB),
market_id,
asset_count,
buy.clone(),
sell.clone(),
amount_in,
0,
));

assert_balance!(BOB, BASE_ASSET, sentinel);
for &asset in buy.iter() {
assert_balance!(BOB, asset, expected_amount_out_buy);
}
for &asset in keep.iter() {
assert_balance!(BOB, asset, expected_amount_out_keep);
}
for &asset in sell.iter() {
assert_balance!(BOB, asset, 0);
}

assert_pool_state!(
market_id,
expected_reserves,
expected_spot_prices,
expected_liquidity,
create_b_tree_map!({ ALICE => liquidity }),
expected_fees,
);
});
}

#[test]
fn combo_buy_fails_on_incorrect_asset_count() {
ExtBuilder::default().build().execute_with(|| {
Expand Down
161 changes: 159 additions & 2 deletions zrml/neo-swaps/src/tests/combo_sell.rs
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,127 @@ fn combo_sell_works() {
});
}

#[test_case(
1000 * _1,
vec![1_250_000_000; 8],
vec![0, 2, 5],
vec![6, 7],
vec![1, 3, 4],
_500,
_300,
2_091_832_646_248,
vec![
12_865_476_891_584,
7_865_476_891_584,
12_865_476_891_584,
7_865_476_891_584,
7_865_476_891_584,
12_865_476_891_584,
10_865_476_891_584,
10_865_476_891_584,
],
vec![
688_861_105,
1_948_393_435,
688_861_105,
1_948_393_435,
1_948_393_435,
688_861_105,
1_044_118_189,
1_044_118_189,
],
21_345_231_084
)]
#[test_case(
_321,
vec![20 * CENT, 30 * CENT, 50 * CENT],
vec![0, 2],
vec![],
vec![1],
_500,
0,
2_012_922_832_062,
vec![
6_155_997_110_140,
347_302_977_256,
4_328_468_861_556,
],
vec![
456_610_616,
8_401_862_845,
1_141_526_539,
],
20_540_028_899
)]
fn combo_sell_works_multi_market(
liquidity: u128,
spot_prices: Vec<u128>,
buy_indices: Vec<u16>,
keep_indices: Vec<u16>,
sell_indices: Vec<u16>,
amount_in_buy: u128,
amount_in_keep: u128,
expected_amount_out: u128,
expected_reserves: Vec<u128>,
expected_spot_prices: Vec<u128>,
expected_fees: u128,
) {
ExtBuilder::default().build().execute_with(|| {
let asset_count = spot_prices.len() as u16;
let swap_fee = CENT;
let market_id = create_market_and_deploy_pool(
ALICE,
BASE_ASSET,
MarketType::Categorical(asset_count),
liquidity,
spot_prices.clone(),
swap_fee,
);

let buy: Vec<_> =
buy_indices.iter().map(|&i| Asset::CategoricalOutcome(market_id, i)).collect();
let keep: Vec<_> =
keep_indices.iter().map(|&i| Asset::CategoricalOutcome(market_id, i)).collect();
let sell: Vec<_> =
sell_indices.iter().map(|&i| Asset::CategoricalOutcome(market_id, i)).collect();

for &asset in buy.iter() {
assert_ok!(AssetManager::deposit(asset, &BOB, amount_in_buy));
}
for &asset in keep.iter() {
assert_ok!(AssetManager::deposit(asset, &BOB, amount_in_keep));
}

let pool = Pools::<Runtime>::get(market_id).unwrap();
let expected_liquidity = pool.liquidity_parameter;

assert_ok!(NeoSwaps::combo_sell(
RuntimeOrigin::signed(BOB),
market_id,
asset_count,
buy.clone(),
keep.clone(),
sell.clone(),
amount_in_buy,
amount_in_keep,
0,
));

assert_balance!(BOB, BASE_ASSET, expected_amount_out);
for asset in pool.assets() {
assert_balance!(BOB, asset, 0);
}
assert_pool_state!(
market_id,
expected_reserves,
expected_spot_prices,
expected_liquidity,
create_b_tree_map!({ ALICE => liquidity }),
expected_fees,
);
});
}

#[test]
fn combo_sell_fails_on_incorrect_asset_count() {
ExtBuilder::default().build().execute_with(|| {
Expand Down Expand Up @@ -296,7 +417,6 @@ fn combo_sell_fails_on_invalid_partition(
indices_sell: Vec<u16>,
) {
ExtBuilder::default().build().execute_with(|| {
println!("{:?}", _1_7);
let market_id = create_market_and_deploy_pool(
ALICE,
BASE_ASSET,
Expand All @@ -320,7 +440,7 @@ fn combo_sell_fails_on_invalid_partition(
keep,
sell,
_2,
_1,
0, // Keep this zero to avoid a different error due to invalid `amount_keep` param.
0
),
Error::<Runtime>::InvalidPartition,
Expand Down Expand Up @@ -450,3 +570,40 @@ fn combo_sell_fails_on_large_amount() {
);
});
}

#[test_case(vec![], 1)]
#[test_case(vec![2], _2)]
fn combo_sell_fails_on_invalid_amount_keep(keep_indices: Vec<u16>, amount_in_keep: u128) {
ExtBuilder::default().build().execute_with(|| {
let spot_prices = vec![25 * CENT; 4];
let asset_count = spot_prices.len() as u16;
let market_id = create_market_and_deploy_pool(
ALICE,
BASE_ASSET,
MarketType::Categorical(asset_count),
_10,
spot_prices,
CENT,
);

let buy = vec![Asset::CategoricalOutcome(market_id, 0)];
let sell = vec![Asset::CategoricalOutcome(market_id, 1)];
let keep: Vec<_> =
keep_indices.iter().map(|&i| Asset::CategoricalOutcome(market_id, i)).collect();

assert_noop!(
NeoSwaps::combo_sell(
RuntimeOrigin::signed(BOB),
market_id,
asset_count,
buy.clone(),
keep.clone(),
sell.clone(),
_1,
amount_in_keep,
0,
),
Error::<Runtime>::InvalidAmountKeep
);
});
}

0 comments on commit e586f66

Please sign in to comment.