BitShares-Core  6.1.0
BitShares blockchain implementation and command-line interface software
credit_offer_evaluator.cpp
Go to the documentation of this file.
1 /*
2  * Copyright (c) 2021 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_CORE_2362_PASSED(block_time), "Not allowed until the core-2362 hardfork" );
44 
45  if( op.enabled )
46  {
47  FC_ASSERT( op.auto_disable_time > block_time, "Auto-disable time should be in the future" );
49  "Auto-disable time should not be later than ${d} days in the future",
51  }
52 
53  // Make sure all the collateral asset types exist
54  for( const auto& collateral : op.acceptable_collateral )
55  {
56  collateral.first(d);
57  }
58 
59  // Make sure all the accounts exist
60  for( const auto& borrower : op.acceptable_borrowers )
61  {
62  borrower.first(d);
63  }
64 
66  "The account is unauthorized by the asset" );
67 
68  return void_result();
69 } FC_CAPTURE_AND_RETHROW( (op) ) }
70 
72 { try {
73  database& d = db();
74 
76 
77  const auto& new_credit_offer_object = d.create<credit_offer_object>([&op](credit_offer_object& obj){
78  obj.owner_account = op.owner_account;
79  obj.asset_type = op.asset_type;
80  obj.total_balance = op.balance;
81  obj.current_balance = op.balance;
82  obj.fee_rate = op.fee_rate;
83  obj.max_duration_seconds = op.max_duration_seconds;
84  obj.min_deal_amount = op.min_deal_amount;
85  obj.enabled = op.enabled;
86  obj.auto_disable_time = op.auto_disable_time;
87  obj.acceptable_collateral = op.acceptable_collateral;
88  obj.acceptable_borrowers = op.acceptable_borrowers;
89  });
90  return new_credit_offer_object.id;
91 } FC_CAPTURE_AND_RETHROW( (op) ) }
92 
94 { try {
95  const database& d = db();
96 
97  _offer = &op.offer_id(d);
98 
99  FC_ASSERT( _offer->owner_account == op.owner_account, "The account is not the owner of the credit offer" );
100 
101  FC_ASSERT( _offer->total_balance == _offer->current_balance,
102  "Can only delete a credit offer when the unpaid amount is zero" );
103 
104  // Note: no asset authorization check here, allow funds to be moved to account balance
105 
106  return void_result();
107 } FC_CAPTURE_AND_RETHROW( (op) ) }
108 
110 { try {
111  database& d = db();
112 
113  asset released( _offer->current_balance, _offer->asset_type );
114 
115  if( _offer->current_balance != 0 )
116  {
117  d.adjust_balance( op.owner_account, released );
118  }
119 
120  d.remove( *_offer );
121 
122  return released;
123 } FC_CAPTURE_AND_RETHROW( (op) ) }
124 
126 { try {
127  const database& d = db();
128  const auto block_time = d.head_block_time();
129 
130  _offer = &op.offer_id(d);
131 
132  FC_ASSERT( _offer->owner_account == op.owner_account, "The account is not the owner of the credit offer" );
133 
134  if( op.delta_amount.valid() )
135  {
136  FC_ASSERT( _offer->asset_type == op.delta_amount->asset_id, "Asset type mismatch" );
137 
138  if( op.delta_amount->amount > 0 )
139  {
140  // Check asset authorization only when moving funds from account balance to somewhere else
141  FC_ASSERT( is_authorized_asset( d, *fee_paying_account, _offer->asset_type(d) ),
142  "The account is unauthorized by the asset" );
143  }
144  else
145  {
146  FC_ASSERT( _offer->total_balance > -op.delta_amount->amount,
147  "Should leave some funds in the credit offer when updating" );
148  FC_ASSERT( _offer->current_balance >= -op.delta_amount->amount, "Insufficient balance in the credit offer" );
149  }
150  }
151 
152  bool enabled = op.enabled.valid() ? *op.enabled : _offer->enabled;
153  if( enabled )
154  {
155  auto auto_disable_time = op.auto_disable_time.valid() ? *op.auto_disable_time : _offer->auto_disable_time;
156  FC_ASSERT( auto_disable_time > block_time, "Auto-disable time should be in the future" );
157  FC_ASSERT( auto_disable_time - block_time <= fc::days(GRAPHENE_MAX_CREDIT_OFFER_DAYS),
158  "Auto-disable time should not be later than ${d} days in the future",
160  }
161 
162  // Make sure all the collateral asset types exist
163  if( op.acceptable_collateral.valid() )
164  {
165  for( const auto& collateral : *op.acceptable_collateral )
166  {
167  collateral.first(d);
168  FC_ASSERT( _offer->asset_type == collateral.second.base.asset_id,
169  "Asset type mismatch in a price of acceptable collateral" );
170  }
171  }
172 
173  // Make sure all the accounts exist
174  if( op.acceptable_borrowers.valid() )
175  {
176  for( const auto& borrower : *op.acceptable_borrowers )
177  {
178  borrower.first(d);
179  }
180  }
181 
182  return void_result();
183 } FC_CAPTURE_AND_RETHROW( (op) ) }
184 
186 { try {
187  database& d = db();
188 
189  if( op.delta_amount.valid() )
190  d.adjust_balance( op.owner_account, -(*op.delta_amount) );
191 
192  d.modify( *_offer, [&op]( credit_offer_object& coo ){
193  if( op.delta_amount.valid() ) {
194  coo.total_balance += op.delta_amount->amount;
195  coo.current_balance += op.delta_amount->amount;
196  }
197  if( op.fee_rate.valid() )
198  coo.fee_rate = *op.fee_rate;
199  if( op.max_duration_seconds.valid() )
201  if( op.min_deal_amount.valid() )
203  if( op.enabled.valid() )
204  coo.enabled = *op.enabled;
205  if( op.auto_disable_time.valid() )
207  if( op.acceptable_collateral.valid() )
209  if( op.acceptable_borrowers.valid() )
211  });
212 
213  // Defensive checks
214  FC_ASSERT( _offer->total_balance > 0, "Total balance in the credit offer should be positive" );
215  FC_ASSERT( _offer->current_balance >= 0, "Current balance in the credit offer should not be negative" );
216  FC_ASSERT( _offer->total_balance >= _offer->current_balance,
217  "Total balance in the credit offer should not be less than current balance" );
218  if( _offer->enabled )
219  {
220  FC_ASSERT( _offer->auto_disable_time > d.head_block_time(),
221  "Auto-disable time should be in the future if the credit offer is enabled" );
222  FC_ASSERT( _offer->auto_disable_time - d.head_block_time() <= fc::days(GRAPHENE_MAX_CREDIT_OFFER_DAYS),
223  "Auto-disable time should not be too late in the future" );
224  }
225 
226  return void_result();
227 } FC_CAPTURE_AND_RETHROW( (op) ) }
228 
230 { try {
231  const database& d = db();
232 
233  _offer = &op.offer_id(d);
234 
235  FC_ASSERT( _offer->enabled, "The credit offer is not enabled" );
236 
237  FC_ASSERT( _offer->asset_type == op.borrow_amount.asset_id, "Asset type mismatch" );
238 
239  FC_ASSERT( _offer->current_balance >= op.borrow_amount.amount,
240  "Insufficient balance in the credit offer thus unable to borrow" );
241 
242  FC_ASSERT( _offer->min_deal_amount <= op.borrow_amount.amount,
243  "Borrowing amount should not be less than minimum deal amount" );
244 
245  FC_ASSERT( _offer->fee_rate <= op.max_fee_rate,
246  "The maximum accceptable fee rate is lower than offered" );
247 
248  FC_ASSERT( _offer->max_duration_seconds >= op.min_duration_seconds,
249  "The minimum accceptable duration is longer than offered" );
250 
251  auto coll_itr = _offer->acceptable_collateral.find( op.collateral.asset_id );
252  FC_ASSERT( coll_itr != _offer->acceptable_collateral.end(),
253  "Collateral asset type is not acceptable by the credit offer" );
254 
255  const asset_object& debt_asset_obj = _offer->asset_type(d);
256  const asset_object& collateral_asset_obj = op.collateral.asset_id(d);
257 
258  FC_ASSERT( is_authorized_asset( d, *fee_paying_account, debt_asset_obj ),
259  "The borrower is unauthorized by the borrowing asset" );
260  FC_ASSERT( is_authorized_asset( d, *fee_paying_account, collateral_asset_obj ),
261  "The borrower is unauthorized by the collateral asset" );
262 
263  const account_object& offer_owner = _offer->owner_account(d);
264 
265  FC_ASSERT( is_authorized_asset( d, offer_owner, debt_asset_obj ),
266  "The owner of the credit offer is unauthorized by the borrowing asset" );
267  FC_ASSERT( is_authorized_asset( d, offer_owner, collateral_asset_obj ),
268  "The owner of the credit offer is unauthorized by the collateral asset" );
269 
270  auto required_collateral = op.borrow_amount.multiply_and_round_up( coll_itr->second );
271  FC_ASSERT( required_collateral.amount <= op.collateral.amount,
272  "Insufficient collateral provided, requires ${r}, provided ${p}",
273  ("r", required_collateral.amount) ("p", op.collateral.amount) );
274 
275  optional<share_type> max_allowed;
276  if( !_offer->acceptable_borrowers.empty() )
277  {
278  auto itr = _offer->acceptable_borrowers.find( op.borrower );
279  FC_ASSERT( itr != _offer->acceptable_borrowers.end(), "Account is not in acceptable borrowers" );
280  max_allowed = itr->second;
281  }
282 
283  share_type already_borrowed = 0;
284  const auto& deal_summary_idx = d.get_index_type<credit_deal_summary_index>().indices().get<by_offer_borrower>();
285  auto summ_itr = deal_summary_idx.find( boost::make_tuple( op.offer_id, op.borrower ) );
286  if( summ_itr != deal_summary_idx.end() )
287  {
288  _deal_summary = &(*summ_itr);
289  already_borrowed = _deal_summary->total_debt_amount;
290  }
291 
292  if( max_allowed.valid() )
293  {
294  FC_ASSERT( already_borrowed + op.borrow_amount.amount <= *max_allowed,
295  "Unable to borrow ${b}, already borrowed ${a}, maximum allowed ${m}",
296  ("b", op.borrow_amount.amount) ("a", already_borrowed) ("m", max_allowed) );
297  }
298 
299  return void_result();
300 } FC_CAPTURE_AND_RETHROW( (op) ) }
301 
303 { try {
304  database& d = db();
305 
306  d.adjust_balance( op.borrower, -op.collateral );
308 
309  d.modify( *_offer, [&op]( credit_offer_object& coo ){
311  });
312 
313  const auto block_time = d.head_block_time();
314  auto repay_time = ( fc::time_point_sec::maximum() - block_time ) >= fc::seconds(_offer->max_duration_seconds)
315  ? ( block_time + _offer->max_duration_seconds )
317 
318  const auto& new_deal = d.create<credit_deal_object>([&op,this,&repay_time](credit_deal_object& obj){
319  obj.borrower = op.borrower;
320  obj.offer_id = op.offer_id;
321  obj.offer_owner = _offer->owner_account;
322  obj.debt_asset = _offer->asset_type;
323  obj.debt_amount = op.borrow_amount.amount;
324  obj.collateral_asset = op.collateral.asset_id;
325  obj.collateral_amount = op.collateral.amount;
326  obj.fee_rate = _offer->fee_rate;
327  obj.latest_repay_time = repay_time;
328  });
329 
330  if( _deal_summary != nullptr )
331  {
332  d.modify( *_deal_summary, [&op]( credit_deal_summary_object& obj ){
333  obj.total_debt_amount += op.borrow_amount.amount;
334  });
335  }
336  else
337  {
339  obj.borrower = op.borrower;
340  obj.offer_id = op.offer_id;
341  obj.offer_owner = _offer->owner_account;
342  obj.debt_asset = _offer->asset_type;
343  obj.total_debt_amount = op.borrow_amount.amount;
344  });
345  }
346 
347  // Defensive check
348  FC_ASSERT( _offer->total_balance > 0, "Total balance in the credit offer should be positive" );
349  FC_ASSERT( _offer->current_balance >= 0, "Current balance in the credit offer should not be negative" );
350  FC_ASSERT( _offer->total_balance >= _offer->current_balance,
351  "Total balance in the credit offer should not be less than current balance" );
352  FC_ASSERT( new_deal.latest_repay_time > block_time,
353  "Latest repayment time should be in the future" );
354  FC_ASSERT( new_deal.latest_repay_time - block_time <= fc::days(GRAPHENE_MAX_CREDIT_DEAL_DAYS),
355  "Latest repayment time should not be too late in the future" );
356 
358  // Note: only return the deal here, deal summary is impl so we do not return it
359  result.value.new_objects = flat_set<object_id_type>({ new_deal.id });
360  result.value.impacted_accounts = flat_set<account_id_type>({ _offer->owner_account });
361 
362  return result;
363 } FC_CAPTURE_AND_RETHROW( (op) ) }
364 
366 { try {
367  const database& d = db();
368 
369  _deal = &op.deal_id(d);
370 
371  FC_ASSERT( _deal->borrower == op.account, "A credit deal can only be repaid by the borrower" );
372 
373  FC_ASSERT( _deal->debt_asset == op.repay_amount.asset_id, "Asset type mismatch" );
374 
375  FC_ASSERT( _deal->debt_amount >= op.repay_amount.amount,
376  "Repay amount should not be greater than unpaid amount" );
377 
378  // Note: the result can be larger than 64 bit, but since we don't store it, it is allowed
379  auto required_fee = ( ( ( fc::uint128_t( op.repay_amount.amount.value ) * _deal->fee_rate )
380  + GRAPHENE_FEE_RATE_DENOM ) - 1 ) / GRAPHENE_FEE_RATE_DENOM; // Round up
381 
382  FC_ASSERT( fc::uint128_t(op.credit_fee.amount.value) >= required_fee,
383  "Insuffient credit fee, requires ${r}, offered ${p}",
384  ("r", required_fee) ("p", op.credit_fee.amount) );
385 
386  const asset_object& debt_asset_obj = _deal->debt_asset(d);
387  // Note: allow collateral to be moved to account balance regardless of collateral asset authorization
388 
389  FC_ASSERT( is_authorized_asset( d, *fee_paying_account, debt_asset_obj ),
390  "The account is unauthorized by the repaying asset" );
391  FC_ASSERT( is_authorized_asset( d, _deal->offer_owner(d), debt_asset_obj ),
392  "The owner of the credit offer is unauthorized by the repaying asset" );
393 
394  return void_result();
395 } FC_CAPTURE_AND_RETHROW( (op) ) }
396 
398 { try {
399  database& d = db();
400 
401  share_type total_amount = op.repay_amount.amount + op.credit_fee.amount;
402 
403  d.adjust_balance( op.account, asset( -total_amount, op.repay_amount.asset_id ) );
404 
405  // Update offer
406  const credit_offer_object& offer = _deal->offer_id(d);
407  d.modify( offer, [&op,&total_amount]( credit_offer_object& obj ){
408  obj.total_balance += op.credit_fee.amount;
409  obj.current_balance += total_amount;
410  });
411  // Defensive check
412  FC_ASSERT( offer.total_balance >= offer.current_balance,
413  "Total balance in the credit offer should not be less than current balance" );
414 
416  result.value.impacted_accounts = flat_set<account_id_type>({ offer.owner_account });
417  result.value.updated_objects = flat_set<object_id_type>({ offer.id });
418 
419  // Process deal summary
420  const auto& deal_summary_idx = d.get_index_type<credit_deal_summary_index>().indices().get<by_offer_borrower>();
421  auto summ_itr = deal_summary_idx.find( boost::make_tuple( _deal->offer_id, op.account ) );
422  FC_ASSERT( summ_itr != deal_summary_idx.end(), "Internal error" );
423 
424  const credit_deal_summary_object& summ_obj = *summ_itr;
425  if( summ_obj.total_debt_amount == op.repay_amount.amount )
426  {
427  d.remove( summ_obj );
428  }
429  else
430  {
431  d.modify( summ_obj, [&op]( credit_deal_summary_object& obj ){
433  });
434  }
435 
436  // Process deal
437  asset collateral_released( _deal->collateral_amount, _deal->collateral_asset );
438  if( _deal->debt_amount == op.repay_amount.amount ) // to fully repay
439  {
440  result.value.removed_objects = flat_set<object_id_type>({ _deal->id });
441  d.remove( *_deal );
442  }
443  else // to partially repay
444  {
445  auto amount_to_release = ( fc::uint128_t( op.repay_amount.amount.value ) * _deal->collateral_amount.value )
446  / _deal->debt_amount.value; // Round down
447  FC_ASSERT( amount_to_release < fc::uint128_t( _deal->collateral_amount.value ), "Internal error" );
448  collateral_released.amount = static_cast<int64_t>( amount_to_release );
449 
450  d.modify( *_deal, [&op,&collateral_released]( credit_deal_object& obj ){
451  obj.debt_amount -= op.repay_amount.amount;
452  obj.collateral_amount -= collateral_released.amount;
453  });
454 
455  result.value.updated_objects->insert( _deal->id );
456  }
457 
458  d.adjust_balance( op.account, collateral_released );
459  result.value.received = vector<asset>({ collateral_released });
460 
461  return result;
462 } FC_CAPTURE_AND_RETHROW( (op) ) }
463 
464 } } // graphene::chain
share_type total_debt_amount
How much funds borrowed.
A credit offer is a fund that can be used by other accounts who provide certain collateral.
account_id_type account
The account who repays to the credit offer.
optional< asset > delta_amount
Delta amount, optional.
void modify(const T &obj, const Lambda &m)
share_type collateral_amount
How much funds in collateral.
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
share_type balance
Usable amount in the credit offer.
bool is_authorized_asset(const database &d, const account_object &acct, const asset_object &asset_obj)
time_point_sec head_block_time() const
Definition: db_getter.cpp:67
A credit deal describes the details of a borrower&#39;s borrowing of funds from a credit offer...
This class represents an account on the object graphAccounts are the primary unit of authority on the...
tracks the blockchain state in an extensible manner
Definition: database.hpp:70
const IndexType & get_index_type() const
Definition: api.cpp:48
A credit deal summary describes the summary of a borrower&#39;s borrowing of funds from a credit offer...
uint32_t fee_rate
Fee rate, the demominator is GRAPHENE_FEE_RATE_DENOM.
const account_object * fee_paying_account
Definition: evaluator.hpp:116
constexpr int64_t GRAPHENE_MAX_CREDIT_OFFER_DAYS
How long a credit offer will be kept active, in days.
Definition: config.hpp:124
share_type current_balance
Usable amount in the fund.
flat_map< asset_id_type, price > acceptable_collateral
Types and rates of acceptable collateral.
credit_offer_id_type offer_id
ID of the credit offer.
bool enabled
Whether this offer is available.
Accept a creadit offer and create a credit deal.
credit_offer_id_type offer_id
ID of the credit offer.
bool enabled
Whether this offer is available.
credit_deal_id_type deal_id
ID of the credit deal.
flat_map< account_id_type, share_type > acceptable_borrowers
Allowed borrowers and their maximum amounts to borrow. No limitation if empty.
optional< uint32_t > max_duration_seconds
New repayment time limit, optional.
asset multiply_and_round_up(const price &p) const
Multiply and round up.
Definition: asset.cpp:77
provides stack-based nullable value similar to boost::optional
Definition: optional.hpp:20
optional< time_point_sec > auto_disable_time
New time to disable automatically, optional.
void_result do_evaluate(const credit_offer_create_operation &op) const
optional< uint32_t > fee_rate
New fee rate, optional.
uint32_t fee_rate
Fee rate, the demominator is GRAPHENE_FEE_RATE_DENOM.
share_type total_balance
Total size of the fund.
constexpr int64_t GRAPHENE_MAX_CREDIT_DEAL_DAYS
How long a credit deal will be kept, in days.
Definition: config.hpp:128
microseconds days(int64_t d)
Definition: time.hpp:38
credit_offer_id_type offer_id
ID of the credit offer.
static time_point_sec maximum()
Definition: time.hpp:86
asset do_apply(const credit_offer_delete_operation &op) const
object_id_type id
Definition: object.hpp:69
microseconds seconds(int64_t s)
Definition: time.hpp:34
bool valid() const
Definition: optional.hpp:186
uint32_t max_duration_seconds
The time limit that borrowed funds should be repaid.
const object & get(object_id_type id) const
Definition: index.hpp:110
void_result do_evaluate(const credit_offer_update_operation &op)
account_id_type owner_account
The account who owns the credit offer.
optional< flat_map< asset_id_type, price > > acceptable_collateral
New types and rates of acceptable collateral, optional.
time_point_sec auto_disable_time
The time when this offer will be disabled automatically.
optional< bool > enabled
Whether this offer is available, optional.
void_result do_evaluate(const credit_deal_repay_operation &op)
#define FC_CAPTURE_AND_RETHROW(...)
Definition: exception.hpp:479
#define FC_ASSERT(TEST,...)
Checks a condition and throws an assert_exception if the test is FALSE.
Definition: exception.hpp:345
void_result do_evaluate(const credit_offer_accept_operation &op)
optional< share_type > min_deal_amount
Minimum amount to borrow for each new deal, optional.
flat_map< asset_id_type, price > acceptable_collateral
Types and rates of acceptable collateral.
void_result do_evaluate(const credit_offer_delete_operation &op)
asset_id_type asset_type
Asset type in the credit offer.
share_type debt_amount
How much funds borrowed.
flat_map< account_id_type, share_type > acceptable_borrowers
Allowed borrowers and their maximum amounts to borrow. No limitation if empty.
share_type min_deal_amount
Minimum amount to borrow for each new deal.
constexpr uint32_t GRAPHENE_FEE_RATE_DENOM
Denominator for SameT Fund fee calculation.
Definition: config.hpp:121
account_id_type owner_account
Owner of the fund.
optional< flat_map< account_id_type, share_type > > acceptable_borrowers
New allowed borrowers and their maximum amounts to borrow, optional.
void_result do_apply(const credit_offer_update_operation &op) const
tracks the parameters of an assetAll assets have a globally unique symbol name that controls how they...
account_id_type borrower
The account who accepts the offer.
extendable_operation_result do_apply(const credit_deal_repay_operation &op) const
asset_id_type asset_id
Definition: asset.hpp:37
credit_offer_id_type offer_id
ID of the credit offer.
asset_id_type collateral_asset
Asset type of the collateral.
share_type min_deal_amount
Minimum amount to borrow for each new deal.
void remove(const object &obj)
account_id_type owner_account
Owner of the credit offer.
uint32_t fee_rate
Fee rate, the demominator is GRAPHENE_FEE_RATE_DENOM.
object_id_type do_apply(const credit_offer_create_operation &op) const
asset credit_fee
The credit fee relative to the amount to repay.
account_id_type owner_account
Owner of the credit offer.
uint32_t max_fee_rate
The maximum acceptable fee rate.
uint32_t max_duration_seconds
The time limit that borrowed funds should be repaid.
credit_offer_id_type offer_id
ID of the credit offer.
const T & create(F &&constructor)
extendable_operation_result do_apply(const credit_offer_accept_operation &op) const
uint32_t min_duration_seconds
The minimum acceptable duration.
T value
Definition: safe.hpp:22
time_point_sec auto_disable_time
The time when this offer will be disabled automatically.
Create a new credit offerA credit offer is a fund that can be used by other accounts who provide cert...