BitShares-Core  5.0.0
BitShares blockchain implementation and command-line interface software
liquidity_pool_evaluator.cpp
Go to the documentation of this file.
1 /*
2  * Copyright (c) 2020 Abit More, and contributors.
3  *
4  * The MIT License
5  *
6  * Permission is hereby granted, free of charge, to any person obtaining a copy
7  * of this software and associated documentation files (the "Software"), to deal
8  * in the Software without restriction, including without limitation the rights
9  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10  * copies of the Software, and to permit persons to whom the Software is
11  * furnished to do so, subject to the following conditions:
12  *
13  * The above copyright notice and this permission notice shall be included in
14  * all copies or substantial portions of the Software.
15  *
16  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22  * THE SOFTWARE.
23  */
26 
28 
31 #include <graphene/chain/hardfork.hpp>
33 
35 
36 namespace graphene { namespace chain {
37 
39 { try {
40  const database& d = db();
41  const auto block_time = d.head_block_time();
42 
43  FC_ASSERT( HARDFORK_LIQUIDITY_POOL_PASSED(block_time), "Not allowed until the LP hardfork" );
44 
45  op.asset_a(d); // Make sure it exists
46  op.asset_b(d); // Make sure it exists
47  _share_asset = &op.share_asset(d);
48 
50  "Only the asset owner can set an asset as the share asset of a liquidity pool" );
51 
53  "Can not specify a market-issued asset as the share asset of a liquidity pool" );
54 
56  "The share asset is already bound to another liquidity pool" );
57 
59  "Current supply of the share asset needs to be zero" );
60 
61  return void_result();
62 } FC_CAPTURE_AND_RETHROW( (op) ) }
63 
65 { try {
66  database& d = db();
68 
69  const auto& new_liquidity_pool_object = d.create<liquidity_pool_object>([&op](liquidity_pool_object& obj){
70  obj.asset_a = op.asset_a;
71  obj.asset_b = op.asset_b;
72  obj.share_asset = op.share_asset;
73  obj.taker_fee_percent = op.taker_fee_percent;
74  obj.withdrawal_fee_percent = op.withdrawal_fee_percent;
75  });
76  result.new_objects.insert( new_liquidity_pool_object.id );
77 
78  result.updated_objects.insert( _share_asset->id );
79  d.modify( *_share_asset, [&new_liquidity_pool_object](asset_object& ao) {
80  ao.for_liquidity_pool = new_liquidity_pool_object.id;
81  });
82 
83  return result;
84 } FC_CAPTURE_AND_RETHROW( (op) ) }
85 
87 { try {
88  const database& d = db();
89 
90  _pool = &op.pool(d);
91 
92  FC_ASSERT( _pool->balance_a == 0 && _pool->balance_b == 0, "Can not delete a non-empty pool" );
93 
94  _share_asset = &_pool->share_asset(d);
95 
96  FC_ASSERT( _share_asset->issuer == op.account, "The account is not the owner of the liquidity pool" );
97 
98  return void_result();
99 } FC_CAPTURE_AND_RETHROW( (op) ) }
100 
102 { try {
103  database& d = db();
105 
106  result.updated_objects.insert( _share_asset->id );
107  d.modify( *_share_asset, [](asset_object& ao) {
109  });
110 
111  result.removed_objects.insert( _pool->id );
112  d.remove( *_pool );
113 
114  return result;
115 } FC_CAPTURE_AND_RETHROW( (op) ) }
116 
118 { try {
119  const database& d = db();
120 
121  _pool = &op.pool(d);
122 
123  FC_ASSERT( op.amount_a.asset_id == _pool->asset_a, "Asset type A mismatch" );
124  FC_ASSERT( op.amount_b.asset_id == _pool->asset_b, "Asset type B mismatch" );
125 
126  FC_ASSERT( (_pool->balance_a == 0) == (_pool->balance_b == 0), "Internal error" );
127 
128  const asset_object& share_asset_obj = _pool->share_asset(d);
129 
130  FC_ASSERT( share_asset_obj.can_create_new_supply(), "Can not create new supply for the share asset" );
131 
132  if( _pool->balance_a == 0 ) // which implies that _pool->balance_b == 0
133  {
134  FC_ASSERT( share_asset_obj.issuer == op.account, "The initial deposit can only be done by the pool owner" );
135  }
136 
137  _share_asset_dyn_data = &share_asset_obj.dynamic_data(d);
138 
139  FC_ASSERT( (_pool->balance_a == 0) == (_share_asset_dyn_data->current_supply == 0), "Internal error" );
140 
141  FC_ASSERT( _share_asset_dyn_data->current_supply < share_asset_obj.options.max_supply,
142  "Can not create new supply for the share asset" );
143 
144  FC_ASSERT( is_authorized_asset( d, *fee_paying_account, share_asset_obj ),
145  "The account is unauthorized by the share asset" );
146  FC_ASSERT( is_authorized_asset( d, *fee_paying_account, _pool->asset_a(d) ),
147  "The account is unauthorized by asset A" );
148  FC_ASSERT( is_authorized_asset( d, *fee_paying_account, _pool->asset_b(d) ),
149  "The account is unauthorized by asset B" );
150 
151  if( _pool->balance_a == 0 )
152  {
153  share_type share_amount = std::max( op.amount_a.amount.value, op.amount_b.amount.value );
154  FC_ASSERT( share_amount <= share_asset_obj.options.max_supply,
155  "For initial deposit, each amount of the two assets in the pool should not be greater than "
156  "the maximum supply of the share asset" );
157  _pool_receives_a = op.amount_a;
158  _pool_receives_b = op.amount_b;
159  _account_receives = asset( share_amount, _pool->share_asset );
160  }
161  else
162  {
163  share_type max_new_supply = share_asset_obj.options.max_supply - _share_asset_dyn_data->current_supply;
164  fc::uint128_t max128( max_new_supply.value );
165  fc::uint128_t supply128( _share_asset_dyn_data->current_supply.value );
166  fc::uint128_t new_supply_if_a = supply128 * op.amount_a.amount.value / _pool->balance_a.value;
167  fc::uint128_t new_supply_if_b = supply128 * op.amount_b.amount.value / _pool->balance_b.value;
168  fc::uint128_t new_supply = std::min( { new_supply_if_a, new_supply_if_b, max128 } );
169 
170  FC_ASSERT( new_supply > 0, "Aborting due to zero outcome" );
171 
172  fc::uint128_t a128 = ( new_supply * _pool->balance_a.value + supply128 - 1 ) / supply128; // round up
173  FC_ASSERT( a128 <= fc::uint128_t( op.amount_a.amount.value ), "Internal error" );
174  _pool_receives_a = asset( static_cast<int64_t>( a128 ), _pool->asset_a );
175 
176  fc::uint128_t b128 = ( new_supply * _pool->balance_b.value + supply128 - 1 ) / supply128; // round up
177  FC_ASSERT( b128 <= fc::uint128_t( op.amount_b.amount.value ), "Internal error" );
178  _pool_receives_b = asset( static_cast<int64_t>( b128 ), _pool->asset_b );
179 
180  _account_receives = asset( static_cast<int64_t>( new_supply ), _pool->share_asset );
181  }
182 
183  return void_result();
184 } FC_CAPTURE_AND_RETHROW( (op) ) }
185 
188 { try {
189  database& d = db();
191 
192  d.adjust_balance( op.account, -_pool_receives_a );
193  d.adjust_balance( op.account, -_pool_receives_b );
194  d.adjust_balance( op.account, _account_receives );
195 
196  d.modify( *_pool, [this]( liquidity_pool_object& lpo ){
197  lpo.balance_a += _pool_receives_a.amount;
198  lpo.balance_b += _pool_receives_b.amount;
199  lpo.update_virtual_value();
200  });
201 
202  d.modify( *_share_asset_dyn_data, [this]( asset_dynamic_data_object& data ){
203  data.current_supply += _account_receives.amount;
204  });
205 
206  FC_ASSERT( _pool->balance_a > 0 && _pool->balance_b > 0, "Internal error" );
207  FC_ASSERT( _share_asset_dyn_data->current_supply > 0, "Internal error" );
208 
209  result.paid.emplace_back( _pool_receives_a );
210  result.paid.emplace_back( _pool_receives_b );
211  result.received.emplace_back( _account_receives );
212 
213  return result;
214 } FC_CAPTURE_AND_RETHROW( (op) ) }
215 
217 { try {
218  const database& d = db();
219 
220  _pool = &op.pool(d);
221 
222  FC_ASSERT( op.share_amount.asset_id == _pool->share_asset, "Share asset type mismatch" );
223 
224  FC_ASSERT( _pool->balance_a > 0 && _pool->balance_b > 0, "The pool has not been initialized" );
225 
226  const asset_object& share_asset_obj = _pool->share_asset(d);
227 
228  FC_ASSERT( is_authorized_asset( d, *fee_paying_account, share_asset_obj ),
229  "The account is unauthorized by the share asset" );
230  FC_ASSERT( is_authorized_asset( d, *fee_paying_account, _pool->asset_a(d) ),
231  "The account is unauthorized by asset A" );
232  FC_ASSERT( is_authorized_asset( d, *fee_paying_account, _pool->asset_b(d) ),
233  "The account is unauthorized by asset B" );
234 
235  _share_asset_dyn_data = &share_asset_obj.dynamic_data(d);
236 
237  FC_ASSERT( _share_asset_dyn_data->current_supply >= op.share_amount.amount,
238  "Can not withdraw an amount that is more than the current supply" );
239 
240  if( _share_asset_dyn_data->current_supply == op.share_amount.amount )
241  {
242  _pool_pays_a = asset( _pool->balance_a, _pool->asset_a );
243  _pool_pays_b = asset( _pool->balance_b, _pool->asset_b );
244  }
245  else
246  {
247  fc::uint128_t share128( op.share_amount.amount.value );
248  fc::uint128_t a128 = share128 * _pool->balance_a.value / _share_asset_dyn_data->current_supply.value;
249  FC_ASSERT( a128 < fc::uint128_t( _pool->balance_a.value ), "Internal error" );
250  fc::uint128_t fee_a = a128 * _pool->withdrawal_fee_percent / GRAPHENE_100_PERCENT;
251  FC_ASSERT( fee_a <= a128, "Withdrawal fee percent of the pool is too high" );
252  a128 -= fee_a;
253  fc::uint128_t b128 = share128 * _pool->balance_b.value / _share_asset_dyn_data->current_supply.value;
254  FC_ASSERT( b128 < fc::uint128_t( _pool->balance_b.value ), "Internal error" );
255  fc::uint128_t fee_b = b128 * _pool->withdrawal_fee_percent / GRAPHENE_100_PERCENT;
256  FC_ASSERT( fee_b <= b128, "Withdrawal fee percent of the pool is too high" );
257  b128 -= fee_b;
258  FC_ASSERT( a128 > 0 || b128 > 0, "Aborting due to zero outcome" );
259  _pool_pays_a = asset( static_cast<int64_t>( a128 ), _pool->asset_a );
260  _pool_pays_b = asset( static_cast<int64_t>( b128 ), _pool->asset_b );
261  }
262 
263  return void_result();
264 } FC_CAPTURE_AND_RETHROW( (op) ) }
265 
268 { try {
269  database& d = db();
271 
272  d.adjust_balance( op.account, -op.share_amount );
273 
274  if( _pool_pays_a.amount > 0 )
275  d.adjust_balance( op.account, _pool_pays_a );
276  if( _pool_pays_b.amount > 0 )
277  d.adjust_balance( op.account, _pool_pays_b );
278 
279  d.modify( *_share_asset_dyn_data, [&op]( asset_dynamic_data_object& data ){
281  });
282 
283  d.modify( *_pool, [this]( liquidity_pool_object& lpo ){
284  lpo.balance_a -= _pool_pays_a.amount;
285  lpo.balance_b -= _pool_pays_b.amount;
286  lpo.update_virtual_value();
287  });
288 
289  FC_ASSERT( (_pool->balance_a == 0) == (_pool->balance_b == 0), "Internal error" );
290  FC_ASSERT( (_pool->balance_a == 0) == (_share_asset_dyn_data->current_supply == 0), "Internal error" );
291 
292  result.paid.emplace_back( op.share_amount );
293  result.received.emplace_back( _pool_pays_a );
294  result.received.emplace_back( _pool_pays_b );
295 
296  return result;
297 } FC_CAPTURE_AND_RETHROW( (op) ) }
298 
300 { try {
301  const database& d = db();
302 
303  _pool = &op.pool(d);
304 
305  FC_ASSERT( _pool->balance_a > 0 && _pool->balance_b > 0, "The pool has not been initialized" );
306 
307  FC_ASSERT( ( op.amount_to_sell.asset_id == _pool->asset_a && op.min_to_receive.asset_id == _pool->asset_b )
308  || ( op.amount_to_sell.asset_id == _pool->asset_b && op.min_to_receive.asset_id == _pool->asset_a ),
309  "Asset type mismatch" );
310 
311  const asset_object& asset_obj_a = _pool->asset_a(d);
312  FC_ASSERT( is_authorized_asset( d, *fee_paying_account, asset_obj_a ),
313  "The account is unauthorized by asset A" );
314 
315  const asset_object& asset_obj_b = _pool->asset_b(d);
316  FC_ASSERT( is_authorized_asset( d, *fee_paying_account, asset_obj_b ),
317  "The account is unauthorized by asset B" );
318 
319  _pool_receives_asset = ( op.amount_to_sell.asset_id == _pool->asset_a ? &asset_obj_a : &asset_obj_b );
320 
321  _maker_market_fee = d.calculate_market_fee( *_pool_receives_asset, op.amount_to_sell, true );
322  FC_ASSERT( _maker_market_fee < op.amount_to_sell,
323  "Aborting since the maker market fee of the selling asset is too high" );
324  _pool_receives = op.amount_to_sell - _maker_market_fee;
325 
326  fc::uint128_t delta;
327  if( op.amount_to_sell.asset_id == _pool->asset_a )
328  {
329  share_type new_balance_a = _pool->balance_a + _pool_receives.amount;
330  // round up
331  fc::uint128_t new_balance_b = ( _pool->virtual_value + new_balance_a.value - 1 ) / new_balance_a.value;
332  FC_ASSERT( new_balance_b <= _pool->balance_b, "Internal error" );
333  delta = fc::uint128_t( _pool->balance_b.value ) - new_balance_b;
334  _pool_pays_asset = &asset_obj_b;
335  }
336  else
337  {
338  share_type new_balance_b = _pool->balance_b + _pool_receives.amount;
339  // round up
340  fc::uint128_t new_balance_a = ( _pool->virtual_value + new_balance_b.value - 1 ) / new_balance_b.value;
341  FC_ASSERT( new_balance_a <= _pool->balance_a, "Internal error" );
342  delta = fc::uint128_t( _pool->balance_a.value ) - new_balance_a;
343  _pool_pays_asset = &asset_obj_a;
344  }
345 
346  fc::uint128_t pool_taker_fee = delta * _pool->taker_fee_percent / GRAPHENE_100_PERCENT;
347  FC_ASSERT( pool_taker_fee <= delta, "Taker fee percent of the pool is too high" );
348 
349  _pool_pays = asset( static_cast<int64_t>( delta - pool_taker_fee ), op.min_to_receive.asset_id );
350 
351  _taker_market_fee = d.calculate_market_fee( *_pool_pays_asset, _pool_pays, false );
352  FC_ASSERT( _taker_market_fee <= _pool_pays, "Market fee should not be greater than the amount to receive" );
353  _account_receives = _pool_pays - _taker_market_fee;
354 
355  FC_ASSERT( _account_receives.amount >= op.min_to_receive.amount, "Unable to exchange at expected price" );
356 
357  return void_result();
358 } FC_CAPTURE_AND_RETHROW( (op) ) }
359 
362 { try {
363  database& d = db();
365 
367  d.adjust_balance( op.account, _account_receives );
368 
369  // TODO whose registrar and referrer should receive the shared maker market fee?
370  d.pay_market_fees( &_pool->share_asset(d).issuer(d), *_pool_receives_asset, op.amount_to_sell, true,
371  _maker_market_fee );
372  d.pay_market_fees( fee_paying_account, *_pool_pays_asset, _pool_pays, false, _taker_market_fee );
373 
374  const auto old_virtual_value = _pool->virtual_value;
375  if( op.amount_to_sell.asset_id == _pool->asset_a )
376  {
377  d.modify( *_pool, [&op,this]( liquidity_pool_object& lpo ){
378  lpo.balance_a += _pool_receives.amount;
379  lpo.balance_b -= _pool_pays.amount;
380  lpo.update_virtual_value();
381  });
382  }
383  else
384  {
385  d.modify( *_pool, [&op,this]( liquidity_pool_object& lpo ){
386  lpo.balance_b += _pool_receives.amount;
387  lpo.balance_a -= _pool_pays.amount;
388  lpo.update_virtual_value();
389  });
390  }
391 
392  FC_ASSERT( _pool->balance_a > 0 && _pool->balance_b > 0, "Internal error" );
393  FC_ASSERT( _pool->virtual_value >= old_virtual_value, "Internal error" );
394 
395  result.paid.emplace_back( op.amount_to_sell );
396  result.received.emplace_back( _account_receives );
397  result.fees.emplace_back( _maker_market_fee );
398  result.fees.emplace_back( _taker_market_fee );
399 
400  return result;
401 } FC_CAPTURE_AND_RETHROW( (op) ) }
402 
403 } } // graphene::chain
flat_set< object_id_type > new_objects
Definition: base.hpp:90
asset share_amount
The amount of the share asset to use.
void modify(const T &obj, const Lambda &m)
liquidity_pool_id_type pool
ID of the liquidity pool.
generic_exchange_operation_result do_apply(const liquidity_pool_exchange_operation &op)
liquidity_pool_id_type pool
ID of the liquidity pool.
void adjust_balance(account_id_type account, asset delta)
Adjust a particular account&#39;s balance in a given asset by a delta.
Definition: db_balance.cpp:54
bool is_authorized_asset(const database &d, const account_object &acct, const asset_object &asset_obj)
void_result do_evaluate(const liquidity_pool_withdraw_operation &op)
void_result do_evaluate(const liquidity_pool_exchange_operation &op)
account_id_type account
The account who withdraws from the liquidity pool.
tracks the blockchain state in an extensible manner
Definition: database.hpp:70
Definition: api.cpp:56
void_result do_evaluate(const liquidity_pool_delete_operation &op)
asset min_to_receive
The minimum amount of the other asset type to receive.
uint16_t withdrawal_fee_percent
Withdrawal fee percent.
liquidity_pool_id_type pool
ID of the liquidity pool.
const account_object * fee_paying_account
Definition: evaluator.hpp:116
asset calculate_market_fee(const asset_object &trade_asset, const asset &trade_amount, const bool &is_maker) const
Calculate the market fee that is to be taken.
Definition: db_market.cpp:1342
account_id_type issuer
ID of the account which issued this asset.
optional< liquidity_pool_id_type > for_liquidity_pool
The ID of the liquidity pool if the asset is the share asset of a liquidity pool. ...
generic_exchange_operation_result do_apply(const liquidity_pool_withdraw_operation &op)
asset pay_market_fees(const account_object *seller, const asset_object &recv_asset, const asset &receives, const bool &is_maker, const optional< asset > &calculated_market_fees={})
Definition: db_market.cpp:1377
asset_id_type asset_a
Type of the first asset in the pool.
account_id_type account
The account who exchanges with the liquidity pool.
object_id_type id
Definition: object.hpp:69
#define GRAPHENE_100_PERCENT
Definition: config.hpp:102
time_point_sec head_block_time() const
Definition: db_getter.cpp:64
liquidity_pool_id_type pool
ID of the liquidity pool.
asset_id_type share_asset
Type of the share asset aka the LP token.
void_result do_evaluate(const liquidity_pool_create_operation &op)
#define FC_CAPTURE_AND_RETHROW(...)
Definition: exception.hpp:478
#define FC_ASSERT(TEST,...)
Checks a condition and throws an assert_exception if the test is FALSE.
Definition: exception.hpp:345
share_type balance_a
The balance of the first asset in the pool.
account_id_type account
The account who creates the liquidity pool.
asset amount_to_sell
The amount of one asset type to sell.
tracks the asset information that changes frequentlyBecause the asset_object is very large it doesn&#39;t...
account_id_type account
The account who deposits to the liquidity pool.
bool is_liquidity_pool_share_asset() const
void reset()
Definition: optional.hpp:224
flat_set< object_id_type > removed_objects
Definition: base.hpp:92
tracks the parameters of an assetAll assets have a globally unique symbol name that controls how they...
asset_id_type asset_id
Definition: asset.hpp:39
flat_set< object_id_type > updated_objects
Definition: base.hpp:91
share_type balance_b
The balance of the second asset in the pool.
void remove(const object &obj)
share_type current_supply
The number of shares currently in existence.
asset amount_b
The amount of the second asset to deposit.
account_id_type account
The account who owns the liquidity pool.
asset amount_a
The amount of the first asset to deposit.
const T & create(F &&constructor)
generic_exchange_operation_result do_apply(const liquidity_pool_deposit_operation &op)
const asset_dynamic_data_object & dynamic_data(const DB &db) const
void_result do_evaluate(const liquidity_pool_deposit_operation &op)
generic_operation_result do_apply(const liquidity_pool_delete_operation &op)
T value
Definition: safe.hpp:22
generic_operation_result do_apply(const liquidity_pool_create_operation &op)
asset_id_type asset_b
Type of the second asset in the pool.