Add BSIP 35
This commit is contained in:
parent
3a526a4109
commit
5fabc12f16
1 changed files with 283 additions and 0 deletions
283
bsip-0035.md
Normal file
283
bsip-0035.md
Normal file
|
@ -0,0 +1,283 @@
|
|||
BSIP: 0035
|
||||
Title: A Solution To Something-For-Nothing Issue
|
||||
Author: Abit More <https://github.com/abitmore>
|
||||
Status: Draft
|
||||
Type: Protocol
|
||||
Created: 2018-02-19
|
||||
Discussion: https://github.com/bitshares/bitshares-core/issues/132,
|
||||
https://github.com/bitshares/bitshares-core/issues/184
|
||||
Replaces: -
|
||||
Worker: To be done
|
||||
|
||||
# Abstract
|
||||
|
||||
Under some circumstances, when two orders get matched, due to rounding,
|
||||
one order may be paying something but receiving nothing,
|
||||
the other order may be paying nothing but receiving something.
|
||||
This is the so-called something-for-nothing issue.
|
||||
|
||||
This looks clearly unfair.
|
||||
|
||||
This BSIP proposes an overall mechanism to avoid something-for-nothing issue
|
||||
completely.
|
||||
|
||||
This BSIP also sets a principle: something-for-nothing shouldn't happen when
|
||||
matching orders.
|
||||
|
||||
# Motivation
|
||||
|
||||
There are mechanisms in the system to try to avoid something-for-nothing issue,
|
||||
however, not all scenarios are well-handled, see [bitshares-core
|
||||
issue #184](https://github.com/bitshares/bitshares-core/issues/184) for example.
|
||||
|
||||
# Rationale
|
||||
|
||||
## Amounts, Prices and Rounding
|
||||
|
||||
Amounts in the system are integers with per-asset fixed precisions.
|
||||
The minimum positive amount of an asset is called one Satoshi.
|
||||
|
||||
Prices in the system are rational numbers, which are expressed as
|
||||
`base_amount / quote_amount` (precisions are omitted here).
|
||||
|
||||
To calculate how much amount of asset B is equivalent to some amount of
|
||||
asset A, need to calculate `amount_of_a * a_to_b_price` which is
|
||||
`amount_of_a * b_amount_in_price / a_amount_in_price`. The accurate result
|
||||
of this formula is a rational number. To convert it to the final result which
|
||||
is an amount, which is an integer, may need to round.
|
||||
|
||||
## Order Matching
|
||||
|
||||
An order means someone is willing to give out some amount of asset X expecting
|
||||
to get some amount of asset Y. The ratio between the two assets is the price of
|
||||
the order. The price can be expressed as either `x_amount / y_amount` or
|
||||
`y_amount / x_amount`, when we know which amount in the price is of which asset,
|
||||
the two expressions are equivalent. The amount of asset X is known and fixed.
|
||||
|
||||
In a market, E.G. the X:Y market, some people are selling X for Y, some people
|
||||
are selling Y for X (or say buying X with Y). Orders are classified by type
|
||||
(buy or sell), then ordered by price. For each type, the order offering the
|
||||
best price is on the top. So, in every market there may be a top buy order and
|
||||
a top sell order, name them highest bid and lowest ask, so there is
|
||||
a highest bid price (in terms of `asset X amount / asset Y amount`),
|
||||
and a lowest ask price (in terms of `asset X amount / asset Y amount` as well).
|
||||
|
||||
When the highest bid price is higher or equal to the lowest ask price, the two
|
||||
top orders can be matched with each other.
|
||||
|
||||
## The Match Price
|
||||
|
||||
In a continuous trading market, orders are placed one by one, when comparing
|
||||
every two orders, it's deterministic that one order is placed earlier than the
|
||||
other.
|
||||
|
||||
In BitShares, it doesn't mean that the transaction that contains the first
|
||||
order is signed before the transaction contains the second, but means that
|
||||
the first order is processed earlier than the second in the witness node that
|
||||
produced the block that contains the second order.
|
||||
|
||||
When two orders get matched, the one placed earlier is maker, the other one
|
||||
is taker. Say, the maker provides an offer, the taker accept the offer.
|
||||
So, when calculating who will get how much, we use the maker order's price,
|
||||
aka maker price, as the match price.
|
||||
|
||||
## The Need for Compromise
|
||||
|
||||
When matching two orders, due to rounding, usually we're unable to completely
|
||||
satisfy both parties.
|
||||
|
||||
Here is an example mentioned in the 4th comment of [bitshares-core
|
||||
issue #132](https://github.com/bitshares/bitshares-core/issues/132):
|
||||
|
||||
Alice's order: Sell CORE at $3 / 8, balance 1000000 CORE
|
||||
Bob's order: Buy CORE at $19 / 50, balance $10
|
||||
|
||||
Both assets have precision of 1, i.e. the order balances are
|
||||
1000000 CORE-satoshis and 10 USD-satoshis repectively.
|
||||
|
||||
Alice is selling at $3/8 CORE = $0.375 / CORE and Bob is buying at
|
||||
$19 / 50 CORE = $0.38, so based on the price, Alice and Bob should match.
|
||||
|
||||
Bob's $10 / $0.38 ~ 26.3. So 26.3 is the fewest CORE he is willing to accept
|
||||
(assuming that the meaning of "price" is "the least favorable exchange rate
|
||||
a party is willing to accept in trade"). Combined with the design restriction
|
||||
that satoshis are indivisible, in practice this means Bob will only accept 27
|
||||
or more CORE for his $10.
|
||||
|
||||
But $10 / 27 gives a price smaller than $0.370 and $0.371, which is smaller
|
||||
than Alice's sale price of $0.375. So neither party can fill this offer.
|
||||
|
||||
We need to come to a compromise.
|
||||
|
||||
## The Possible Solutions
|
||||
|
||||
There are some possible solutions listed in the 5th comment of [bitshares-core
|
||||
issue #132](https://github.com/bitshares/bitshares-core/issues/132):
|
||||
|
||||
- (a) Fill someone at a less favorable exchange rate than the price they
|
||||
specified in their order. Downside: This violates the above definition of
|
||||
price; i.e. if a user enters a price intending the system to never sell below
|
||||
that price in any circumstance, the system will not always behave in a way
|
||||
which fulfills that user intent.
|
||||
|
||||
- (b) Keep both orders on the books. Downside: This complicates the matching
|
||||
algorithm, as now Alice might be able to match an order behind Bob's order.
|
||||
Naive implementation would have potentially unbounded matching complexity;
|
||||
a more clever implementation might be possible but would require substantial
|
||||
design and testing effort.
|
||||
|
||||
- (c) Cancel an order. This is complicated by the fact that an order such as
|
||||
a margin call cannot be cancelled. Downside: When there are margin calls
|
||||
happening, it seems perverse to delete a large order that's willing to fill
|
||||
them just because the lead margin call happens to fall in a narrow window
|
||||
which causes a rounding issue. Also, orders cancelled by this mechanism
|
||||
cannot be refunded. Otherwise an attacker who wants to consume
|
||||
a lot of memory on all nodes could create a large number of orders, then
|
||||
trigger this case to cancel them all, getting their investment in deferred
|
||||
cancellation fees back without paying the cancel op's per-order fee as
|
||||
intended.
|
||||
|
||||
- (d) Require all orders to use the same denominator. Altcoin exchanges and
|
||||
many real-world markets like the stock market solve this problem by specifying
|
||||
one asset as the denominator asset, specifying a "tick" which is the smallest
|
||||
unit of price precision, and requiring all prices to conform.
|
||||
Downside: Complicates the implementation of flipped market UI, may require
|
||||
re-working part of market GUI, reduces user flexibility, new asset fields
|
||||
required to specify precision, if `n` assets exist then `O(n^2)` markets
|
||||
could exist and we need to figure out how to determine the precision
|
||||
requirement for all of them.
|
||||
|
||||
## The Chosen Solution
|
||||
|
||||
Current code actually implemented (a) in the first place: when matching two
|
||||
orders, if there is a rounding issue, the order with smaller volume will be
|
||||
filled at a less favorable price. It's the least bad compromise since it has
|
||||
the most efficiency (highest traded volume while not hard to implement) among
|
||||
the solutions.
|
||||
|
||||
However, when filling a small order at a less favorable price, the receiving
|
||||
amount is often rounded down to zero, thus causes the something-for-nothing
|
||||
issue. Current code tried to solve the issue by cancelling the smaller order
|
||||
when it would receive nothing, but only applied this rule in a few senarios:
|
||||
* when matching two limit orders, processed the maker
|
||||
* when matching a maker limit order with a call order, processed the maker
|
||||
* when matching a limit order with a call order, processed the call order
|
||||
* when matching a settle order with a call order, processed the call order
|
||||
* when globally settling, processed the call order
|
||||
|
||||
Other senarios that need to be processed as well:
|
||||
* when matching two limit orders, process the taker
|
||||
* when matching a taker limit order with a call order, process the taker
|
||||
* when matching a force settle order with a call order, process the settle order
|
||||
* when globally settling, process the settlement fund
|
||||
|
||||
## The Improved Solution (This BSIP)
|
||||
|
||||
The detailed rules proposes in this BSIP (new rules highlighted):
|
||||
* match in favor of taker, or say, match at maker price
|
||||
* round down receiving amounts when possible
|
||||
* when matching two limit orders, round down the receiving amounts in favor
|
||||
of bigger order, or say, try to fill the smaller order
|
||||
* **if the smaller order would get nothing after the round-down, cancel it**
|
||||
* when matching a limit order with a call order, in favor of call order,
|
||||
round down receiving collateral amount
|
||||
* **if the limit order would get nothing after the round-down, cancel it**
|
||||
* when matching a settle order with a call order, in favor of call order,
|
||||
round down receiving collateral amount
|
||||
* **if the settle order would get nothing after the round-down, give it one
|
||||
Satoshi (round up); after paid both side, check (and allow) if a black swan
|
||||
is triggered by the round-up**
|
||||
* when globally settling, in favor of call order, round down receiving
|
||||
collateral amount
|
||||
* **when the asset is not a prediction market, if a call order would pay
|
||||
nothing, let it pay 1 Satoshi (round up).**
|
||||
|
||||
Take the example mentioned in the 4th comment of [bitshares-core
|
||||
issue #132](https://github.com/bitshares/bitshares-core/issues/132):
|
||||
* Alice's order: Sell CORE at `$3 / 8 = $0.375`, balance `1000000 CORE`
|
||||
* Bob's order: Buy CORE at `$19 / 50 = $0.38`, balance `$10`
|
||||
|
||||
Process:
|
||||
* If both orders are limit orders
|
||||
* If Alice's order is maker, use `$3 / 8` as match price;
|
||||
since Bob's order is smaller, round in favor of Alice's order,
|
||||
so Bob will get
|
||||
`round_down($10 * 8 CORE / $3) = round_down(26.67 CORE) = 26 CORE`,
|
||||
the effective price would be `$10 / 26 CORE = $0.3846`.
|
||||
* If Bob's order is maker, use `$19 / 50` as match price; since Bob's
|
||||
order is smaller, round in favor of Alice's order, so Bob will get
|
||||
`round_down($10 * 50 CORE / $19 = round_down(26.32 CORE) = 26 CORE`,
|
||||
the effective price would still be `$10 / 26 CORE = $0.3846`.
|
||||
* If Alice's order is a call order, always round in favor of it, we get
|
||||
same results.
|
||||
|
||||
If we change the example to this:
|
||||
* Alice's order: Buy CORE at `3 CORE / $8 = 0.375`, balance `$1000000`
|
||||
* Bob's order: Sell CORE at `19 CORE / $50 = 0.38`, balance `10 CORE`
|
||||
|
||||
Process:
|
||||
* If both orders are limit orders, we get same results as above
|
||||
* If Bob's order is a call order, we should always round in favor of it,
|
||||
however, it should have a debt amount which is an integer, for example
|
||||
`$27`, then Alice would get
|
||||
* `round_down(27 * 3 / 8) = round_down(10.125) = 10 CORE` as a maker, or
|
||||
* `round_down(27 * 19 / 50) = round_down(10.26) = 10 CORE` as a taker.
|
||||
|
||||
|
||||
# Specifications
|
||||
|
||||
## When Matching Two Limit Orders
|
||||
|
||||
In `match( const limit_order_object&, OrderType ... )` function of `database`
|
||||
class, after calculated `usd_receives` which is for the taker,
|
||||
check if it is zero.
|
||||
If the answer is `true`, skip filling and see the order is filled, return `1`,
|
||||
so the order will be cancelled later.
|
||||
|
||||
## When Matching A Limit Order With A Call Order
|
||||
|
||||
In `check_call_orders(...)` function of `database` class,
|
||||
after calculated `order_receives`, check if it is zero.
|
||||
If the answer is `true`, and the limit order is taker, skip filling and cancel
|
||||
the limit order.
|
||||
|
||||
## When Matching A Settle Order With A Call Order
|
||||
|
||||
In `match( const call_order_object&, ... )` function of `database` class,
|
||||
after calculated `call_pays`, check if it is zero.
|
||||
If the answer is `true`, round up it to `1`.
|
||||
|
||||
If rounded up, after filled both orders, check and allow a black swan event.
|
||||
|
||||
## When Globally Settling
|
||||
|
||||
In `global_settle_asset(...)` function of `database` class, check each `pays`,
|
||||
once it's zero, and the asset is not a prediction market, let it be `1`.
|
||||
|
||||
# Discussion
|
||||
|
||||
There is an argument suggests when matching call orders, we should always
|
||||
round in favour of the call. If a settlement receives 0 collateral as a result,
|
||||
that's acceptable, because the settlement price is unknown at the time when
|
||||
settlement is requested, so no guarantee is violated (within the range of
|
||||
rounding errors). This should keep the collateral > 0 as long as there is
|
||||
outstanding debt. A counter-argument supports rounding up to 1 Satoshi since
|
||||
rounding down to zero may break the promise of "every smart coin is backed
|
||||
by something".
|
||||
|
||||
There is an argument says breaking the `min_to_receive` limit is a no-go,
|
||||
because that's why it's called a "limit order". A counter-argument says
|
||||
slightly breaking the limit is the least bad compromise.
|
||||
|
||||
# Summary for Shareholders
|
||||
|
||||
[to be added if any]
|
||||
|
||||
# Copyright
|
||||
|
||||
This document is placed in the public domain.
|
||||
|
||||
# See Also
|
||||
|
||||
* https://github.com/bitshares/bitshares-core/issues/132
|
||||
* https://github.com/bitshares/bitshares-core/issues/184
|
Loading…
Reference in a new issue