-
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
Add market ID to Market
struct
#1248
Conversation
Codecov ReportAttention:
❗ Your organization needs to install the Codecov GitHub app to enable full functionality. Additional details and impacted files@@ Coverage Diff @@
## mkl-market-id-feature #1248 +/- ##
=========================================================
+ Coverage 92.83% 92.91% +0.08%
=========================================================
Files 154 158 +4
Lines 31321 31444 +123
=========================================================
+ Hits 29076 29216 +140
+ Misses 2245 2228 -17
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. |
Ok(Market { | ||
market_id: ok_or_incomplete::<T, _>(self.market_id)?, | ||
base_asset: ok_or_incomplete::<T, _>(self.base_asset)?, | ||
creator: ok_or_incomplete::<T, _>(self.creator)?, | ||
creation: ok_or_incomplete::<T, _>(self.creation)?, | ||
creator_fee: ok_or_incomplete::<T, _>(self.creator_fee)?, | ||
oracle: ok_or_incomplete::<T, _>(self.oracle)?, | ||
metadata: ok_or_incomplete::<T, _>(self.metadata)?, | ||
market_type: ok_or_incomplete::<T, _>(self.market_type)?, | ||
period: ok_or_incomplete::<T, _>(self.period)?, | ||
deadlines: ok_or_incomplete::<T, _>(self.deadlines)?, | ||
scoring_rule: ok_or_incomplete::<T, _>(self.scoring_rule)?, | ||
status: ok_or_incomplete::<T, _>(self.status)?, | ||
report: ok_or_incomplete::<T, _>(self.report)?, | ||
resolved_outcome: ok_or_incomplete::<T, _>(self.resolved_outcome)?, | ||
dispute_mechanism: ok_or_incomplete::<T, _>(self.dispute_mechanism)?, | ||
bonds: ok_or_incomplete::<T, _>(self.bonds)?, | ||
early_close: ok_or_incomplete::<T, _>(self.early_close)?, | ||
}) |
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 do we have a builder pattern, if only the complete market (with all fields) can be built? Shouldn't we rather provide an update
function for all the fields instead of having the possibility of initialising with an 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.
Ignoring edit_market
, which is on its way out, we actually wouldn't be using the update
function. The whole reason why I'm using the builder pattern here is to ensure that part of the data can be set in prediction-markets, the builder then passed to market-commons, where the market ID is set. This means that we have to allow some of the fields to be incomplete (at least for a while).
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 would avoid all this confusion of the tangle of Option
and maintainer error possibility by using this approach.
@@ -2128,7 +2131,8 @@ mod pallet { | |||
bonds.total_amount_bonded(&who), | |||
)?; | |||
|
|||
let market_id = <zrml_market_commons::Pallet<T>>::push_market(market.clone())?; | |||
let (market_id, market) = | |||
<zrml_market_commons::Pallet<T>>::build_market(market_builder)?; |
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 would simply avoid the builder pattern by peeking the next market id, then construct the market structure and then push the market.
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 don't we even decouple market id creation from push_market
?
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.
So basically just throw let market_id = Self::next_market_id()?;
out of push_market
and use it directly.
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.
Again, builder pattern makes total sense, if you just care about a few fields for example in mocks, benchmarking and tests to get to a working structure quick and not having the full structure initialisation and associated maintenance if the underlying structure changes all the time.
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 also generally seem to derive a sense of a will of you to put all sorts of "clean code" patterns in the business logic without taking a step back to reconsider if those patterns are really appropriate for this specific scenario and couldn't be done more simple. I don't encourage anti-patterns either. I am just open to avoid "clean code" patterns without introducing "anti-patterns" for the sake of simplicity. In your specific case, it's even preventing a maintenance error named IncompleteMarketBuilder
.
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 don't we even decouple market id creation from push_market?
Because they belong together. Placing the Market
object in storage is the responsibility of market-commons. Part of that is ensuring that 1) the correct next market ID is used; 2) (this is new) the storage key matches the market_id
field of the market.
If we let other modules set the market ID, we take that responsiblity away from market-commons and place it in the hands of the callers. This risks that they put the wrong market ID into the market struct, overwrite old markets or skip market IDs. This is just basic encapsulation.
To maintain encapsulation, we need to be able to pass the market creation parameters to market-commons without setting the market_id
field. The builder pattern seemed appropriate to me.
Moreover, as I've pointed out in the description, the builder pattern will make even more sense when we get rid of edit_market
and can move valdiation functionality into the trait implementation. This allows us to move validation out of extrinsics and free functions of a bloated lib.rs
into separate objects.
And once edit_market
is done for, we can even remove certain setters. For example, why would we have a setter for the early_close
field? Clearly, a newly created market should never have an early close scheduled, right? These will be removed as soon as edit_market
is gone. This further encapsulates the market creation.
I also generally seem to derive a sense of a will of you to put all sorts of "clean code" patterns in the business logic without taking a step back to reconsider if those patterns are really appropriate for this specific scenario and couldn't be done more simple.
I'm indeed very concerned with increasing the code quality of our code base. As for "[not] taking a step back", I'm surprised I'm making that impression. Not sure what to tell you except that I always weigh the available options very carefully.
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 understand your point about encapsulation. My approach wasn't meant to be the best either ;-), but a first attempt. Yeah, this wasn't in my mind, but I still don't like the ImcompleteMarketBuilder
error either. And the fact that we have a "complex" market builder because of one field for this huge struct. Another option would be to have a function with a lot of parameters for all market fields or a PartialMarket
struct passed to this function.
Moreover, as I've pointed out in the description, the builder pattern will make even more sense when we get rid of edit_market and can move valdiation functionality into the trait implementation. This allows us to move validation out of extrinsics and free functions of a bloated lib.rs into separate objects.
This sounds interesting, I like the idea a lot. Why not using a function check_market(market)
and validate everything inside it? Then we could also get rid of the bloated lib.rs. I mean the logic of validation needs to sit somewhere anyways.
And once edit_market is done for, we can even remove certain setters. For example, why would we have a setter for the early_close field? Clearly, a newly created market should never have an early close scheduled, right? These will be removed as soon as edit_market is gone. This further encapsulates the market creation.
This is a good point. However for market creation, you could just use a PartialMarket
without an early close field and assign None
to it inside the market-commons pallet. In that way the market's early close hasn't to be assigned in the prediction markets pallet either.
As for "[not] taking a step back", I'm surprised I'm making that impression.
That's why I said seem
. It's very subtle and I wanted to alert about things early to improve overall readability and wanted to challenge assumptions. At the end, I try (also fail a lot ^^) to make you even better ;-) nothing else. It was never about me offending your code style. I just want to find out the truth about the best possible solution.
Not sure what to tell you except that I always weigh the available options very carefully.
❤️
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.
The whole purpose of a builder pattern is to be capable to create different representations of the same object in a clean and structured manner. So my question is, is there any foreseen utilization of that feature? Will the Market
object ever be created with different fields set or not set? If the answer is yes, then this is the way to go. If the answer is no, then simply create a constructor for Market
that is invoked in market-commons
within a function that resembles the constructor but does not contain the market_id
(as it is derived within that function).
I am not quite sure how updating markets is planned to work in the future, but I think in any case it makes sense to apply a good design pattern and make the fields of Market
private while providing getters for all fields and setters for fields that can change during the lifetime of a market.
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.
That's not the only purpose of the builder struct. Here, the purpose is to allow the Market
object to be created piece-by-piece across multiple functions. This is the case here. It also allows placing the validation logic somewhere outside of the prediction-marktes pallet.
I think I've made my point here and in the long discussion above. Could somebody else please make a decision so we can get this out the door?
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 see, if that's the requirement than this is the correct design choice. Move forward.
* Add market ID to `Market` struct (#1248) * Add market ID to `Market` struct * Add market builder struct * Use `PredictionMarketBuilder` * Fix issues * Fix copyright * Make `build` return a `Result` * Let `build` raise an error on incomplete data. * Fix formatting * Refactor `MarketBuilder` * Add missing files * AMM-CDA-1: Switch to AmmCdaHybrid enum field (#1274) * switch to AmmCdaHybrid enum field * Update zrml/parimutuel/src/tests/buy.rs Co-authored-by: Malte Kliemann <[email protected]> * Update zrml/parimutuel/src/tests/claim.rs Co-authored-by: Malte Kliemann <[email protected]> * Update zrml/parimutuel/src/tests/refund.rs Co-authored-by: Malte Kliemann <[email protected]> * Update storage version to 11 and add MigrateScoringRuleAmmCdaHybrid * Update primitives/src/market.rs * update migration --------- Co-authored-by: Malte Kliemann <[email protected]> * merge amm-cda-3 changes * merge amm-cda-2 changes * merge amm-cda-4 changes * correct clippy * merge amm-cda-5 changes * merge amm-cda-6 changes * Fix Hybrid Router clippy and tests (#1291) * glue everything together * add hybrid router to configuration * fix tests * fix conditional tests * AMM-CDA-8 Add event information for fees and aggregated amount_out (#1293) * add event info * use slice as function parameter * update test amm amount out of event * fix order book tests * update documentation * removed dependency * Merge `main` into `mkl-market-id-feature` (#1302) * Merge `main` into `mkl-mkl-market-id-feature` * Remove unused `outcomes` * Remove old migrations (#1301) * AMM-CDA-9 Adds soft and hard failure distinction for AMM and order book errors (#1294) * add event info * use slice as function parameter * update test amm amount out of event * wip * handle soft and hard failure * add order book soft failure * fix clippy * fix CI * remove swaps pallet dependency * add compact to order book struct * fix recursion overflow * Migration: Add Market ID to Market (#1257) * Add market ID to `Market` struct * Add market builder struct * Use `PredictionMarketBuilder` * Fix issues * Fix copyright * Make `build` return a `Result` * Let `build` raise an error on incomplete data. * Fix formatting * Refactor `MarketBuilder` * Add missing files * Add migration to new market * Fix migration * Fix missing import * Fix duplicate import * Fix formatting * Remove unused `types/` * Fix of Hybrid Router after asset system merge (#1309) * wip * use asset conversions * adapt hybrid router to new asset system * apply review suggestions * fmt * rename Asset to Assets * update copyrights * Make minor fixes after merge * update hybrid router crate version * correct orderbook spelling * add amount is zero tests * add price limit too high test * add max order exceeded test * use saturated conversion * use saturated conversion again * remove unused code * Update changelog * add tests for soft failures * add skip order test * add changelog for devs description * add amm soft failure test * add numerical soft failure test for sell * Remove ZeitgeistAssetManager trait * correct failing test for parachain feature * cover remaining is zero execution path * Fix try-runtime test --------- Co-authored-by: Chralt <[email protected]> Co-authored-by: Chralt98 <[email protected]> Co-authored-by: Harald Heckmann <[email protected]>
What does it do?
Adds market ID to
Market
struct. To safely handle writing the market ID, we use aMarketBuilder
trait. This may look like a bit much, but is exactly the right pattern here.I've added
build_market
to the market-commons interface to replacepush_market
. The latter will stick around so that I can avoid rewriting the tests of all other pallets depending onpush_market
. But it is deprecated and pallet maintainers are encouraged to get rid of it!What important points should reviewers know?
We're doing this in order to be able to implement
Market::asset(&self)
, which requires access to the market ID.Is there something left for follow-up PRs?
Market::asset
.edit_market
; move validation logic intoPredictionMarketBuilder
.What alternative implementations were considered?
We considered the following alternatives:
assets(&self)
for(MarketId, Market)
instead ofMarket
.assets
as a free function.edit_market
and move validation logic toMarketBuilder::build()
. We're currently not doing this because the validation is a bit iffy withedit_market
still in the mix.We settled on this particular option since it seemed to be safest. The only concern is that it's possible to add a
Market
instancemarket
to the storage mapMarkets
with keymarket_id
so thatmarket_id != market.market_id
. However, this is not the case if the market is added usingbuild_market
.The whole
MarketBuilder
trait is only used to avoid a user being able to mess with a market's ID. We could have gone withmarket_id: Some(MarketId)
as field in theMarket
struct, but that would have meant that need more storage per market for no reason other than that we're too lazy to implement the builder struct.Are there relevant PRs or issues?
References