-
Notifications
You must be signed in to change notification settings - Fork 646
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
BSIP38 add target_cr option to call order #838
Conversation
/** this is slightly more expensive than limit orders, this pricing impacts prediction markets */ | ||
struct fee_parameters_type { uint64_t fee = 20 * GRAPHENE_BLOCKCHAIN_PRECISION; }; | ||
|
||
asset fee; | ||
account_id_type funding_account; ///< pays fee, collateral, and cover | ||
asset delta_collateral; ///< the amount of collateral to add to the margin position | ||
asset delta_debt; ///< the amount of the debt to be paid off, may be negative to issue new debt | ||
extensions_type extensions; | ||
|
||
typedef vector<extension<options_type>> extension_type; // use a vector here to be compatible with old JSON |
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.
Wrapping the extension in a 1-element-vector is ugly. Is it really worth it?
I suppose that everyone is ignoring the extensions at this time, so I'd tend to sacrifice JSON compatibility for future readability.
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.
Sacrificing JSON compatibility means all UI / bot / client software need to update before any API node is updated (before and after hard fork), and need to use different JSON format for different version of nodes (before hardfork). It affects not only reading from the blockchain, but also broadcasting new transactions. Giving that we have many different clients now, I don't think it's a good idea to break the compatibility. (I personally is using a 2016 build of bitshares-ui and don't want to upgrade :P)
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.
Actually for clients that submit extensions, an empty array is treated like an empty object: https://github.com/bitshares/bitshares-core/blob/master/libraries/chain/include/graphene/chain/protocol/ext.hpp#L169
So we're breaking compatibility only when reading from the chain, and even there it won't matter in client languages that treat objects and arrays mostly the same (like JavaScript).
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 old clients can still broad transactions with new nodes even without the vector
. That makes sense. I'll change this as suggested.
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.
Removed vector
.
call.call_price = price::call_price(call.get_debt(), call.get_collateral(), | ||
_bitasset_data->current_feed.maintenance_collateral_ratio); | ||
} | ||
call.target_collateral_ratio = new_target_cr; |
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 usual semantics of *_update operations is "if absent leave untouched", while here it is "if absent delete". This is confusing.
Perhaps make the extension field an optional<optional<uint16_t>>, or use a special value like 0 for deletion.
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 don't think optional<optional<uint16_t>>
can be jsonified correctly.
Since type of that field is optional
already, it would be always confusing about how to delete it, IMHO it's easier to treat it as an always-required field, aka always update.
Another approach is to use 2 fields, one for existence and the other for the real value. But I think it's too complicated since the optional
type is just for doing this.
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'd still prefer to keep the "if absent leave untouched" semantic. I suppose the option will not be available in the UI (all UI's) very soon, so someone who is used to trade via UI might want to set the tcr once via cli_wallet and continue normal operation using the UI.
Since tcr < 1000 doesn't make sense anyway, you could treat and invalid value (i. e. < 1000) as a deliberate delete request.
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 still think it's clearer to just update as is (which is written in BSIP).
Compare the two approaches:
- update as is
- found null in op -> set null in obj
- found x in op -> set x in obj
- update by checking if it's a special number
- found null in op -> don't change obj (no matter what it was)
- found 0 in op -> set null in obj
- found x(>0) in op -> set x(>0) in obj
IMHO the 2nd one will lead to more confusion.
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.
Another thing related is that call_order_update_operation
is not only for updating a call order, but also for creating and removing, although removing is irrelevant here.
If the option is in "if absent leave untouched" semantic, the user may expect that the system will "remember" the option when a position is closed (either intendedly or unintendedly), also sometimes may forget to set when recreating a new position, both may lead to unintended behavior.
Compare the two approaches (I think the 1st one is better):
- create new position as is
- found null in op -> set null in obj
- found x in op -> set x in obj
- create new position checking if it's a special number
- found null in op -> set null in obj?
- found 0 in op -> set null in obj
- found x(>0) in op -> set x(>0) in obj
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.
Well, seems we have to agree to disagree (again ;-) ).
BSIP is authoritative though, so I'm OK with leaving it as it is.
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.
Perhaps propose a BSIP change? E.G. drop optional
from object, use value 0
as "not set" instead.
|
||
/// Calculate maximum quantity of collateral to sell and debt to cover to meet @ref target_collateral_ratio. | ||
/// @return a pair of assets, the first item is collateral to sell, the second is debt to cover | ||
pair<asset, asset> get_max_sell_receive_pair( price match_price, |
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 return both? You only ever use the second member of the return 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.
Actually, in the beginning, I only returned the first member; while coding I noticed that the second is needed, so changed it to return a pair; at last I found the first is not being used in main code, but is heavily used in test cases, in addition I'm not sure whether it will be useful for potential new APIs, so decided to leave it there.
Do you think it's necessary to change it to only return the second?
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.
Not strictly necessary, but a significant simplification IMO.
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.
Removed pair
.
libraries/chain/market_object.cpp
Outdated
|
||
max_amount_to_sell = ( (debt + x) * target_CR - collateral * feed_price ) | ||
/ (target_CR * match_price - feed_price) | ||
= ( (debt + x) * tCR / DENOM - collateral * fp_debt_amt / fp_coll_amt + target_CR * x ) |
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 last product in this line (target_CR*x) is too much.
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.
Thanks. Updated.
libraries/chain/market_object.cpp
Outdated
const uint16_t maintenance_collateral_ratio )const | ||
{ try { | ||
// make sure feed_price is in collateral / debt format | ||
if( feed_price.base.asset_id != call_price.base.asset_id ) |
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 check should always be true (feed price is defined as debt/collateral, see asset.hpp).
(That's just a remark. Probably a good idea to be defensive here.)
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.
Yes, the caller may pass in a reversed price pair.
libraries/chain/market_object.cpp
Outdated
if( feed_price.base.asset_id != call_price.base.asset_id ) | ||
feed_price = ~feed_price; | ||
|
||
FC_ASSERT( feed_price.base.asset_id == call_price.base.asset_id |
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.
Should always be true, by definition.
(That's just a remark. Probably a good idea to be defensive here.)
tests/tests/operation_tests.cpp
Outdated
GRAPHENE_REQUIRE_THROW( borrow( alice, bitusd.amount(100000), core.amount(400000), 1750 ), fc::exception ); | ||
GRAPHENE_REQUIRE_THROW( borrow( alice, bitusd.amount(100000), core.amount(400000), 1751 ), fc::exception ); | ||
GRAPHENE_REQUIRE_THROW( borrow( alice, bitusd.amount(100000), core.amount(400000), 65535 ), fc::exception ); | ||
|
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.
Please add a test that wraps a call_order_update with extension in a proposal.
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.
It's planned but not yet done. Will do definitely.
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.
Added proposal tests.
" sell full (perfect), sell full (<0.01%), sell full (<0.1%),sell full (<1%), sell full (other), ...," | ||
" sell some (perfect), sell some (<0.01%), sell some (<0.1%),sell some (<1%), sell some (other), ... ]" ); | ||
idump( (total)(count) ); | ||
|
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.
Please add some tests for the difficult edge cases. I suppose mssr=1001 and mcr=tcr=1002 or something will be problematic.
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.
Will do.
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.
Added 1001/1002 to random tests. The result shows that ~99.6% will cover full debt.
libraries/chain/protocol/market.cpp
Outdated
if( extensions.size() > 0 ) | ||
{ | ||
const auto& options = extensions.front().value; | ||
size_t count = 0; // use a count variable for future expansions |
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 wouldn't make assumptions about the logic of future extensions. Just check that tcr is valid right now, instead of counting stuff. (See account extensions for example.)
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.
IMHO nonempty is basic requirement for extensions
field. Additional validation may or may not apply when adding new fields (no more validation this time since TCR is optional). Btw there are more fields to be added as discussed in bitshares/bsips#51, rather than counting after added them, I decided to count from now on.
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.
In regards to account extensions, since it's not using a container which can jsonify to an array, but replaced flat_set<static_variant<X>>
with extension<Y>
directly (thus have broken JSON compatibility), it should be allowed if the extensions field is empty. So there are individual validations about the fields inside.
I used a vector here, so doesn't make sense to allow an empty object inside. The validation code here is for different things.
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.
Even when more fields are added, the actual count is irrelevant, as long as it is > 0.
If we remove the vector wrapper, count==0 will have to be accepted, which makes the whole check here useless.
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.
Removed vector
, so removed validation.
libraries/chain/protocol/asset.cpp
Outdated
@@ -97,15 +97,19 @@ namespace graphene { namespace chain { | |||
const asset& a = *this; | |||
if( a.asset_id == b.base.asset_id ) | |||
{ | |||
// TODO replace exception type with price_multiplication_undefined? |
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.
Please remove these unrelated comments. If you think this might be a good idea, open a github issue.
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.
Just realized that the definitions of those exceptions are commented out. Will remove the unrelated comments.
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.
Removed.
More test cases added. Ready for final review. |
tests/tests/operation_tests.cpp
Outdated
tx.operations.push_back( prop ); | ||
db.current_fee_schedule().set_fee( tx.operations.back() ); | ||
set_expiration( db, tx ); | ||
db.push_transaction(trx, ~0); |
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.
You're pushing the wrong transaction here... :-)
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.
Good catch 💯
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 kind of bug is hard to detect, especially when the code is in a lambda, changing [&]
to [this]
here doesn't help. :-(
What's best practice?
This is PR for #834, aka BSIP38: Add target collateral ratio option to short positions.
The code is ready for review
, but still need (lots of) test cases.call_order_update_operation
with extension in a proposal is not allowed before hard fork