-
Notifications
You must be signed in to change notification settings - Fork 41
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 Combinatorial Betting and Futarchy #1364
base: main
Are you sure you want to change the base?
Conversation
* Revert "New Asset System (#1295)" (#1338) * Revert "New Asset System (#1295)" This reverts commit a956877. * Fix formatting * Update copyright * Remove pallet-assets dependency * Fix fuzz tests * Merge `main` into `develop` (#1345) * Update dependencies to Polkadot v1.1.0 (#1331) * Update dependencies (#1319) * Update dependencies to polkadot-v1.1.0 * Format code * Remove duplicate dependencies * Update zrml-asset-router (#1321) * Update zrml-primitives * Partially update asset-router * Finalize logic adjustments in asset-router * Make asset-router tests compilable * Correct Inspect routing for market assets in Currencies * Directly invoke Inspect API for Currencies * Add tests for remaining Unbalances functions * Update remaining Zeitgeist pallets (#1322) * Update zrml-asset-router (#1321) * Upgrade zrml-market-commons * Upgrade zrml-authorized && use MockBlock instead of MockBlockU32 * Upgrade zrml-court * Upgrade zrml-global-disputes * Upgrade liquidity mining * Upgrade zrml-rikiddo * Upgrade zrml-simple-disputes * Upgrade zrml-styx * Upgrade zrml-orderbook * Upgrade zrml-parimutuel * Upgrade zrml-swaps * Upgrade zrml-prediction-markets * Upgrade zrml-neo-swaps * Upgrade zrml-hybrid-router * Update license headers * Update runtime (#1323) * Update weight files & Runtime enum * Use workspace metadata * Always use serde serialization for asset types * Make battery station standalone runtime compilable * Make benchmark and try-runtime feature compilable * Make BS build with all features * Make parachain tests compile * Partially fix xcm tests * Use safe xcm version 2 * Update Zeitgeist runtime (except xcm tests) * Format code * Remove deprecated comment * Integrate new xcm-emulator (#1324) * Integrate new xcm-emulator environment * Utilize new xcm-emulator interfaces * Spawn relay-para network using patched xcm-emulator * Use proper collator genesis config * Fix Rococo tests * Finalize Battery Station XCM tests * Finalize Zeitgeist XCM tests * Update client (#1327) * Fix rpc and work on client update * Finalize standalone client * Update parachain client * Use same try-runtime subcommand in every case * Update node/src/cli.rs Co-authored-by: Malte Kliemann <[email protected]> * Update try-runtime* Makefile targets --------- Co-authored-by: Malte Kliemann <[email protected]> * Make CI succeed and add migrations (#1329) * Fix rpc and work on client update * Finalize standalone client * Update parachain client * Use same try-runtime subcommand in every case * Satisfy Clippy * Fix benchmarks * Add migrations * Satisfy Clippy * Update moonkit depedencies * Free disk space more aggressively --------- Co-authored-by: Malte Kliemann <[email protected]> * Update spec version, try-runtime Makefile * Fix copyright notices * Fix broken chain state (#1336) * Add `StorageVersion` fix and contrats fix migrations * Don't set pallet-balances' storage version * Remove migrations from pallet-contracts config * Clear storage tries of contracts * Fix migration and info logs in try-runtime * Fix licenses and comments * Fix formatting --------- Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> --------- Co-authored-by: Harald Heckmann <[email protected]> Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> * Merge * Fix benchmark * Fix compiler error * Fix tests and imports * Fix imports (again...) * Fix orderbook benchmarks * Fix fuzz tests * Fix formatting * Fix orderbook fuzz --------- Co-authored-by: Harald Heckmann <[email protected]> Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> * Update versions to v0.5.3 * Restructure math module * More scaffolding * Implement combinatorial buy math * Implement price calculation for combo * Remove `println!` * Implement equalization * Implement selling * Add tests for combinatorial buying * Add more tests for combinatorial buying * Add tests for equalization * Add more tests/corner cases * Implement full testing, fix critical bug --------- Co-authored-by: Harald Heckmann <[email protected]> Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
* Implement classical buying using combinatorial buys * Calculate classical sells with combinatorial math * Implement `combo_buy` * Implement `combo_sell`
* Fix compiler and clippy issues * Fix formatting * Fix tests
* Scaffold combo pallet * Fix dependencies/features
* More scaffolding * Add `IdManager` trait * Scaffold ID manager * Use generics * WIP * Rough outline of `CryptographicIdManager` implementation * Improve `MaybeToBytes` implementation for `bool` * Implement pseudo-root * Add more tests * Implement `quadratic_residue` and add tests * Partial implementation of decompression algorithm * Loads of cleaning up * Refactor * Clean up `hash_pair` * Simplify interface * Simplify serialization * More cleanup * Clean up and tests * Better `ToBytes` implementation * Abstract `decompressor` tests * Reorganize tests * Minor clean up * More clean up * Test `get_collection_id`, fix bugs * Clean up * Expose `force_max_work` parameter and test it properly * Properly forget dummies * Add more tests * Fix some error handling, docs, and add missing tests * Prettify
* Extend `Config` * wip * Some refactors * Implement splitting tokens * Implement merging tokens
…1370) * . * Use `core::hint` * Replace `halo2curves` dependency with `ark-*` dependency * Fix clippy issues * Fix formatting
* Add mock for zrml-combinatorial-tokens * Add test framework * Add negative tests for `split_token` * Add more tests, fix some bugs, extend `Event` object * Add more tests * Add more integration tests * Add more integration tests * Add more integration tests * More tests * Add merge tests * final tests * fixes
Codecov ReportAttention: Patch coverage is
❗ Your organization needs to install the Codecov GitHub app to enable full functionality. Additional details and impacted files@@ Coverage Diff @@
## main #1364 +/- ##
==========================================
+ Coverage 93.21% 93.53% +0.32%
==========================================
Files 132 178 +46
Lines 29358 34547 +5189
==========================================
+ Hits 27366 32315 +4949
- Misses 1992 2232 +240
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. |
* Add numerical thresholds to combinatorial betting * Add protected `exp` for normal bets * Ensure correctness of partitions * Check partitions
* Add licenses * Add remark about LGPL-3.0 licensing by Gnosis
* Fix formatting * Fix mess with copyright notices
ExtBuilder::build().execute_with(|| { | ||
let alice = Account::new(0).deposit(Asset::ForeignAsset(1), _99).unwrap(); | ||
|
||
// Market has three outcomes, but there's an element in the partition of size two. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// Market has three outcomes, but there's an element in the partition of size two. |
|
||
#[test_case(vec![B0, B1, B0, B1]; "incorrect_len")] | ||
#[test_case(vec![B0, B0, B0]; "all_zero")] | ||
#[test_case(vec![B0, B0, B0]; "all_one")] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
#[test_case(vec![B0, B0, B0]; "all_one")] | |
#[test_case(vec![B1, B1, B1]; "all_one")] |
} | ||
|
||
#[test] | ||
fn redeem_position_fails_if_tokens_have_to_value() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fn redeem_position_fails_if_tokens_have_to_value() { | |
fn redeem_position_fails_if_tokens_have_no_value() { |
let try_mutate_result = Proposals::<T>::try_mutate(to_be_scheduled_at, |proposals| { | ||
proposals.try_push(proposal.clone()).map_err(|_| Error::<T>::CacheFull) | ||
}); | ||
|
||
Self::deposit_event(Event::<T>::Submitted { duration, proposal }); | ||
|
||
Ok(try_mutate_result?) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
let try_mutate_result = Proposals::<T>::try_mutate(to_be_scheduled_at, |proposals| { | |
proposals.try_push(proposal.clone()).map_err(|_| Error::<T>::CacheFull) | |
}); | |
Self::deposit_event(Event::<T>::Submitted { duration, proposal }); | |
Ok(try_mutate_result?) | |
Proposals::<T>::try_mutate(to_be_scheduled_at, |proposals| { | |
proposals.try_push(proposal.clone()).map_err(|_| Error::<T>::CacheFull) | |
})?; | |
Self::deposit_event(Event::<T>::Submitted { duration, proposal }); | |
Ok(()) |
if result.is_ok() { | ||
Self::deposit_event(Event::<T>::Scheduled { proposal }); | ||
} else { | ||
Self::deposit_event(Event::<T>::UnexpectedSchedulerError); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This case is unfortunate. This can obviously lead to the following scenario: A proposal is submitted and stored in Proposals
, but because the scheduler has too many scheduled calls at proposal.when
, it fails and the whole proposal is removed from the storage, although it has been evaluated to be approved. It might be better to have another extrinsic call that allows approved proposals to be scheduled at the time of the root origins choosing. So essentially this function here could be called evaluate_proposal
that either emits Approved
and stores a boolean flag approved
in the proposal or Rejected
that removes the proposal. After the successful scheduling from the root origin, the proposal can be removed too.
use frame_support::{ | ||
dispatch::RawOrigin, | ||
pallet_prelude::Weight, | ||
traits::schedule::{v3::Anon, DispatchTime}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
traits::schedule::{v3::Anon, DispatchTime}, | |
traits::schedule::{v3::Anon, DispatchTime, HARD_DEADLINE}, |
let result = T::Scheduler::schedule( | ||
DispatchTime::At(proposal.when), | ||
None, | ||
63, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
63, | |
HARD_DEADLINE, |
#[benchmark] | ||
fn submit_proposal() { | ||
let duration = T::MinDuration::get(); | ||
|
||
let oracle = T::BenchmarkHelper::create_oracle(true); | ||
let proposal = Proposal { | ||
when: Default::default(), | ||
call: Bounded::Inline(vec![7u8; 128].try_into().unwrap()), | ||
oracle, | ||
}; | ||
|
||
#[extrinsic_call] | ||
_(RawOrigin::Root, duration, proposal.clone()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
At to_be_scheduled_at
there could be 15 existing proposals in the worst case. This isn't accounted here in the benchmark.
#[benchmark] | ||
fn maybe_schedule_proposal() { | ||
let when = u32::MAX.into(); | ||
let oracle = T::BenchmarkHelper::create_oracle(true); | ||
let proposal = | ||
Proposal { when, call: Bounded::Inline(vec![7u8; 128].try_into().unwrap()), oracle }; | ||
|
||
#[block] | ||
{ | ||
Pallet::<T>::maybe_schedule_proposal(proposal.clone()); | ||
} | ||
|
||
let expected_event = <T as Config>::RuntimeEvent::from(Event::<T>::Scheduled { proposal }); | ||
System::<T>::assert_last_event(expected_event.into()); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
At now
in on_initialize
there could be 15 existing proposals in the worst case. T::DbWeight::get().reads_writes(1, 1)
does not account for storage decoding and encoding.
// Copyright 2024 Forecasting Technologies LTD. | ||
// | ||
// This file is part of Zeitgeist. | ||
// | ||
// Zeitgeist is free software: you can redistribute it and/or modify it | ||
// under the terms of the GNU General Public License as published by the | ||
// Free Software Foundation, either version 3 of the License, or (at | ||
// your option) any later version. | ||
// | ||
// Zeitgeist is distributed in the hope that it will be useful, but | ||
// WITHOUT ANY WARRANTY; without even the implied warranty of | ||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | ||
// General Public License for more details. | ||
// | ||
// You should have received a copy of the GNU General Public License | ||
// along with Zeitgeist. If not, see <https://www.gnu.org/licenses/>. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This file and directory can be removed, since it has no content.
CombinatorialOutcome, | ||
CombinatorialToken(CombinatorialId), | ||
PoolShare(PoolId), | ||
#[default] | ||
Ztg, | ||
ForeignAsset(u32), | ||
ParimutuelShare(MarketId, CategoryIndex), | ||
} | ||
// TODO Needs storage migration |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does not need a storage migration, because the enum variant CombinatorialOutcome
has never been used before in the storage.
Each enum variant is encoded separately in the storage. https://docs.substrate.io/reference/scale-codec/
The two potential storage items that could contain a CombinatorialOutcome
are Markets
that contains the base_asset
as a potential Asset
. The other is Pools
. But the Asset
CombinatorialOutcome
was never assigned.
@@ -444,7 +573,7 @@ mod pallet { | |||
/// | |||
/// # Parameters | |||
/// | |||
/// - `market_id`: Identifier for the market related to the pool. | |||
/// - `poold_id`: Identifier for the pool to withdraw liquidity from. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
/// - `poold_id`: Identifier for the pool to withdraw liquidity from. | |
/// - `pool_id`: Identifier for the pool to withdraw liquidity from. |
if keep.is_empty() { | ||
ensure!(amount_keep.is_zero(), Error::<T>::InvalidAmountKeep); | ||
} else { | ||
ensure!(amount_keep < amount_buy, Error::<T>::InvalidAmountKeep); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why?
// Ensure that numerical limits of all prices are respected. | ||
for &asset in pool.assets().iter() { | ||
let spot_price = pool.calculate_spot_price(asset)?; | ||
ensure!( | ||
spot_price >= COMBO_MIN_SPOT_PRICE.saturated_into(), | ||
Error::<T>::NumericalLimits(NumericalLimitsError::SpotPriceSlippedTooLow) | ||
); | ||
ensure!( | ||
spot_price <= COMBO_MAX_SPOT_PRICE.saturated_into(), | ||
Error::<T>::NumericalLimits(NumericalLimitsError::SpotPriceSlippedTooHigh) | ||
); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is duplicated from above.
let swap_amount_out = | ||
pool.calculate_swap_amount_out_for_buy(asset_out, amount_in_minus_fees)?; | ||
pool.calculate_swap_amount_out_for_buy(buy, sell, amount_in_minus_fees)?; | ||
let amount_out = swap_amount_out.checked_add_res(&amount_in_minus_fees)?; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe it's worth commenting here that swap_amount_out
is just y(x)
, means the amount of assets in sell
(S
) that were sold for more assets of buy
(B
). That's why amount_out = swap_amount_out + amount_in_minus_fees
, which is the buy complete set amount plus the additional amount that was received through the sale of the unwanted outcomes in sell
.
vec![1, 3, 4], | ||
5_208_333_333_333, | ||
6_576_234_413_778, | ||
5_000_000_000_000, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This amount_keep
isn't checked, because the vector keep_indices
is empty. Maybe it's better to use 0
and add a comment.
} | ||
|
||
#[test] | ||
fn combo_buy_fails_on_market_not_found() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Rename to combo_buy_fails_on_pool_not_found
.
6, | ||
market_ids, | ||
_10, | ||
vec![20 * CENT; 5], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Comment here that the above markets produce 2 * 3 = 6
collections, instead of 5
: LONG & 0
, LONG & 1
, LONG & 2
, SHORT & 0
, SHORT & 1
, SHORT & 2
.
assert_noop!( | ||
NeoSwaps::deploy_combinatorial_pool( | ||
RuntimeOrigin::signed(BOB), | ||
2, | ||
vec![market_id], | ||
liquidity, | ||
vec![_3_4, _1_4], | ||
CENT, | ||
false, | ||
), | ||
expected_error | ||
); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Make clear to the reader that BOB has no sufficient funds as opposed to ALICE.
|
||
// We don't care about being precise here and just deposit a huge bunch of tokens for Bob. | ||
for &asset in assets.iter() { | ||
let amount_for_bob = amount_buy.max(amount_buy); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is always amount_buy
.
let numerator = exp_sum_buy | ||
.checked_add(exp_sum_sell)? | ||
.checked_sub(exp_of_minus_amount_in_times_exp_sum_sell)?; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe it's good to comment here that exp_sum_buy + exp_sum_sell = 1 - exp_sum_keep
to arrive at the formula of the ZIP-10 document.
Some([ | ||
0x22, 0x9B, 0x06, 0x7E, 0x14, 0x2F, 0xCE, 0x0A, 0xEA, 0x84, 0xAF, 0xB9, 0x35, 0x09, 0x5C, | ||
0x6E, 0xCB, 0xEA, 0x86, 0x47, 0xB8, 0xA0, 0x13, 0xE7, 0x95, 0xCC, 0x0C, 0xED, 0x32, 0x10, | ||
0xA3, 0xD5, | ||
]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This hex string 0x229b067e142fce0aea84afb935095c6ecbea8647b8a013e795cc0ced3210a3d5
for parent collection id can be found on https://docs.gnosis.io/conditionaltokens/docs/devguide05
[ | ||
0x52, 0xFF, 0x54, 0xF0, 0xF5, 0x61, 0x6E, 0x34, 0xA2, 0xD4, 0xF5, 0x6F, 0xB6, 0x8A, 0xB4, | ||
0xCC, 0x63, 0x6B, 0xF0, 0xD9, 0x21, 0x11, 0xDE, 0x74, 0xD1, 0xEC, 0x99, 0x04, 0x0A, 0x8D, | ||
0xA1, 0x18, | ||
], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This hash 0x52ff54f0f5616e34a2d4f56fb68ab4cc636bf0d92111de74d1ec99040a8da118
can be found here.
Some([ | ||
0x56, 0x0A, 0xE3, 0x73, 0xED, 0x30, 0x49, 0x32, 0xB6, 0xF4, 0x24, 0xC8, 0xA2, 0x43, 0x84, | ||
0x20, 0x92, 0xC1, 0x17, 0x64, 0x55, 0x33, 0x39, 0x0A, 0x3C, 0x1C, 0x95, 0xFF, 0x48, 0x15, | ||
0x87, 0xC2, | ||
]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This hex string 0x560ae373ed304932b6f424c8a243842092c117645533390a3c1c95ff481587c2
can be found here.
Some([ | ||
0x6F, 0x72, 0x2A, 0xA2, 0x50, 0x22, 0x1A, 0xF2, 0xEB, 0xA9, 0x86, 0x8F, 0xC9, 0xD7, 0xD4, | ||
0x39, 0x94, 0x79, 0x41, 0x77, 0xDD, 0x6F, 0xA7, 0x76, 0x6E, 0x3E, 0x72, 0xBA, 0x3C, 0x11, | ||
0x19, 0x09, | ||
]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This hex string 0x6f722aa250221af2eba9868fc9d7d43994794177dd6fa7766e3e72ba3c111909
can be found here. It represents (A|B)&(LO)
0xD7, 0x9C, 0x1D, 0x3F, 0x71, 0xF6, 0xC9, 0xD9, 0x98, 0x35, 0x3B, 0xA2, 0xA8, 0x48, 0xE5, | ||
0x96, 0xF0, 0xC6, 0xC1, 0xA9, 0xF6, 0xFA, 0x63, 0x3F, 0x2C, 0x9E, 0xC6, 0x5A, 0xAA, 0x09, | ||
0x7C, 0xDC, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I couldn't find this hash. It's probably generated using the method described here.
#[test_case( | ||
[0x16, 0x74, 0xab, 0x10, 0xed, 0xf8, 0xc4, 0xe2, 0x25, 0x72, 0x9e, 0x20, 0x9a, 0x58, 0x75, 0xa1, 0x9f, 0x14, 0x46, 0xba, 0xec, 0x3b, 0x30, 0xdf, 0x9b, 0xa8, 0x65, 0x75, 0xd5, 0x2d, 0xe3, 0xd3], | ||
( | ||
"0x1674ab10edf8c4e225729e209a5875a19f1446baec3b30df9ba86575d52de3d3", | ||
"0x1919edaf92ff08c3c5a2a5dafef1a0c01376dab9681be7fbe3895a18b96af98e", | ||
) | ||
)] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How were these values generated? How can I reproduce them?
[0x83, 0x15, 0x48, 0x88, 0xe8, 0x2c, 0xe4, 0xfc, 0x32, 0xc2, 0xd5, 0xcd, 0x76, 0x6f, 0xfd, 0xc1, 0x8a, 0x8b, 0x00, 0xd9, 0xb7, 0x18, 0x15, 0xc7, 0x2c, 0x52, 0x38, 0x91, 0x11, 0x4e, 0x19, 0xca], | ||
( | ||
"0x224caba325c9a4a8c2224a60736d4d065b882bb6e63480acb411206360541f3f", | ||
"0x2f69f0116b9c27402783c23a83890dd2e8d11598c875b4562ac61f8716674497", | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How were these values generated? How can I reproduce them?
No description provided.