mirror of
https://github.com/XRPLF/rippled.git
synced 2025-11-21 03:26:01 +00:00
Autobridging:
Complete implementation of bridged offers crossings. While processing an offer A:B we consider both the A:B order book and the combined A:XRP and XRP:B books and pick the better offers. The net result is better liquidity and potentially better rates. * Rearchitect core::Taker to perform direct and bridged crossings. * Compute bridged qualities. * Implement a new Bridged OfferCreate transactor. * Factor out common code from the Bridged and Direct OfferCreate transactors. * Perform flow calculations without losing accuracy. * Rename all transactors. * Cleanups.
This commit is contained in:
committed by
Vinnie Falco
parent
72bc4ebf37
commit
27e8d44a56
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -20,6 +20,7 @@
|
|||||||
#ifndef RIPPLE_TYPES_RIPPLEASSETS_H_INCLUDED
|
#ifndef RIPPLE_TYPES_RIPPLEASSETS_H_INCLUDED
|
||||||
#define RIPPLE_TYPES_RIPPLEASSETS_H_INCLUDED
|
#define RIPPLE_TYPES_RIPPLEASSETS_H_INCLUDED
|
||||||
|
|
||||||
|
#include <cassert>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <type_traits>
|
#include <type_traits>
|
||||||
|
|
||||||
@@ -66,6 +67,9 @@ public:
|
|||||||
: currency (currency_)
|
: currency (currency_)
|
||||||
, issuer (issuer_)
|
, issuer (issuer_)
|
||||||
{
|
{
|
||||||
|
// Either XRP and (currency == zero && issuer == zero) or some custom
|
||||||
|
// currency and (currency != 0 && issuer != 0)
|
||||||
|
assert (currency.isZero () == issuer.isZero ());
|
||||||
}
|
}
|
||||||
|
|
||||||
template <bool OtherByValue>
|
template <bool OtherByValue>
|
||||||
@@ -87,6 +91,7 @@ public:
|
|||||||
|
|
||||||
bool is_xrp () const
|
bool is_xrp () const
|
||||||
{
|
{
|
||||||
|
assert (currency.isZero () == issuer.isZero ());
|
||||||
if (currency.isZero ())
|
if (currency.isZero ())
|
||||||
return true;
|
return true;
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
/*
|
/*
|
||||||
This file is part of rippled: https://github.com/ripple/rippled
|
This file is part of rippled: https://github.com/ripple/rippled
|
||||||
Copyright (c) 2012, 2013 Ripple Labs Inc.
|
Copyright (c) 2014 Ripple Labs Inc.
|
||||||
|
|
||||||
Permission to use, copy, modify, and/or distribute this software for any
|
Permission to use, copy, modify, and/or distribute this software for any
|
||||||
purpose with or without fee is hereby granted, provided that the above
|
purpose with or without fee is hereby granted, provided that the above
|
||||||
@@ -191,18 +191,14 @@ public:
|
|||||||
|
|
||||||
if (sig == 0)
|
if (sig == 0)
|
||||||
return os << "0";
|
return os << "0";
|
||||||
else if (sig < 0)
|
|
||||||
os << "-";
|
|
||||||
|
|
||||||
|
if (sig < 0)
|
||||||
|
os << "-";
|
||||||
if (m_integral)
|
if (m_integral)
|
||||||
return os << m_mantissa;
|
return os << m_mantissa;
|
||||||
|
|
||||||
if (m_exponent != 0 && (m_exponent < -25 || m_exponent > -5))
|
if (m_exponent != 0 && (m_exponent < -25 || m_exponent > -5))
|
||||||
return os << m_mantissa << "e" << m_exponent;
|
return os << m_mantissa << "e" << m_exponent;
|
||||||
|
|
||||||
//if (m_exponent > 0)
|
|
||||||
// return os <<
|
|
||||||
|
|
||||||
return os;
|
return os;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
/*
|
/*
|
||||||
This file is part of rippled: https://github.com/ripple/rippled
|
This file is part of rippled: https://github.com/ripple/rippled
|
||||||
Copyright (c) 2012, 2013 Ripple Labs Inc.
|
Copyright (c) 2014 Ripple Labs Inc.
|
||||||
|
|
||||||
Permission to use, copy, modify, and/or distribute this software for any
|
Permission to use, copy, modify, and/or distribute this software for any
|
||||||
purpose with or without fee is hereby granted, provided that the above
|
purpose with or without fee is hereby granted, provided that the above
|
||||||
@@ -22,6 +22,8 @@
|
|||||||
|
|
||||||
#include "Amount.h"
|
#include "Amount.h"
|
||||||
|
|
||||||
|
#include "../../beast/beast/utility/noexcept.h"
|
||||||
|
|
||||||
namespace ripple {
|
namespace ripple {
|
||||||
namespace core {
|
namespace core {
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
/*
|
/*
|
||||||
This file is part of rippled: https://github.com/ripple/rippled
|
This file is part of rippled: https://github.com/ripple/rippled
|
||||||
Copyright (c) 2012, 2013 Ripple Labs Inc.
|
Copyright (c) 2014 Ripple Labs Inc.
|
||||||
|
|
||||||
Permission to use, copy, modify, and/or distribute this software for any
|
Permission to use, copy, modify, and/or distribute this software for any
|
||||||
purpose with or without fee is hereby granted, provided that the above
|
purpose with or without fee is hereby granted, provided that the above
|
||||||
@@ -25,8 +25,7 @@
|
|||||||
|
|
||||||
#include "../../beast/beast/utility/noexcept.h"
|
#include "../../beast/beast/utility/noexcept.h"
|
||||||
|
|
||||||
#include <ostream>
|
#include <functional>
|
||||||
#include <utility>
|
|
||||||
|
|
||||||
namespace ripple {
|
namespace ripple {
|
||||||
namespace core {
|
namespace core {
|
||||||
@@ -54,15 +53,7 @@ private:
|
|||||||
|
|
||||||
public:
|
public:
|
||||||
/** Create the iterator. */
|
/** Create the iterator. */
|
||||||
BookTip (LedgerView& view, BookRef book)
|
BookTip (LedgerView& view, BookRef book);
|
||||||
: m_view (view)
|
|
||||||
, m_valid (false)
|
|
||||||
, m_book (Ledger::getBookBase (
|
|
||||||
book.in.currency, book.in.issuer,
|
|
||||||
book.out.currency, book.out.issuer))
|
|
||||||
, m_end (Ledger::getQualityNext (m_book))
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
uint256 const&
|
uint256 const&
|
||||||
dir() const noexcept
|
dir() const noexcept
|
||||||
@@ -93,49 +84,7 @@ public:
|
|||||||
@return `true` if there is a next offer
|
@return `true` if there is a next offer
|
||||||
*/
|
*/
|
||||||
bool
|
bool
|
||||||
step ()
|
step ();
|
||||||
{
|
|
||||||
if (m_valid)
|
|
||||||
{
|
|
||||||
if (m_entry)
|
|
||||||
{
|
|
||||||
view().offerDelete (m_index);
|
|
||||||
m_entry = nullptr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for(;;)
|
|
||||||
{
|
|
||||||
// See if there's an entry at or worse than current quality.
|
|
||||||
auto const page (
|
|
||||||
view().getNextLedgerIndex (m_book, m_end));
|
|
||||||
|
|
||||||
if (page.isZero())
|
|
||||||
return false;
|
|
||||||
|
|
||||||
unsigned int di (0);
|
|
||||||
SLE::pointer dir;
|
|
||||||
if (view().dirFirst (page, dir, di, m_index))
|
|
||||||
{
|
|
||||||
m_dir = dir->getIndex();
|
|
||||||
m_entry = view().entryCache (ltOFFER, m_index);
|
|
||||||
m_valid = true;
|
|
||||||
|
|
||||||
// Next query should start before this directory
|
|
||||||
m_book = page;
|
|
||||||
|
|
||||||
// The quality immediately before the next quality
|
|
||||||
--m_book;
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
// There should never be an empty directory but just in case,
|
|
||||||
// we handle that case by advancing to the next directory.
|
|
||||||
m_book = page;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
/*
|
/*
|
||||||
This file is part of rippled: https://github.com/ripple/rippled
|
This file is part of rippled: https://github.com/ripple/rippled
|
||||||
Copyright (c) 2012, 2013 Ripple Labs Inc.
|
Copyright (c) 2014 Ripple Labs Inc.
|
||||||
|
|
||||||
Permission to use, copy, modify, and/or distribute this software for any
|
Permission to use, copy, modify, and/or distribute this software for any
|
||||||
purpose with or without fee is hereby granted, provided that the above
|
purpose with or without fee is hereby granted, provided that the above
|
||||||
@@ -27,6 +27,8 @@
|
|||||||
#include "../misc/SerializedLedger.h"
|
#include "../misc/SerializedLedger.h"
|
||||||
#include "../../ripple_data/protocol/FieldNames.h"
|
#include "../../ripple_data/protocol/FieldNames.h"
|
||||||
|
|
||||||
|
#include "../../beast/beast/utility/noexcept.h"
|
||||||
|
|
||||||
#include <ostream>
|
#include <ostream>
|
||||||
|
|
||||||
namespace ripple {
|
namespace ripple {
|
||||||
@@ -76,12 +78,23 @@ public:
|
|||||||
Some or all of the out amount may be unfunded.
|
Some or all of the out amount may be unfunded.
|
||||||
*/
|
*/
|
||||||
Amounts const
|
Amounts const
|
||||||
amount() const noexcept
|
amount() const
|
||||||
{
|
{
|
||||||
return Amounts (m_entry->getFieldAmount (sfTakerPays),
|
return Amounts (m_entry->getFieldAmount (sfTakerPays),
|
||||||
m_entry->getFieldAmount (sfTakerGets));
|
m_entry->getFieldAmount (sfTakerGets));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Returns `true` if no more funds can flow through this offer. */
|
||||||
|
bool
|
||||||
|
fully_consumed() const
|
||||||
|
{
|
||||||
|
if (m_entry->getFieldAmount (sfTakerPays) <= zero)
|
||||||
|
return true;
|
||||||
|
if (m_entry->getFieldAmount (sfTakerGets) <= zero)
|
||||||
|
return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
/** Returns the ledger entry underlying the offer. */
|
/** Returns the ledger entry underlying the offer. */
|
||||||
// AVOID USING THIS
|
// AVOID USING THIS
|
||||||
SLE::pointer
|
SLE::pointer
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
/*
|
/*
|
||||||
This file is part of rippled: https://github.com/ripple/rippled
|
This file is part of rippled: https://github.com/ripple/rippled
|
||||||
Copyright (c) 2012, 2013 Ripple Labs Inc.
|
Copyright (c) 2014 Ripple Labs Inc.
|
||||||
|
|
||||||
Permission to use, copy, modify, and/or distribute this software for any
|
Permission to use, copy, modify, and/or distribute this software for any
|
||||||
purpose with or without fee is hereby granted, provided that the above
|
purpose with or without fee is hereby granted, provided that the above
|
||||||
@@ -27,8 +27,7 @@
|
|||||||
|
|
||||||
#include "../../beast/beast/utility/noexcept.h"
|
#include "../../beast/beast/utility/noexcept.h"
|
||||||
|
|
||||||
#include <ostream>
|
#include <functional>
|
||||||
#include <utility>
|
|
||||||
|
|
||||||
namespace ripple {
|
namespace ripple {
|
||||||
namespace core {
|
namespace core {
|
||||||
@@ -49,12 +48,10 @@ namespace core {
|
|||||||
|
|
||||||
When an offer is removed, it is removed from both views. This grooms the
|
When an offer is removed, it is removed from both views. This grooms the
|
||||||
order book regardless of whether or not the transaction is successful.
|
order book regardless of whether or not the transaction is successful.
|
||||||
|
|
||||||
TODO: Remove offers belonging to the taker
|
|
||||||
*/
|
*/
|
||||||
class OfferStream
|
class OfferStream
|
||||||
{
|
{
|
||||||
protected:
|
private:
|
||||||
beast::Journal m_journal;
|
beast::Journal m_journal;
|
||||||
std::reference_wrapper <LedgerView> m_view;
|
std::reference_wrapper <LedgerView> m_view;
|
||||||
std::reference_wrapper <LedgerView> m_view_cancel;
|
std::reference_wrapper <LedgerView> m_view_cancel;
|
||||||
@@ -63,59 +60,12 @@ protected:
|
|||||||
BookTip m_tip;
|
BookTip m_tip;
|
||||||
Offer m_offer;
|
Offer m_offer;
|
||||||
|
|
||||||
// Handle the case where a directory item with no corresponding ledger entry
|
|
||||||
// is found. This shouldn't happen but if it does we clean it up.
|
|
||||||
void
|
void
|
||||||
erase (LedgerView& view)
|
erase (LedgerView& view);
|
||||||
{
|
|
||||||
// VFALCO NOTE
|
|
||||||
//
|
|
||||||
// This should be using LedgerView::dirDelete, which will
|
|
||||||
// correctly remove the directory if its the last entry.
|
|
||||||
// Unfortunately this is a protocol breaking change.
|
|
||||||
|
|
||||||
auto p (view.entryCache (ltDIR_NODE, m_tip.dir()));
|
|
||||||
|
|
||||||
if (p == nullptr)
|
|
||||||
{
|
|
||||||
if (m_journal.error) m_journal.error <<
|
|
||||||
"Missing directory " << m_tip.dir() <<
|
|
||||||
" for offer " << m_tip.index();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto v (p->getFieldV256 (sfIndexes));
|
|
||||||
auto& x (v.peekValue());
|
|
||||||
auto it (std::find (x.begin(), x.end(), m_tip.index()));
|
|
||||||
|
|
||||||
if (it == x.end())
|
|
||||||
{
|
|
||||||
if (m_journal.error) m_journal.error <<
|
|
||||||
"Missing offer " << m_tip.index() <<
|
|
||||||
" for directory " << m_tip.dir();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
x.erase (it);
|
|
||||||
p->setFieldV256 (sfIndexes, v);
|
|
||||||
view.entryModify (p);
|
|
||||||
|
|
||||||
if (m_journal.trace) m_journal.trace <<
|
|
||||||
"Missing offer " << m_tip.index() <<
|
|
||||||
" removed from directory " << m_tip.dir();
|
|
||||||
}
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
OfferStream (LedgerView& view, LedgerView& view_cancel, BookRef book,
|
OfferStream (LedgerView& view, LedgerView& view_cancel, BookRef book,
|
||||||
Clock::time_point when, beast::Journal journal)
|
Clock::time_point when, beast::Journal journal);
|
||||||
: m_journal (journal)
|
|
||||||
, m_view (view)
|
|
||||||
, m_view_cancel (view_cancel)
|
|
||||||
, m_book (book)
|
|
||||||
, m_when (when)
|
|
||||||
, m_tip (view, book)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
LedgerView&
|
LedgerView&
|
||||||
view () noexcept
|
view () noexcept
|
||||||
@@ -135,12 +85,6 @@ public:
|
|||||||
return m_book;
|
return m_book;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint256 const&
|
|
||||||
dir() const noexcept
|
|
||||||
{
|
|
||||||
return m_tip.dir();
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Returns the offer at the tip of the order book.
|
/** Returns the offer at the tip of the order book.
|
||||||
Offers are always presented in decreasing quality.
|
Offers are always presented in decreasing quality.
|
||||||
Only valid if step() returned `true`.
|
Only valid if step() returned `true`.
|
||||||
@@ -159,166 +103,22 @@ dir() const noexcept
|
|||||||
@return `true` if there is a valid offer.
|
@return `true` if there is a valid offer.
|
||||||
*/
|
*/
|
||||||
bool
|
bool
|
||||||
step()
|
step ();
|
||||||
{
|
|
||||||
// Modifying the order or logic of these
|
|
||||||
// operations causes a protocol breaking change.
|
|
||||||
|
|
||||||
for(;;)
|
/** Advance to the next valid offer that is not from the specified account.
|
||||||
{
|
This automatically removes:
|
||||||
// BookTip::step deletes the current offer from the view before
|
- Offers with missing ledger entries
|
||||||
// advancing to the next (unless the ledger entry is missing).
|
- Offers found unfunded
|
||||||
if (! m_tip.step())
|
- Offers from the same account
|
||||||
return false;
|
- Expired offers
|
||||||
|
@return `true` if there is a valid offer.
|
||||||
SLE::pointer const& entry (m_tip.entry());
|
|
||||||
|
|
||||||
// Remove if missing
|
|
||||||
if (! entry)
|
|
||||||
{
|
|
||||||
erase (view());
|
|
||||||
erase (view_cancel());
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove if expired
|
|
||||||
if (entry->isFieldPresent (sfExpiration) &&
|
|
||||||
entry->getFieldU32 (sfExpiration) <= m_when)
|
|
||||||
{
|
|
||||||
view_cancel().offerDelete (entry->getIndex());
|
|
||||||
if (m_journal.trace) m_journal.trace <<
|
|
||||||
"Removing expired offer " << entry->getIndex();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
m_offer = Offer (entry, m_tip.quality());
|
|
||||||
|
|
||||||
Amounts const amount (m_offer.amount());
|
|
||||||
|
|
||||||
// Remove if either amount is zero
|
|
||||||
if (amount.empty())
|
|
||||||
{
|
|
||||||
view_cancel().offerDelete (entry->getIndex());
|
|
||||||
if (m_journal.warning) m_journal.warning <<
|
|
||||||
"Removing bad offer " << entry->getIndex();
|
|
||||||
m_offer = Offer{};
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Calculate owner funds
|
|
||||||
// VFALCO NOTE The calling code also checks the funds,
|
|
||||||
// how expensive is looking up the funds twice?
|
|
||||||
Amount const owner_funds (view().accountFunds (
|
|
||||||
m_offer.account(), m_offer.amount().out));
|
|
||||||
|
|
||||||
// Check for unfunded offer
|
|
||||||
if (owner_funds <= zero)
|
|
||||||
{
|
|
||||||
// If the owner's balance in the pristine view is the same,
|
|
||||||
// we haven't modified the balance and therefore the
|
|
||||||
// offer is "found unfunded" versus "became unfunded"
|
|
||||||
if (view_cancel().accountFunds (m_offer.account(),
|
|
||||||
m_offer.amount().out) == owner_funds)
|
|
||||||
{
|
|
||||||
view_cancel().offerDelete (entry->getIndex());
|
|
||||||
if (m_journal.trace) m_journal.trace <<
|
|
||||||
"Removing unfunded offer " << entry->getIndex();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (m_journal.trace) m_journal.trace <<
|
|
||||||
"Removing became unfunded offer " << entry->getIndex();
|
|
||||||
}
|
|
||||||
m_offer = Offer{};
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
#if 0
|
|
||||||
// Remove if its our own offer
|
|
||||||
//
|
|
||||||
// VFALCO NOTE We might not want this for payments
|
|
||||||
//
|
|
||||||
if (m_account == owner)
|
|
||||||
{
|
|
||||||
view_cancel().offerDelete (entry->getIndex());
|
|
||||||
if (m_journal.trace) m_journal.trace <<
|
|
||||||
"Removing self offer " << entry->getIndex();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Updates the offer to reflect remaining funds.
|
|
||||||
The caller is responsible for following all the rounding rules.
|
|
||||||
The offer will be considered fully consumed if either the in
|
|
||||||
or the out amount is zero.
|
|
||||||
@return `true` If the offer had no funds remaining.
|
|
||||||
*/
|
*/
|
||||||
bool
|
bool
|
||||||
fill (Amounts const& remaining_funds)
|
step_account (Account const& account);
|
||||||
{
|
|
||||||
// Erase the offer if it is fully consumed (in==0 || out==0)
|
|
||||||
// This is the same as becoming unfunded
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
Does everything an OfferStream does, and:
|
|
||||||
- remove offers that became unfunded (if path is used)
|
|
||||||
*/
|
|
||||||
#if 0
|
|
||||||
class PaymentOfferStream : public OfferStream
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
PaymentOfferStream (LedgerView& view_base, BookRef book,
|
|
||||||
Clock::time_point when, beast::Journal journal)
|
|
||||||
: m_journal (journal)
|
|
||||||
, m_view (view_base.duplicate())
|
|
||||||
, m_view_apply (view_base.duplicate())
|
|
||||||
, m_book (book)
|
|
||||||
, m_when (when)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
};
|
|
||||||
#endif
|
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
/*
|
|
||||||
TakerOfferStream
|
|
||||||
Does everything a PaymentOfferStream does, and:
|
|
||||||
- remove offers owned by the taker (if tx succeeds?)
|
|
||||||
*/
|
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
OfferStream
|
|
||||||
- remove offers with missing ledger entries (always)
|
|
||||||
- remove expired offers (always)
|
|
||||||
- remove offers found unfunded (always)
|
|
||||||
|
|
||||||
PaymentOfferStream
|
|
||||||
Does everything an OfferStream does, and:
|
|
||||||
- remove offers that became unfunded (if path is used)
|
|
||||||
|
|
||||||
TakerOfferStream
|
|
||||||
Does everything a PaymentOfferStream does, and:
|
|
||||||
- remove offers owned by the taker (if tx succeeds?)
|
|
||||||
*/
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|||||||
@@ -23,9 +23,7 @@
|
|||||||
#include "Amount.h"
|
#include "Amount.h"
|
||||||
#include "Amounts.h"
|
#include "Amounts.h"
|
||||||
|
|
||||||
#include <cassert>
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <limits>
|
|
||||||
#include <ostream>
|
#include <ostream>
|
||||||
|
|
||||||
namespace ripple {
|
namespace ripple {
|
||||||
@@ -49,57 +47,53 @@ private:
|
|||||||
public:
|
public:
|
||||||
Quality() = default;
|
Quality() = default;
|
||||||
|
|
||||||
|
/** Create a quality from the integer encoding of an Amount */
|
||||||
explicit
|
explicit
|
||||||
Quality (std::uint64_t value)
|
Quality (std::uint64_t value);
|
||||||
: m_value (value)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Create a quality from the ratio of two amounts. */
|
/** Create a quality from the ratio of two amounts. */
|
||||||
explicit
|
explicit
|
||||||
Quality (Amounts const& amount)
|
Quality (Amounts const& amount);
|
||||||
: m_value (Amount::getRate (amount.out, amount.in))
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Advances to the next higher quality level. */
|
/** Advances to the next higher quality level. */
|
||||||
/** @{ */
|
/** @{ */
|
||||||
Quality&
|
Quality&
|
||||||
operator++()
|
operator++();
|
||||||
{
|
|
||||||
assert (m_value > 0);
|
|
||||||
--m_value;
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
Quality
|
Quality
|
||||||
operator++ (int)
|
operator++ (int);
|
||||||
{
|
|
||||||
Quality prev (*this);
|
|
||||||
--*this;
|
|
||||||
return prev;
|
|
||||||
}
|
|
||||||
/** @} */
|
/** @} */
|
||||||
|
|
||||||
/** Advances to the next lower quality level. */
|
/** Advances to the next lower quality level. */
|
||||||
/** @{ */
|
/** @{ */
|
||||||
Quality&
|
Quality&
|
||||||
operator--()
|
operator--();
|
||||||
{
|
|
||||||
assert (m_value < std::numeric_limits<value_type>::max());
|
|
||||||
++m_value;
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
Quality
|
Quality
|
||||||
operator-- (int)
|
operator-- (int);
|
||||||
{
|
|
||||||
Quality prev (*this);
|
|
||||||
++*this;
|
|
||||||
return prev;
|
|
||||||
}
|
|
||||||
/** @} */
|
/** @} */
|
||||||
|
|
||||||
|
/** Returns the quality as Amount. */
|
||||||
|
Amount
|
||||||
|
rate () const
|
||||||
|
{
|
||||||
|
return Amount::setRate (m_value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns the scaled amount with in capped.
|
||||||
|
Math is avoided if the result is exact. The output is clamped
|
||||||
|
to prevent money creation.
|
||||||
|
*/
|
||||||
|
Amounts
|
||||||
|
ceil_in (Amounts const& amount, Amount const& limit) const;
|
||||||
|
|
||||||
|
/** Returns the scaled amount with out capped.
|
||||||
|
Math is avoided if the result is exact. The input is clamped
|
||||||
|
to prevent money creation.
|
||||||
|
*/
|
||||||
|
Amounts
|
||||||
|
ceil_out (Amounts const& amount, Amount const& limit) const;
|
||||||
|
|
||||||
/** Returns `true` if lhs is lower quality than `rhs`.
|
/** Returns `true` if lhs is lower quality than `rhs`.
|
||||||
Lower quality means the taker receives a worse deal.
|
Lower quality means the taker receives a worse deal.
|
||||||
Higher quality is better for the taker.
|
Higher quality is better for the taker.
|
||||||
@@ -119,79 +113,28 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
friend
|
friend
|
||||||
std::ostream&
|
|
||||||
operator<< (std::ostream& os, Quality const& quality)
|
|
||||||
{
|
|
||||||
os << quality.m_value;
|
|
||||||
return os;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Returns the quality as Amount. */
|
|
||||||
Amount
|
|
||||||
rate() const
|
|
||||||
{
|
|
||||||
return Amount::setRate (m_value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Returns the scaled amount with in capped.
|
|
||||||
Math is avoided if the result is exact. The output is clamped
|
|
||||||
to prevent money creation.
|
|
||||||
*/
|
|
||||||
Amounts
|
|
||||||
ceil_in (Amounts const& amount, Amount const& limit) const
|
|
||||||
{
|
|
||||||
if (amount.in > limit)
|
|
||||||
{
|
|
||||||
// VFALCO TODO make mulRound avoid math when v2==one
|
|
||||||
#if 0
|
|
||||||
Amounts result (limit, Amount::mulRound (
|
|
||||||
limit, rate(), amount.out, true));
|
|
||||||
#else
|
|
||||||
Amounts result (limit, Amount::divRound (
|
|
||||||
limit, rate(), amount.out, true));
|
|
||||||
#endif
|
|
||||||
// Clamp out
|
|
||||||
if (result.out > amount.out)
|
|
||||||
result.out = amount.out;
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
return amount;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Returns the scaled amount with out capped.
|
|
||||||
Math is avoided if the result is exact. The input is clamped
|
|
||||||
to prevent money creation.
|
|
||||||
*/
|
|
||||||
Amounts
|
|
||||||
ceil_out (Amounts const& amount, Amount const& limit) const
|
|
||||||
{
|
|
||||||
if (amount.out > limit)
|
|
||||||
{
|
|
||||||
// VFALCO TODO make divRound avoid math when v2==one
|
|
||||||
#if 0
|
|
||||||
Amounts result (Amount::divRound (
|
|
||||||
limit, rate(), amount.in, true), limit);
|
|
||||||
#else
|
|
||||||
Amounts result (Amount::mulRound (
|
|
||||||
limit, rate(), amount.in, true), limit);
|
|
||||||
#endif
|
|
||||||
// Clamp in
|
|
||||||
if (result.in > amount.in)
|
|
||||||
result.in = amount.in;
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
return amount;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
inline
|
|
||||||
bool
|
bool
|
||||||
operator!= (Quality const& lhs, Quality const& rhs) noexcept
|
operator!= (Quality const& lhs, Quality const& rhs) noexcept
|
||||||
{
|
{
|
||||||
return ! (lhs == rhs);
|
return ! (lhs == rhs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
friend
|
||||||
|
std::ostream&
|
||||||
|
operator<< (std::ostream& os, Quality const& quality)
|
||||||
|
{
|
||||||
|
os << quality.m_value;
|
||||||
|
return os;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Calculate the quality of a two-hop path given the two hops.
|
||||||
|
@param lhs The first leg of the path: input to intermediate.
|
||||||
|
@param rhs The second leg of the path: intermediate to output.
|
||||||
|
*/
|
||||||
|
Quality
|
||||||
|
composed_quality (Quality const& lhs, Quality const& rhs);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
/*
|
/*
|
||||||
This file is part of rippled: https://github.com/ripple/rippled
|
This file is part of rippled: https://github.com/ripple/rippled
|
||||||
Copyright (c) 2012, 2013 Ripple Labs Inc.
|
Copyright (c) 2014 Ripple Labs Inc.
|
||||||
|
|
||||||
Permission to use, copy, modify, and/or distribute this software for any
|
Permission to use, copy, modify, and/or distribute this software for any
|
||||||
purpose with or without fee is hereby granted, provided that the above
|
purpose with or without fee is hereby granted, provided that the above
|
||||||
@@ -22,11 +22,14 @@
|
|||||||
|
|
||||||
#include "Amounts.h"
|
#include "Amounts.h"
|
||||||
#include "Quality.h"
|
#include "Quality.h"
|
||||||
|
#include "Offer.h"
|
||||||
#include "Types.h"
|
#include "Types.h"
|
||||||
|
|
||||||
#include "../../beast/beast/streams/debug_ostream.h"
|
#include "../../beast/beast/streams/debug_ostream.h"
|
||||||
|
#include "../../beast/beast/utility/noexcept.h"
|
||||||
|
|
||||||
#include <utility>
|
#include <functional>
|
||||||
|
//#include <utility>
|
||||||
|
|
||||||
namespace ripple {
|
namespace ripple {
|
||||||
namespace core {
|
namespace core {
|
||||||
@@ -37,7 +40,7 @@ class Taker
|
|||||||
public:
|
public:
|
||||||
struct Options
|
struct Options
|
||||||
{
|
{
|
||||||
Options() = default;
|
Options() = delete;
|
||||||
|
|
||||||
explicit
|
explicit
|
||||||
Options (std::uint32_t tx_flags)
|
Options (std::uint32_t tx_flags)
|
||||||
@@ -56,47 +59,31 @@ public:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
std::reference_wrapper <LedgerView> m_view;
|
std::reference_wrapper <LedgerView> m_view;
|
||||||
Book m_book;
|
|
||||||
Account m_account;
|
Account m_account;
|
||||||
Options m_options;
|
Options m_options;
|
||||||
Quality m_quality;
|
Quality m_quality;
|
||||||
Quality m_threshold;
|
Quality m_threshold;
|
||||||
|
|
||||||
// The original in and out quantities
|
// The original in and out quantities.
|
||||||
Amounts m_amount;
|
Amounts const m_amount;
|
||||||
|
|
||||||
// Amount of input currency remaining.
|
// The amounts still left over for us to try and take.
|
||||||
Amount m_in;
|
Amounts m_remain;
|
||||||
|
|
||||||
// Amount of output currency we have received.
|
private:
|
||||||
Amount m_out;
|
Amounts
|
||||||
|
flow (Amounts amount, Offer const& offer, Account const& taker);
|
||||||
|
|
||||||
// Returns the balance of the taker's input currency,
|
TER
|
||||||
Amount
|
fill (Offer const& offer, Amounts const& amount);
|
||||||
funds() const
|
|
||||||
{
|
TER
|
||||||
return view().accountFunds (account(), m_in);
|
fill (Offer const& leg1, Amounts const& amount1,
|
||||||
}
|
Offer const& leg2, Amounts const& amount2);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
Taker (LedgerView& view, BookRef const& book,
|
Taker (LedgerView& view, Account const& account,
|
||||||
Account const& account, Amounts const& amount,
|
Amounts const& amount, Options const& options);
|
||||||
Options const& options)
|
|
||||||
: m_view (view)
|
|
||||||
, m_book (book)
|
|
||||||
, m_account (account)
|
|
||||||
, m_options (options)
|
|
||||||
, m_quality (amount)
|
|
||||||
, m_threshold (m_quality)
|
|
||||||
, m_amount (amount)
|
|
||||||
, m_in (amount.in)
|
|
||||||
, m_out (amount.out.getCurrency(), amount.out.getIssuer())
|
|
||||||
{
|
|
||||||
// If this is a passive order (tfPassive), this prevents
|
|
||||||
// offers at the same quality level from being consumed.
|
|
||||||
if (m_options.passive)
|
|
||||||
++m_threshold;
|
|
||||||
}
|
|
||||||
|
|
||||||
LedgerView&
|
LedgerView&
|
||||||
view () const noexcept
|
view () const noexcept
|
||||||
@@ -104,12 +91,14 @@ public:
|
|||||||
return m_view;
|
return m_view;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Returns the input and output asset pair identifier. */
|
/** Returns the amount remaining on the offer.
|
||||||
Book const&
|
This is the amount at which the offer should be placed. It may either
|
||||||
book() const noexcept
|
be for the full amount when there were no crossing offers, or for zero
|
||||||
{
|
when the offer fully crossed, or any amount in between.
|
||||||
return m_book;
|
It is always at the original offer quality (m_quality)
|
||||||
}
|
*/
|
||||||
|
Amounts
|
||||||
|
remaining_offer () const;
|
||||||
|
|
||||||
/** Returns the account identifier of the taker. */
|
/** Returns the account identifier of the taker. */
|
||||||
Account const&
|
Account const&
|
||||||
@@ -118,37 +107,6 @@ public:
|
|||||||
return m_account;
|
return m_account;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Returns `true` if order crossing should not continue.
|
|
||||||
Order processing is stopped if the taker's order quantities have
|
|
||||||
been reached, or if the taker has run out of input funds.
|
|
||||||
*/
|
|
||||||
bool
|
|
||||||
done() const noexcept
|
|
||||||
{
|
|
||||||
if (m_options.sell)
|
|
||||||
{
|
|
||||||
// With the sell option, we are finished when
|
|
||||||
// we have consumed all the input currency.
|
|
||||||
if (m_in <= zero)
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
else if (m_out >= m_amount.out)
|
|
||||||
{
|
|
||||||
// With the buy option (!sell) we are finished when we
|
|
||||||
// have received the desired amount of output currency.
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// We are finished if the taker is out of funds
|
|
||||||
return funds() <= zero;
|
|
||||||
}
|
|
||||||
|
|
||||||
Quality
|
|
||||||
threshold() const noexcept
|
|
||||||
{
|
|
||||||
return m_threshold;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Returns `true` if the quality does not meet the taker's requirements. */
|
/** Returns `true` if the quality does not meet the taker's requirements. */
|
||||||
bool
|
bool
|
||||||
reject (Quality const& quality) const noexcept
|
reject (Quality const& quality) const noexcept
|
||||||
@@ -156,138 +114,26 @@ public:
|
|||||||
return quality < m_threshold;
|
return quality < m_threshold;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Calcualtes the result of applying the taker's funds to the offer.
|
/** Returns `true` if order crossing should not continue.
|
||||||
@return The flow and flag indicating if the order was consumed.
|
Order processing is stopped if the taker's order quantities have
|
||||||
|
been reached, or if the taker has run out of input funds.
|
||||||
*/
|
*/
|
||||||
std::pair <Amounts, bool>
|
bool
|
||||||
fill (Offer const& offer) const
|
done () const;
|
||||||
{
|
|
||||||
// Best flow the owner can get.
|
|
||||||
// Start out assuming entire offer will flow.
|
|
||||||
Amounts owner_amount (offer.amount());
|
|
||||||
|
|
||||||
// Limit owner's output by available funds less fees
|
/** Perform direct crossing through given offer.
|
||||||
|
@return tesSUCCESS on success, error code otherwise.
|
||||||
// VFALCO TODO Rename accountFounds to make it clear that
|
|
||||||
// it can return a clamped value.
|
|
||||||
Amount const owner_funds (view().accountFunds (
|
|
||||||
offer.account(), owner_amount.out));
|
|
||||||
|
|
||||||
// Get fee rate paid by owner
|
|
||||||
std::uint32_t const owner_charge_rate (view().rippleTransferRate (
|
|
||||||
offer.account(), account(), offer.entry()->getFieldAmount (
|
|
||||||
sfTakerGets).getIssuer()));
|
|
||||||
Amount const owner_charge (Amount::saFromRate (owner_charge_rate));
|
|
||||||
|
|
||||||
// VFALCO Make Amount::divide skip math if v2 == QUALITY_ONE
|
|
||||||
if (owner_charge_rate == QUALITY_ONE)
|
|
||||||
{
|
|
||||||
// Skip some math when there's no fee
|
|
||||||
owner_amount = offer.quality().ceil_out (
|
|
||||||
owner_amount, owner_funds);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
owner_amount = offer.quality().ceil_out (owner_amount,
|
|
||||||
Amount::divide (owner_funds, owner_charge));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Best flow the taker can get.
|
|
||||||
// Start out assuming entire offer will flow.
|
|
||||||
Amounts taker_amount (offer.amount());
|
|
||||||
|
|
||||||
// Limit taker's input by available funds less fees
|
|
||||||
|
|
||||||
Amount const taker_funds (view().accountFunds (account(), m_in));
|
|
||||||
|
|
||||||
// Get fee rate paid by taker
|
|
||||||
std::uint32_t const taker_charge_rate (view().rippleTransferRate (
|
|
||||||
account(), offer.account(), offer.entry()->getFieldAmount (
|
|
||||||
sfTakerPays).getIssuer()));
|
|
||||||
Amount const taker_charge (Amount::saFromRate (taker_charge_rate));
|
|
||||||
|
|
||||||
// VFALCO Make Amount::divide skip math if v2 == QUALITY_ONE
|
|
||||||
if (taker_charge_rate == QUALITY_ONE)
|
|
||||||
{
|
|
||||||
// Skip some math when there's no fee
|
|
||||||
taker_amount = offer.quality().ceil_in (
|
|
||||||
taker_amount, taker_funds);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
taker_amount = offer.quality().ceil_in (taker_amount,
|
|
||||||
Amount::divide (taker_funds, taker_charge));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Limit taker's input by options
|
|
||||||
if (! m_options.sell)
|
|
||||||
{
|
|
||||||
assert (m_out < m_amount.out);
|
|
||||||
taker_amount = offer.quality().ceil_out (
|
|
||||||
taker_amount, m_amount.out - m_out);
|
|
||||||
assert (taker_amount.in != zero);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Calculate the amount that will flow through the offer
|
|
||||||
// This does not include the fees.
|
|
||||||
Amounts const flow ((owner_amount.in < taker_amount.in) ?
|
|
||||||
owner_amount : taker_amount);
|
|
||||||
|
|
||||||
bool const consumed (flow.out >= owner_amount.out);
|
|
||||||
|
|
||||||
return std::make_pair (flow, consumed);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Process the result of fee and funds calculation on the offer.
|
|
||||||
|
|
||||||
To protect the ledger, conditions which should never occur are
|
|
||||||
checked. If the invariants are broken, the processing fails.
|
|
||||||
|
|
||||||
If processing succeeds, the funds are distributed to the taker,
|
|
||||||
owner, and issuers.
|
|
||||||
|
|
||||||
@return `false` if processing failed (due to math errors).
|
|
||||||
*/
|
*/
|
||||||
TER
|
TER
|
||||||
process (Amounts const& flow, Offer const& offer)
|
cross (Offer const& offer);
|
||||||
{
|
|
||||||
TER result (tesSUCCESS);
|
|
||||||
|
|
||||||
// VFALCO For the case of !sell, is it possible for the taker
|
/** Perform bridged crossing through given offers.
|
||||||
// to get a tiny bit more than he asked for?
|
@return tesSUCCESS on success, error code otherwise.
|
||||||
// DAVIDS Can you verify?
|
*/
|
||||||
assert (m_options.sell || flow.out <= m_amount.out);
|
TER
|
||||||
|
cross (Offer const& leg1, Offer const& leg2);
|
||||||
// Calculate remaining portion of offer
|
|
||||||
Amounts const remain (
|
|
||||||
offer.entry()->getFieldAmount (sfTakerPays) - flow.in,
|
|
||||||
offer.entry()->getFieldAmount (sfTakerGets) - flow.out);
|
|
||||||
|
|
||||||
offer.entry()->setFieldAmount (sfTakerPays, remain.in);
|
|
||||||
offer.entry()->setFieldAmount (sfTakerGets, remain.out);
|
|
||||||
view().entryModify (offer.entry());
|
|
||||||
|
|
||||||
// Pay the taker
|
|
||||||
result = view().accountSend (offer.account(), account(), flow.out);
|
|
||||||
|
|
||||||
if (result == tesSUCCESS)
|
|
||||||
{
|
|
||||||
m_out += flow.out;
|
|
||||||
|
|
||||||
// Pay the owner
|
|
||||||
result = view().accountSend (account(), offer.account(), flow.in);
|
|
||||||
|
|
||||||
if (result == tesSUCCESS)
|
|
||||||
{
|
|
||||||
m_in -= flow.in;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
inline
|
inline
|
||||||
std::ostream&
|
std::ostream&
|
||||||
operator<< (std::ostream& os, Taker const& taker)
|
operator<< (std::ostream& os, Taker const& taker)
|
||||||
@@ -298,59 +144,4 @@ operator<< (std::ostream& os, Taker const& taker)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
|
|
||||||
// This code calculates the fees but then we discovered
|
|
||||||
// that LedgerEntrySet::accountSend does it for you.
|
|
||||||
|
|
||||||
Amounts fees;
|
|
||||||
|
|
||||||
// Calculate taker fee
|
|
||||||
if (taker_charge_rate == QUALITY_ONE)
|
|
||||||
{
|
|
||||||
// No fee, skip math
|
|
||||||
fees.in = Amount (flow.in.getCurrency(),
|
|
||||||
flow.in.getIssuer());
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// VFALCO TODO Check the units (versus 4-arg version of mulRound)
|
|
||||||
Amount const in_plus_fees (Amount::mulRound (
|
|
||||||
flow.in, taker_charge, true));
|
|
||||||
// Make sure the taker has enough to pay the fee
|
|
||||||
if (in_plus_fees > taker_funds)
|
|
||||||
{
|
|
||||||
// Not enough funds, stiff the issuer
|
|
||||||
fees.in = taker_funds - flow.in;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
fees.in = in_plus_fees - flow.in;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Calculate owner fee
|
|
||||||
if (owner_charge_rate == QUALITY_ONE)
|
|
||||||
{
|
|
||||||
// No fee, skip math
|
|
||||||
fees.out = Amount (flow.out.getCurrency(),
|
|
||||||
flow.out.getIssuer());
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Amount const out_plus_fees (Amount::mulRound (
|
|
||||||
flow.out, owner_charge, true));
|
|
||||||
// Make sure the owner has enough to pay the fee
|
|
||||||
if (out_plus_fees > owner_funds)
|
|
||||||
{
|
|
||||||
// Not enough funds, stiff the issuer
|
|
||||||
fees.out = owner_funds - flow.out;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
fees.out = out_plus_fees - flow.out;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
/*
|
/*
|
||||||
This file is part of rippled: https://github.com/ripple/rippled
|
This file is part of rippled: https://github.com/ripple/rippled
|
||||||
Copyright (c) 2012, 2013 Ripple Labs Inc.
|
Copyright (c) 2014 Ripple Labs Inc.
|
||||||
|
|
||||||
Permission to use, copy, modify, and/or distribute this software for any
|
Permission to use, copy, modify, and/or distribute this software for any
|
||||||
purpose with or without fee is hereby granted, provided that the above
|
purpose with or without fee is hereby granted, provided that the above
|
||||||
@@ -22,7 +22,7 @@
|
|||||||
|
|
||||||
#include "../ledger/LedgerEntrySet.h"
|
#include "../ledger/LedgerEntrySet.h"
|
||||||
#include "../../ripple/types/api/RippleAssets.h"
|
#include "../../ripple/types/api/RippleAssets.h"
|
||||||
#include "../../ripple/types/api/UInt160.h"
|
#include "../../ripple/types/api/base_uint.h"
|
||||||
|
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
|||||||
81
src/ripple_app/book/impl/BookTip.cpp
Normal file
81
src/ripple_app/book/impl/BookTip.cpp
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
//------------------------------------------------------------------------------
|
||||||
|
/*
|
||||||
|
This file is part of rippled: https://github.com/ripple/rippled
|
||||||
|
Copyright (c) 2014 Ripple Labs Inc.
|
||||||
|
|
||||||
|
Permission to use, copy, modify, and/or distribute this software for any
|
||||||
|
purpose with or without fee is hereby granted, provided that the above
|
||||||
|
copyright notice and this permission notice appear in all copies.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
*/
|
||||||
|
//==============================================================================
|
||||||
|
|
||||||
|
#include "../BookTip.h"
|
||||||
|
|
||||||
|
namespace ripple {
|
||||||
|
namespace core {
|
||||||
|
|
||||||
|
BookTip::BookTip (LedgerView& view, BookRef book)
|
||||||
|
: m_view (view)
|
||||||
|
, m_valid (false)
|
||||||
|
, m_book (Ledger::getBookBase (
|
||||||
|
book.in.currency, book.in.issuer,
|
||||||
|
book.out.currency, book.out.issuer))
|
||||||
|
, m_end (Ledger::getQualityNext (m_book))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
BookTip::step ()
|
||||||
|
{
|
||||||
|
if (m_valid)
|
||||||
|
{
|
||||||
|
if (m_entry)
|
||||||
|
{
|
||||||
|
view().offerDelete (m_index);
|
||||||
|
m_entry = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for(;;)
|
||||||
|
{
|
||||||
|
// See if there's an entry at or worse than current quality.
|
||||||
|
auto const page (
|
||||||
|
view().getNextLedgerIndex (m_book, m_end));
|
||||||
|
|
||||||
|
if (page.isZero())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
unsigned int di (0);
|
||||||
|
SLE::pointer dir;
|
||||||
|
if (view().dirFirst (page, dir, di, m_index))
|
||||||
|
{
|
||||||
|
m_dir = dir->getIndex();
|
||||||
|
m_entry = view().entryCache (ltOFFER, m_index);
|
||||||
|
m_valid = true;
|
||||||
|
|
||||||
|
// Next query should start before this directory
|
||||||
|
m_book = page;
|
||||||
|
|
||||||
|
// The quality immediately before the next quality
|
||||||
|
--m_book;
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// There should never be an empty directory but just in case,
|
||||||
|
// we handle that case by advancing to the next directory.
|
||||||
|
m_book = page;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
170
src/ripple_app/book/impl/OfferStream.cpp
Normal file
170
src/ripple_app/book/impl/OfferStream.cpp
Normal file
@@ -0,0 +1,170 @@
|
|||||||
|
//------------------------------------------------------------------------------
|
||||||
|
/*
|
||||||
|
This file is part of rippled: https://github.com/ripple/rippled
|
||||||
|
Copyright (c) 2012, 2013 Ripple Labs Inc.
|
||||||
|
|
||||||
|
Permission to use, copy, modify, and/or distribute this software for any
|
||||||
|
purpose with or without fee is hereby granted, provided that the above
|
||||||
|
copyright notice and this permission notice appear in all copies.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
*/
|
||||||
|
//==============================================================================
|
||||||
|
|
||||||
|
#include "../OfferStream.h"
|
||||||
|
|
||||||
|
namespace ripple {
|
||||||
|
namespace core {
|
||||||
|
|
||||||
|
OfferStream::OfferStream (LedgerView& view, LedgerView& view_cancel,
|
||||||
|
BookRef book, Clock::time_point when, beast::Journal journal)
|
||||||
|
: m_journal (journal)
|
||||||
|
, m_view (view)
|
||||||
|
, m_view_cancel (view_cancel)
|
||||||
|
, m_book (book)
|
||||||
|
, m_when (when)
|
||||||
|
, m_tip (view, book)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle the case where a directory item with no corresponding ledger entry
|
||||||
|
// is found. This shouldn't happen but if it does we clean it up.
|
||||||
|
void
|
||||||
|
OfferStream::erase (LedgerView& view)
|
||||||
|
{
|
||||||
|
// NIKB NOTE This should be using LedgerView::dirDelete, which would
|
||||||
|
// correctly remove the directory if its the last entry.
|
||||||
|
// Unfortunately this is a protocol breaking change.
|
||||||
|
|
||||||
|
auto p (view.entryCache (ltDIR_NODE, m_tip.dir()));
|
||||||
|
|
||||||
|
if (p == nullptr)
|
||||||
|
{
|
||||||
|
if (m_journal.error) m_journal.error <<
|
||||||
|
"Missing directory " << m_tip.dir() <<
|
||||||
|
" for offer " << m_tip.index();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto v (p->getFieldV256 (sfIndexes));
|
||||||
|
auto& x (v.peekValue());
|
||||||
|
auto it (std::find (x.begin(), x.end(), m_tip.index()));
|
||||||
|
|
||||||
|
if (it == x.end())
|
||||||
|
{
|
||||||
|
if (m_journal.error) m_journal.error <<
|
||||||
|
"Missing offer " << m_tip.index() <<
|
||||||
|
" for directory " << m_tip.dir();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
x.erase (it);
|
||||||
|
p->setFieldV256 (sfIndexes, v);
|
||||||
|
view.entryModify (p);
|
||||||
|
|
||||||
|
if (m_journal.trace) m_journal.trace <<
|
||||||
|
"Missing offer " << m_tip.index() <<
|
||||||
|
" removed from directory " << m_tip.dir();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
OfferStream::step ()
|
||||||
|
{
|
||||||
|
// Modifying the order or logic of these
|
||||||
|
// operations causes a protocol breaking change.
|
||||||
|
|
||||||
|
for(;;)
|
||||||
|
{
|
||||||
|
// BookTip::step deletes the current offer from the view before
|
||||||
|
// advancing to the next (unless the ledger entry is missing).
|
||||||
|
if (! m_tip.step())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
SLE::pointer const& entry (m_tip.entry());
|
||||||
|
|
||||||
|
// Remove if missing
|
||||||
|
if (! entry)
|
||||||
|
{
|
||||||
|
erase (view());
|
||||||
|
erase (view_cancel());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove if expired
|
||||||
|
if (entry->isFieldPresent (sfExpiration) &&
|
||||||
|
entry->getFieldU32 (sfExpiration) <= m_when)
|
||||||
|
{
|
||||||
|
view_cancel().offerDelete (entry->getIndex());
|
||||||
|
if (m_journal.trace) m_journal.trace <<
|
||||||
|
"Removing expired offer " << entry->getIndex();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_offer = Offer (entry, m_tip.quality());
|
||||||
|
|
||||||
|
Amounts const amount (m_offer.amount());
|
||||||
|
|
||||||
|
// Remove if either amount is zero
|
||||||
|
if (amount.empty())
|
||||||
|
{
|
||||||
|
view_cancel().offerDelete (entry->getIndex());
|
||||||
|
if (m_journal.warning) m_journal.warning <<
|
||||||
|
"Removing bad offer " << entry->getIndex();
|
||||||
|
m_offer = Offer{};
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate owner funds
|
||||||
|
// NIKB NOTE The calling code also checks the funds, how expensive is
|
||||||
|
// looking up the funds twice?
|
||||||
|
Amount const owner_funds (view().accountFunds (
|
||||||
|
m_offer.account(), m_offer.amount().out));
|
||||||
|
|
||||||
|
// Check for unfunded offer
|
||||||
|
if (owner_funds <= zero)
|
||||||
|
{
|
||||||
|
// If the owner's balance in the pristine view is the same,
|
||||||
|
// we haven't modified the balance and therefore the
|
||||||
|
// offer is "found unfunded" versus "became unfunded"
|
||||||
|
if (view_cancel().accountFunds (m_offer.account(),
|
||||||
|
m_offer.amount().out) == owner_funds)
|
||||||
|
{
|
||||||
|
view_cancel().offerDelete (entry->getIndex());
|
||||||
|
if (m_journal.trace) m_journal.trace <<
|
||||||
|
"Removing unfunded offer " << entry->getIndex();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (m_journal.trace) m_journal.trace <<
|
||||||
|
"Removing became unfunded offer " << entry->getIndex();
|
||||||
|
}
|
||||||
|
m_offer = Offer{};
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
OfferStream::step_account (Account const& account)
|
||||||
|
{
|
||||||
|
while (step ())
|
||||||
|
{
|
||||||
|
if (tip ().account () != account)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
120
src/ripple_app/book/impl/Quality.cpp
Normal file
120
src/ripple_app/book/impl/Quality.cpp
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
//------------------------------------------------------------------------------
|
||||||
|
/*
|
||||||
|
This file is part of rippled: https://github.com/ripple/rippled
|
||||||
|
Copyright (c) 2012, 2013 Ripple Labs Inc.
|
||||||
|
|
||||||
|
Permission to use, copy, modify, and/or distribute this software for any
|
||||||
|
purpose with or without fee is hereby granted, provided that the above
|
||||||
|
copyright notice and this permission notice appear in all copies.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
*/
|
||||||
|
//==============================================================================
|
||||||
|
|
||||||
|
#include "../Quality.h"
|
||||||
|
|
||||||
|
#include <cassert>
|
||||||
|
#include <limits>
|
||||||
|
|
||||||
|
namespace ripple {
|
||||||
|
namespace core {
|
||||||
|
|
||||||
|
Quality::Quality (std::uint64_t value)
|
||||||
|
: m_value (value)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
Quality::Quality (Amounts const& amount)
|
||||||
|
: m_value (Amount::getRate (amount.out, amount.in))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
Quality&
|
||||||
|
Quality::operator++()
|
||||||
|
{
|
||||||
|
assert (m_value > 0);
|
||||||
|
--m_value;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
Quality
|
||||||
|
Quality::operator++ (int)
|
||||||
|
{
|
||||||
|
Quality prev (*this);
|
||||||
|
--*this;
|
||||||
|
return prev;
|
||||||
|
}
|
||||||
|
|
||||||
|
Quality&
|
||||||
|
Quality::operator--()
|
||||||
|
{
|
||||||
|
assert (m_value < std::numeric_limits<value_type>::max());
|
||||||
|
++m_value;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
Quality
|
||||||
|
Quality::operator-- (int)
|
||||||
|
{
|
||||||
|
Quality prev (*this);
|
||||||
|
++*this;
|
||||||
|
return prev;
|
||||||
|
}
|
||||||
|
|
||||||
|
Amounts
|
||||||
|
Quality::ceil_in (Amounts const& amount, Amount const& limit) const
|
||||||
|
{
|
||||||
|
if (amount.in > limit)
|
||||||
|
{
|
||||||
|
Amounts result (limit, Amount::divRound (
|
||||||
|
limit, rate(), amount.out, true));
|
||||||
|
// Clamp out
|
||||||
|
if (result.out > amount.out)
|
||||||
|
result.out = amount.out;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
return amount;
|
||||||
|
}
|
||||||
|
|
||||||
|
Amounts
|
||||||
|
Quality::ceil_out (Amounts const& amount, Amount const& limit) const
|
||||||
|
{
|
||||||
|
if (amount.out > limit)
|
||||||
|
{
|
||||||
|
Amounts result (Amount::mulRound (
|
||||||
|
limit, rate(), amount.in, true), limit);
|
||||||
|
// Clamp in
|
||||||
|
if (result.in > amount.in)
|
||||||
|
result.in = amount.in;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
return amount;
|
||||||
|
}
|
||||||
|
|
||||||
|
Quality
|
||||||
|
composed_quality (Quality const& lhs, Quality const& rhs)
|
||||||
|
{
|
||||||
|
Amount const lhs_rate (lhs.rate ());
|
||||||
|
assert (lhs_rate != zero);
|
||||||
|
|
||||||
|
Amount const rhs_rate (rhs.rate ());
|
||||||
|
assert (rhs_rate != zero);
|
||||||
|
|
||||||
|
Amount const rate (Amount::mulRound (lhs_rate, rhs_rate, true));
|
||||||
|
|
||||||
|
std::uint64_t const stored_exponent (rate.getExponent () + 100);
|
||||||
|
std::uint64_t const stored_mantissa (rate.getMantissa ());
|
||||||
|
|
||||||
|
assert ((stored_exponent >= 0) && (stored_exponent <= 255));
|
||||||
|
|
||||||
|
return Quality ((stored_exponent << (64 - 8)) | stored_mantissa);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
281
src/ripple_app/book/impl/Taker.cpp
Normal file
281
src/ripple_app/book/impl/Taker.cpp
Normal file
@@ -0,0 +1,281 @@
|
|||||||
|
//------------------------------------------------------------------------------
|
||||||
|
/*
|
||||||
|
This file is part of rippled: https://github.com/ripple/rippled
|
||||||
|
Copyright (c) 2014 Ripple Labs Inc.
|
||||||
|
|
||||||
|
Permission to use, copy, modify, and/or distribute this software for any
|
||||||
|
purpose with or without fee is hereby granted, provided that the above
|
||||||
|
copyright notice and this permission notice appear in all copies.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
*/
|
||||||
|
//==============================================================================
|
||||||
|
|
||||||
|
#include "../Taker.h"
|
||||||
|
|
||||||
|
//#include <utility>
|
||||||
|
|
||||||
|
namespace ripple {
|
||||||
|
namespace core {
|
||||||
|
|
||||||
|
Taker::Taker (LedgerView& view, Account const& account,
|
||||||
|
Amounts const& amount, Options const& options)
|
||||||
|
: m_view (view)
|
||||||
|
, m_account (account)
|
||||||
|
, m_options (options)
|
||||||
|
, m_quality (amount)
|
||||||
|
, m_threshold (m_quality)
|
||||||
|
, m_amount (amount)
|
||||||
|
, m_remain (amount)
|
||||||
|
{
|
||||||
|
// If this is a passive order (tfPassive), this prevents
|
||||||
|
// offers at the same quality level from being consumed.
|
||||||
|
if (m_options.passive)
|
||||||
|
++m_threshold;
|
||||||
|
}
|
||||||
|
|
||||||
|
Amounts
|
||||||
|
Taker::remaining_offer () const
|
||||||
|
{
|
||||||
|
// If the taker is done, then there's no offer to place.
|
||||||
|
if (done ())
|
||||||
|
{
|
||||||
|
return Amounts (
|
||||||
|
Amount (m_amount.in.getCurrency (), m_amount.in.getIssuer ()),
|
||||||
|
Amount (m_amount.out.getCurrency (), m_amount.out.getIssuer ()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Avoid math altogether if we didn't cross.
|
||||||
|
if (m_amount == m_remain)
|
||||||
|
return m_amount;
|
||||||
|
|
||||||
|
if (m_options.sell)
|
||||||
|
{
|
||||||
|
assert (m_remain.in > zero);
|
||||||
|
|
||||||
|
// We scale the output based on the remaining input:
|
||||||
|
return Amounts (m_remain.in, Amount::divRound (
|
||||||
|
m_remain.in, m_quality.rate (), m_remain.out, true));
|
||||||
|
}
|
||||||
|
|
||||||
|
assert (m_remain.out > zero);
|
||||||
|
|
||||||
|
// We scale the input based on the remaining output:
|
||||||
|
return Amounts (Amount::mulRound (
|
||||||
|
m_remain.out, m_quality.rate (), m_remain.in, true), m_remain.out);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate the amount particular user could get through an offer.
|
||||||
|
// @param amount the maximum flow that is available to the taker.
|
||||||
|
// @param offer the offer to flow through.
|
||||||
|
// @param taker the person taking the offer.
|
||||||
|
// @return the maximum amount that can flow through this offer.
|
||||||
|
Amounts
|
||||||
|
Taker::flow (Amounts amount, Offer const& offer, Account const& taker)
|
||||||
|
{
|
||||||
|
// Limit taker's input by available funds less fees
|
||||||
|
Amount const taker_funds (view ().accountFunds (taker, amount.in));
|
||||||
|
|
||||||
|
// Get fee rate paid by taker
|
||||||
|
std::uint32_t const taker_charge_rate (view ().rippleTransferRate (
|
||||||
|
taker, offer.account (), amount.in.getIssuer()));
|
||||||
|
|
||||||
|
// Skip some math when there's no fee
|
||||||
|
if (taker_charge_rate == QUALITY_ONE)
|
||||||
|
{
|
||||||
|
amount = offer.quality ().ceil_in (amount, taker_funds);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Amount const taker_charge (Amount::saFromRate (taker_charge_rate));
|
||||||
|
amount = offer.quality ().ceil_in (amount,
|
||||||
|
Amount::divide (taker_funds, taker_charge));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Best flow the owner can get.
|
||||||
|
// Start out assuming entire offer will flow.
|
||||||
|
Amounts owner_amount (amount);
|
||||||
|
|
||||||
|
// Limit owner's output by available funds less fees
|
||||||
|
Amount const owner_funds (view ().accountFunds (
|
||||||
|
offer.account (), owner_amount.out));
|
||||||
|
|
||||||
|
// Get fee rate paid by owner
|
||||||
|
std::uint32_t const owner_charge_rate (view ().rippleTransferRate (
|
||||||
|
offer.account (), taker, amount.out.getIssuer()));
|
||||||
|
|
||||||
|
if (owner_charge_rate == QUALITY_ONE)
|
||||||
|
{
|
||||||
|
// Skip some math when there's no fee
|
||||||
|
owner_amount = offer.quality ().ceil_out (owner_amount, owner_funds);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Amount const owner_charge (Amount::saFromRate (owner_charge_rate));
|
||||||
|
owner_amount = offer.quality ().ceil_out (owner_amount,
|
||||||
|
Amount::divide (owner_funds, owner_charge));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate the amount that will flow through the offer
|
||||||
|
// This does not include the fees.
|
||||||
|
return (owner_amount.in < amount.in)
|
||||||
|
? owner_amount
|
||||||
|
: amount;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fill a direct offer.
|
||||||
|
// @param offer the offer we are going to use.
|
||||||
|
// @param amount the amount to flow through the offer.
|
||||||
|
// @returns: tesSUCCESS if successful, or an error code otherwise.
|
||||||
|
TER
|
||||||
|
Taker::fill (Offer const& offer, Amounts const& amount)
|
||||||
|
{
|
||||||
|
TER result (tesSUCCESS);
|
||||||
|
|
||||||
|
Amounts const remain (
|
||||||
|
offer.entry ()->getFieldAmount (sfTakerPays) - amount.in,
|
||||||
|
offer.entry ()->getFieldAmount (sfTakerGets) - amount.out);
|
||||||
|
|
||||||
|
offer.entry ()->setFieldAmount (sfTakerPays, remain.in);
|
||||||
|
offer.entry ()->setFieldAmount (sfTakerGets, remain.out);
|
||||||
|
view ().entryModify (offer.entry());
|
||||||
|
|
||||||
|
// Pay the taker, then the owner
|
||||||
|
result = view ().accountSend (offer.account(), account(), amount.out);
|
||||||
|
|
||||||
|
if (result == tesSUCCESS)
|
||||||
|
result = view ().accountSend (account(), offer.account(), amount.in);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fill a bridged offer.
|
||||||
|
// @param leg1 the first leg we are going to use.
|
||||||
|
// @param amount1 the amount to flow through the first leg of the offer.
|
||||||
|
// @param leg2 the second leg we are going to use.
|
||||||
|
// @param amount2 the amount to flow through the second leg of the offer.
|
||||||
|
// @return tesSUCCESS if successful, or an error code otherwise.
|
||||||
|
TER
|
||||||
|
Taker::fill (
|
||||||
|
Offer const& leg1, Amounts const& amount1,
|
||||||
|
Offer const& leg2, Amounts const& amount2)
|
||||||
|
{
|
||||||
|
assert (amount1.out == amount2.in);
|
||||||
|
|
||||||
|
TER result (tesSUCCESS);
|
||||||
|
|
||||||
|
Amounts const remain1 (
|
||||||
|
leg1.entry ()->getFieldAmount (sfTakerPays) - amount1.in,
|
||||||
|
leg1.entry ()->getFieldAmount (sfTakerGets) - amount1.out);
|
||||||
|
|
||||||
|
leg1.entry ()->setFieldAmount (sfTakerPays, remain1.in);
|
||||||
|
leg1.entry ()->setFieldAmount (sfTakerGets, remain1.out);
|
||||||
|
view ().entryModify (leg1.entry());
|
||||||
|
|
||||||
|
Amounts const remain2 (
|
||||||
|
leg2.entry ()->getFieldAmount (sfTakerPays) - amount2.in,
|
||||||
|
leg2.entry ()->getFieldAmount (sfTakerGets) - amount2.out);
|
||||||
|
view ().entryModify (leg2.entry ());
|
||||||
|
|
||||||
|
// Execute the payments in order:
|
||||||
|
// Taker pays Leg1 (amount1.in - A currency)
|
||||||
|
// Leg1 pays Leg2 (amount1.out == amount2.in, XRP)
|
||||||
|
// Leg2 pays Taker (amount2.out - B currency)
|
||||||
|
|
||||||
|
result = view ().accountSend (m_account, leg1.account (), amount1.in);
|
||||||
|
|
||||||
|
if (result == tesSUCCESS)
|
||||||
|
result = view ().accountSend (leg1.account (), leg2.account (), amount1.out);
|
||||||
|
|
||||||
|
if (result == tesSUCCESS)
|
||||||
|
result = view ().accountSend (leg2.account (), m_account, amount2.out);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
Taker::done () const
|
||||||
|
{
|
||||||
|
if (m_options.sell)
|
||||||
|
{
|
||||||
|
// With the sell option, we are finished when
|
||||||
|
// we have consumed all the input currency.
|
||||||
|
if (m_remain.in <= zero)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (m_remain.out <= zero)
|
||||||
|
{
|
||||||
|
// With the buy option (!sell) we are finished when we
|
||||||
|
// have received the desired amount of output currency.
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We are finished if the taker is out of funds
|
||||||
|
return view().accountFunds (account(), m_remain.in) <= zero;
|
||||||
|
}
|
||||||
|
|
||||||
|
TER
|
||||||
|
Taker::cross (Offer const& offer)
|
||||||
|
{
|
||||||
|
assert (!done ());
|
||||||
|
Amounts limit (offer.amount());
|
||||||
|
if (m_options.sell)
|
||||||
|
limit = offer.quality().ceil_in (limit, m_remain.in);
|
||||||
|
else
|
||||||
|
limit = offer.quality ().ceil_out (limit, m_remain.out);
|
||||||
|
|
||||||
|
assert (limit.out <= offer.amount().out);
|
||||||
|
assert (limit.in <= offer.amount().in);
|
||||||
|
|
||||||
|
Amounts const amount (flow (limit, offer, account ()));
|
||||||
|
m_remain.out -= amount.out;
|
||||||
|
m_remain.in -= amount.in;
|
||||||
|
assert (m_remain.in >= zero);
|
||||||
|
return fill (offer, amount);
|
||||||
|
}
|
||||||
|
|
||||||
|
TER
|
||||||
|
Taker::cross (Offer const& leg1, Offer const& leg2)
|
||||||
|
{
|
||||||
|
assert (!done ());
|
||||||
|
|
||||||
|
assert (leg1.amount ().out.isNative ());
|
||||||
|
assert (leg2.amount ().in.isNative ());
|
||||||
|
|
||||||
|
Amounts amount1 (leg1.amount());
|
||||||
|
Amounts amount2 (leg2.amount());
|
||||||
|
|
||||||
|
if (m_options.sell)
|
||||||
|
amount1 = leg1.quality().ceil_in (amount1, m_remain.in);
|
||||||
|
else
|
||||||
|
amount2 = leg2.quality().ceil_out (amount2, m_remain.out);
|
||||||
|
|
||||||
|
if (amount1.out <= amount2.in)
|
||||||
|
amount2 = leg2.quality().ceil_in (amount2, amount1.out);
|
||||||
|
else
|
||||||
|
amount1 = leg1.quality().ceil_out (amount1, amount2.in);
|
||||||
|
|
||||||
|
assert (amount1.out == amount2.in);
|
||||||
|
|
||||||
|
// As written, flow can't handle a 3-party transfer, but this works for
|
||||||
|
// us because the output of leg1 and the input leg2 are XRP.
|
||||||
|
Amounts flow1 (flow (amount1, leg1, m_account));
|
||||||
|
|
||||||
|
amount2 = leg2.quality().ceil_in (amount2, flow1.out);
|
||||||
|
|
||||||
|
Amounts flow2 (flow (amount2, leg2, m_account));
|
||||||
|
|
||||||
|
m_remain.out -= amount2.out;
|
||||||
|
m_remain.in -= amount1.in;
|
||||||
|
|
||||||
|
return fill (leg1, flow1, leg2, flow2);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1384,8 +1384,8 @@ private:
|
|||||||
return resultSuccess;
|
return resultSuccess;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isTefFailure (result) || isTemMalformed
|
if (isTefFailure (result) || isTemMalformed (result) ||
|
||||||
(result) || isTelLocal (result))
|
isTelLocal (result))
|
||||||
{
|
{
|
||||||
// failure
|
// failure
|
||||||
WriteLog (lsDEBUG, LedgerConsensus)
|
WriteLog (lsDEBUG, LedgerConsensus)
|
||||||
|
|||||||
@@ -21,6 +21,11 @@
|
|||||||
|
|
||||||
#include "ripple_app.h"
|
#include "ripple_app.h"
|
||||||
|
|
||||||
|
#include "book/impl/Taker.cpp"
|
||||||
|
#include "book/impl/BookTip.cpp"
|
||||||
|
#include "book/impl/OfferStream.cpp"
|
||||||
|
#include "book/impl/Quality.cpp"
|
||||||
|
|
||||||
#include "transactors/Transactor.cpp"
|
#include "transactors/Transactor.cpp"
|
||||||
|
|
||||||
#include "transactors/Change.cpp"
|
#include "transactors/Change.cpp"
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
|
|
||||||
namespace ripple {
|
namespace ripple {
|
||||||
|
|
||||||
TER WalletAddTransactor::doApply ()
|
TER AddWallet::doApply ()
|
||||||
{
|
{
|
||||||
std::uint32_t const uTxFlags = mTxn.getFlags ();
|
std::uint32_t const uTxFlags = mTxn.getFlags ();
|
||||||
|
|
||||||
|
|||||||
@@ -22,20 +22,20 @@
|
|||||||
|
|
||||||
namespace ripple {
|
namespace ripple {
|
||||||
|
|
||||||
class WalletAddTransactorLog;
|
class AddWalletLog;
|
||||||
|
|
||||||
template <>
|
template <>
|
||||||
char const*
|
char const*
|
||||||
LogPartition::getPartitionName <WalletAddTransactorLog> ()
|
LogPartition::getPartitionName <AddWalletLog> ()
|
||||||
{
|
{
|
||||||
return "Tx/WalletAdd";
|
return "Tx/WalletAdd";
|
||||||
}
|
}
|
||||||
|
|
||||||
class WalletAddTransactor
|
class AddWallet
|
||||||
: public Transactor
|
: public Transactor
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
WalletAddTransactor (
|
AddWallet (
|
||||||
SerializedTransaction const& txn,
|
SerializedTransaction const& txn,
|
||||||
TransactionEngineParams params,
|
TransactionEngineParams params,
|
||||||
TransactionEngine* engine)
|
TransactionEngine* engine)
|
||||||
@@ -43,13 +43,23 @@ public:
|
|||||||
txn,
|
txn,
|
||||||
params,
|
params,
|
||||||
engine,
|
engine,
|
||||||
LogPartition::getJournal <WalletAddTransactorLog> ())
|
LogPartition::getJournal <AddWalletLog> ())
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
TER doApply ();
|
TER doApply ();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
inline
|
||||||
|
std::unique_ptr <Transactor>
|
||||||
|
make_AddWallet (
|
||||||
|
SerializedTransaction const& txn,
|
||||||
|
TransactionEngineParams params,
|
||||||
|
TransactionEngine* engine)
|
||||||
|
{
|
||||||
|
return std::make_unique <AddWallet> (txn, params, engine);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
namespace ripple {
|
namespace ripple {
|
||||||
|
|
||||||
|
|
||||||
TER OfferCancelTransactor::doApply ()
|
TER CancelOffer::doApply ()
|
||||||
{
|
{
|
||||||
std::uint32_t const uOfferSequence = mTxn.getFieldU32 (sfOfferSequence);
|
std::uint32_t const uOfferSequence = mTxn.getFieldU32 (sfOfferSequence);
|
||||||
std::uint32_t const uAccountSequenceNext = mTxnAccount->getFieldU32 (sfSequence);
|
std::uint32_t const uAccountSequenceNext = mTxnAccount->getFieldU32 (sfSequence);
|
||||||
|
|||||||
@@ -22,20 +22,20 @@
|
|||||||
|
|
||||||
namespace ripple {
|
namespace ripple {
|
||||||
|
|
||||||
class OfferCancelTransactorLog;
|
class CancelOfferLog;
|
||||||
|
|
||||||
template <>
|
template <>
|
||||||
char const*
|
char const*
|
||||||
LogPartition::getPartitionName <OfferCancelTransactorLog> ()
|
LogPartition::getPartitionName <CancelOfferLog> ()
|
||||||
{
|
{
|
||||||
return "Tx/OfferCancel";
|
return "Tx/OfferCancel";
|
||||||
}
|
}
|
||||||
|
|
||||||
class OfferCancelTransactor
|
class CancelOffer
|
||||||
: public Transactor
|
: public Transactor
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
OfferCancelTransactor (
|
CancelOffer (
|
||||||
SerializedTransaction const& txn,
|
SerializedTransaction const& txn,
|
||||||
TransactionEngineParams params,
|
TransactionEngineParams params,
|
||||||
TransactionEngine* engine)
|
TransactionEngine* engine)
|
||||||
@@ -43,7 +43,7 @@ public:
|
|||||||
txn,
|
txn,
|
||||||
params,
|
params,
|
||||||
engine,
|
engine,
|
||||||
LogPartition::getJournal <OfferCancelTransactorLog> ())
|
LogPartition::getJournal <CancelOfferLog> ())
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -51,6 +51,16 @@ public:
|
|||||||
TER doApply () override;
|
TER doApply () override;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
inline
|
||||||
|
std::unique_ptr <Transactor>
|
||||||
|
make_CancelOffer (
|
||||||
|
SerializedTransaction const& txn,
|
||||||
|
TransactionEngineParams params,
|
||||||
|
TransactionEngine* engine)
|
||||||
|
{
|
||||||
|
return std::make_unique <CancelOffer> (txn, params, engine);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
|
|
||||||
namespace ripple {
|
namespace ripple {
|
||||||
|
|
||||||
TER ChangeTransactor::doApply ()
|
TER Change::doApply ()
|
||||||
{
|
{
|
||||||
if (mTxn.getTxnType () == ttAMENDMENT)
|
if (mTxn.getTxnType () == ttAMENDMENT)
|
||||||
return applyAmendment ();
|
return applyAmendment ();
|
||||||
@@ -30,7 +30,7 @@ TER ChangeTransactor::doApply ()
|
|||||||
return temUNKNOWN;
|
return temUNKNOWN;
|
||||||
}
|
}
|
||||||
|
|
||||||
TER ChangeTransactor::checkSig ()
|
TER Change::checkSig ()
|
||||||
{
|
{
|
||||||
if (mTxn.getFieldAccount160 (sfAccount).isNonZero ())
|
if (mTxn.getFieldAccount160 (sfAccount).isNonZero ())
|
||||||
{
|
{
|
||||||
@@ -47,7 +47,7 @@ TER ChangeTransactor::checkSig ()
|
|||||||
return tesSUCCESS;
|
return tesSUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
TER ChangeTransactor::checkSeq ()
|
TER Change::checkSeq ()
|
||||||
{
|
{
|
||||||
if ((mTxn.getSequence () != 0) || mTxn.isFieldPresent (sfPreviousTxnID))
|
if ((mTxn.getSequence () != 0) || mTxn.isFieldPresent (sfPreviousTxnID))
|
||||||
{
|
{
|
||||||
@@ -58,7 +58,7 @@ TER ChangeTransactor::checkSeq ()
|
|||||||
return tesSUCCESS;
|
return tesSUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
TER ChangeTransactor::payFee ()
|
TER Change::payFee ()
|
||||||
{
|
{
|
||||||
if (mTxn.getTransactionFee () != STAmount ())
|
if (mTxn.getTransactionFee () != STAmount ())
|
||||||
{
|
{
|
||||||
@@ -69,7 +69,7 @@ TER ChangeTransactor::payFee ()
|
|||||||
return tesSUCCESS;
|
return tesSUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
TER ChangeTransactor::preCheck ()
|
TER Change::preCheck ()
|
||||||
{
|
{
|
||||||
mTxnAccountID = mTxn.getSourceAccount ().getAccountID ();
|
mTxnAccountID = mTxn.getSourceAccount ().getAccountID ();
|
||||||
|
|
||||||
@@ -89,7 +89,7 @@ TER ChangeTransactor::preCheck ()
|
|||||||
return tesSUCCESS;
|
return tesSUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
TER ChangeTransactor::applyAmendment ()
|
TER Change::applyAmendment ()
|
||||||
{
|
{
|
||||||
uint256 amendment (mTxn.getFieldH256 (sfAmendment));
|
uint256 amendment (mTxn.getFieldH256 (sfAmendment));
|
||||||
|
|
||||||
@@ -119,7 +119,7 @@ TER ChangeTransactor::applyAmendment ()
|
|||||||
return tesSUCCESS;
|
return tesSUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
TER ChangeTransactor::applyFee ()
|
TER Change::applyFee ()
|
||||||
{
|
{
|
||||||
|
|
||||||
SLE::pointer feeObject = mEngine->entryCache (
|
SLE::pointer feeObject = mEngine->entryCache (
|
||||||
|
|||||||
@@ -22,20 +22,20 @@
|
|||||||
|
|
||||||
namespace ripple {
|
namespace ripple {
|
||||||
|
|
||||||
class ChangeTransactorLog;
|
class ChangeLog;
|
||||||
|
|
||||||
template <>
|
template <>
|
||||||
char const*
|
char const*
|
||||||
LogPartition::getPartitionName <ChangeTransactorLog> ()
|
LogPartition::getPartitionName <ChangeLog> ()
|
||||||
{
|
{
|
||||||
return "Tx/Change";
|
return "Tx/Change";
|
||||||
}
|
}
|
||||||
|
|
||||||
class ChangeTransactor
|
class Change
|
||||||
: public Transactor
|
: public Transactor
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
ChangeTransactor (
|
Change (
|
||||||
SerializedTransaction const& txn,
|
SerializedTransaction const& txn,
|
||||||
TransactionEngineParams params,
|
TransactionEngineParams params,
|
||||||
TransactionEngine* engine)
|
TransactionEngine* engine)
|
||||||
@@ -43,7 +43,7 @@ public:
|
|||||||
txn,
|
txn,
|
||||||
params,
|
params,
|
||||||
engine,
|
engine,
|
||||||
LogPartition::getJournal <ChangeTransactorLog> ())
|
LogPartition::getJournal <ChangeLog> ())
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -64,6 +64,16 @@ private:
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
inline
|
||||||
|
std::unique_ptr <Transactor>
|
||||||
|
make_Change (
|
||||||
|
SerializedTransaction const& txn,
|
||||||
|
TransactionEngineParams params,
|
||||||
|
TransactionEngine* engine)
|
||||||
|
{
|
||||||
|
return std::make_unique <Change> (txn, params, engine);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -26,10 +26,11 @@
|
|||||||
|
|
||||||
#include "../book/OfferStream.h"
|
#include "../book/OfferStream.h"
|
||||||
#include "../book/Taker.h"
|
#include "../book/Taker.h"
|
||||||
|
#include "../book/Types.h"
|
||||||
|
|
||||||
namespace ripple {
|
namespace ripple {
|
||||||
|
|
||||||
OfferCreateTransactor::OfferCreateTransactor (
|
CreateOffer::CreateOffer (
|
||||||
SerializedTransaction const& txn,
|
SerializedTransaction const& txn,
|
||||||
TransactionEngineParams params,
|
TransactionEngineParams params,
|
||||||
TransactionEngine* engine)
|
TransactionEngine* engine)
|
||||||
@@ -37,31 +38,495 @@ OfferCreateTransactor::OfferCreateTransactor (
|
|||||||
txn,
|
txn,
|
||||||
params,
|
params,
|
||||||
engine,
|
engine,
|
||||||
LogPartition::getJournal <OfferCreateTransactorLog> ())
|
LogPartition::getJournal <CreateOfferLog> ())
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr <Transactor> make_OfferCreateTransactor (
|
TER
|
||||||
|
CreateOffer::checkAcceptAsset(core::AssetRef asset) const
|
||||||
|
{
|
||||||
|
/* Only valid for custom currencies */
|
||||||
|
assert (!asset.is_xrp ());
|
||||||
|
|
||||||
|
SLE::pointer const issuerAccount = mEngine->entryCache (
|
||||||
|
ltACCOUNT_ROOT, Ledger::getAccountRootIndex (asset.issuer));
|
||||||
|
|
||||||
|
if (!issuerAccount)
|
||||||
|
{
|
||||||
|
if (m_journal.warning) m_journal.warning <<
|
||||||
|
"delay: can't receive IOUs from non-existent issuer: " <<
|
||||||
|
RippleAddress::createHumanAccountID (asset.issuer);
|
||||||
|
|
||||||
|
return is_bit_set (mParams, tapRETRY)
|
||||||
|
? terNO_ACCOUNT
|
||||||
|
: tecNO_ISSUER;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_bit_set (issuerAccount->getFieldU32 (sfFlags), lsfRequireAuth))
|
||||||
|
{
|
||||||
|
SLE::pointer const trustLine (mEngine->entryCache (
|
||||||
|
ltRIPPLE_STATE, Ledger::getRippleStateIndex (
|
||||||
|
mTxnAccountID, asset.issuer, asset.currency)));
|
||||||
|
|
||||||
|
if (!trustLine)
|
||||||
|
{
|
||||||
|
return is_bit_set (mParams, tapRETRY)
|
||||||
|
? terNO_LINE
|
||||||
|
: tecNO_LINE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Entries have a canonical representation, determined by a
|
||||||
|
// lexicographical "greater than" comparison employing strict weak
|
||||||
|
// ordering. Determine which entry we need to access.
|
||||||
|
bool const canonical_gt (mTxnAccountID > asset.issuer);
|
||||||
|
|
||||||
|
bool const need_auth (is_bit_set (
|
||||||
|
trustLine->getFieldU32 (sfFlags),
|
||||||
|
(canonical_gt ? lsfLowAuth : lsfHighAuth)));
|
||||||
|
|
||||||
|
if (need_auth)
|
||||||
|
{
|
||||||
|
if (m_journal.debug) m_journal.debug <<
|
||||||
|
"delay: can't receive IOUs from issuer without auth.";
|
||||||
|
|
||||||
|
return is_bit_set (mParams, tapRETRY)
|
||||||
|
? terNO_AUTH
|
||||||
|
: tecNO_AUTH;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return tesSUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
TER
|
||||||
|
CreateOffer::doApply ()
|
||||||
|
{
|
||||||
|
if (m_journal.debug) m_journal.debug <<
|
||||||
|
"OfferCreate> " << mTxn.getJson (0);
|
||||||
|
|
||||||
|
std::uint32_t const uTxFlags = mTxn.getFlags ();
|
||||||
|
|
||||||
|
bool const bPassive = is_bit_set (uTxFlags, tfPassive);
|
||||||
|
bool const bImmediateOrCancel = is_bit_set (uTxFlags, tfImmediateOrCancel);
|
||||||
|
bool const bFillOrKill = is_bit_set (uTxFlags, tfFillOrKill);
|
||||||
|
bool const bSell = is_bit_set (uTxFlags, tfSell);
|
||||||
|
|
||||||
|
STAmount saTakerPays = mTxn.getFieldAmount (sfTakerPays);
|
||||||
|
STAmount saTakerGets = mTxn.getFieldAmount (sfTakerGets);
|
||||||
|
|
||||||
|
if (!saTakerPays.isLegalNet () || !saTakerGets.isLegalNet ())
|
||||||
|
return temBAD_AMOUNT;
|
||||||
|
|
||||||
|
uint160 const& uPaysIssuerID = saTakerPays.getIssuer ();
|
||||||
|
uint160 const& uPaysCurrency = saTakerPays.getCurrency ();
|
||||||
|
|
||||||
|
uint160 const& uGetsIssuerID = saTakerGets.getIssuer ();
|
||||||
|
uint160 const& uGetsCurrency = saTakerGets.getCurrency ();
|
||||||
|
|
||||||
|
bool const bHaveExpiration (mTxn.isFieldPresent (sfExpiration));
|
||||||
|
bool const bHaveCancel (mTxn.isFieldPresent (sfOfferSequence));
|
||||||
|
|
||||||
|
std::uint32_t const uExpiration = mTxn.getFieldU32 (sfExpiration);
|
||||||
|
std::uint32_t const uCancelSequence = mTxn.getFieldU32 (sfOfferSequence);
|
||||||
|
|
||||||
|
// FIXME understand why we use SequenceNext instead of current transaction
|
||||||
|
// sequence to determine the transaction. Why is the offer seuqnce
|
||||||
|
// number insufficient?
|
||||||
|
|
||||||
|
std::uint32_t const uAccountSequenceNext = mTxnAccount->getFieldU32 (sfSequence);
|
||||||
|
std::uint32_t const uSequence = mTxn.getSequence ();
|
||||||
|
|
||||||
|
const uint256 uLedgerIndex = Ledger::getOfferIndex (mTxnAccountID, uSequence);
|
||||||
|
|
||||||
|
if (m_journal.debug)
|
||||||
|
{
|
||||||
|
m_journal.debug <<
|
||||||
|
"Creating offer node: " << to_string (uLedgerIndex) <<
|
||||||
|
" uSequence=" << uSequence;
|
||||||
|
|
||||||
|
if (bImmediateOrCancel)
|
||||||
|
m_journal.debug << "Transaction: IoC set.";
|
||||||
|
|
||||||
|
if (bFillOrKill)
|
||||||
|
m_journal.debug << "Transaction: FoK set.";
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is the original rate of this offer, and is the rate at which it will
|
||||||
|
// be placed, even if crossing offers change the amounts.
|
||||||
|
std::uint64_t const uRate = STAmount::getRate (saTakerGets, saTakerPays);
|
||||||
|
|
||||||
|
TER terResult (tesSUCCESS);
|
||||||
|
uint256 uDirectory;
|
||||||
|
std::uint64_t uOwnerNode;
|
||||||
|
std::uint64_t uBookNode;
|
||||||
|
|
||||||
|
// This is the ledger view that we work against. Transactions are applied
|
||||||
|
// as we go on processing transactions.
|
||||||
|
core::LedgerView& view (mEngine->view ());
|
||||||
|
|
||||||
|
// This is a checkpoint with just the fees paid. If something goes wrong
|
||||||
|
// with this transaction, we roll back to this ledger.
|
||||||
|
core::LedgerView view_checkpoint (view);
|
||||||
|
|
||||||
|
view.bumpSeq (); // Begin ledger variance.
|
||||||
|
|
||||||
|
SLE::pointer sleCreator = mEngine->entryCache (
|
||||||
|
ltACCOUNT_ROOT, Ledger::getAccountRootIndex (mTxnAccountID));
|
||||||
|
|
||||||
|
if (uTxFlags & tfOfferCreateMask)
|
||||||
|
{
|
||||||
|
if (m_journal.debug) m_journal.debug <<
|
||||||
|
"Malformed transaction: Invalid flags set.";
|
||||||
|
|
||||||
|
terResult = temINVALID_FLAG;
|
||||||
|
}
|
||||||
|
else if (bImmediateOrCancel && bFillOrKill)
|
||||||
|
{
|
||||||
|
if (m_journal.debug) m_journal.debug <<
|
||||||
|
"Malformed transaction: both IoC and FoK set.";
|
||||||
|
|
||||||
|
terResult = temINVALID_FLAG;
|
||||||
|
}
|
||||||
|
else if (bHaveExpiration && !uExpiration)
|
||||||
|
{
|
||||||
|
m_journal.warning <<
|
||||||
|
"Malformed offer: bad expiration";
|
||||||
|
|
||||||
|
terResult = temBAD_EXPIRATION;
|
||||||
|
}
|
||||||
|
else if (saTakerPays.isNative () && saTakerGets.isNative ())
|
||||||
|
{
|
||||||
|
m_journal.warning <<
|
||||||
|
"Malformed offer: XRP for XRP";
|
||||||
|
|
||||||
|
terResult = temBAD_OFFER;
|
||||||
|
}
|
||||||
|
else if (saTakerPays <= zero || saTakerGets <= zero)
|
||||||
|
{
|
||||||
|
m_journal.warning <<
|
||||||
|
"Malformed offer: bad amount";
|
||||||
|
|
||||||
|
terResult = temBAD_OFFER;
|
||||||
|
}
|
||||||
|
else if (uPaysCurrency == uGetsCurrency && uPaysIssuerID == uGetsIssuerID)
|
||||||
|
{
|
||||||
|
m_journal.warning <<
|
||||||
|
"Malformed offer: redundant offer";
|
||||||
|
|
||||||
|
terResult = temREDUNDANT;
|
||||||
|
}
|
||||||
|
// We don't allow a non-native currency to use the currency code XRP.
|
||||||
|
else if (CURRENCY_BAD == uPaysCurrency || CURRENCY_BAD == uGetsCurrency)
|
||||||
|
{
|
||||||
|
m_journal.warning <<
|
||||||
|
"Malformed offer: Bad currency.";
|
||||||
|
|
||||||
|
terResult = temBAD_CURRENCY;
|
||||||
|
}
|
||||||
|
else if (saTakerPays.isNative () != !uPaysIssuerID ||
|
||||||
|
saTakerGets.isNative () != !uGetsIssuerID)
|
||||||
|
{
|
||||||
|
m_journal.warning <<
|
||||||
|
"Malformed offer: bad issuer";
|
||||||
|
|
||||||
|
terResult = temBAD_ISSUER;
|
||||||
|
}
|
||||||
|
else if (view.accountFunds (mTxnAccountID, saTakerGets) <= zero)
|
||||||
|
{
|
||||||
|
m_journal.warning <<
|
||||||
|
"delay: Offers must be at least partially funded.";
|
||||||
|
|
||||||
|
terResult = tecUNFUNDED_OFFER;
|
||||||
|
}
|
||||||
|
// This can probably be simplified to make sure that you cancel sequences
|
||||||
|
// before the transaction sequence number.
|
||||||
|
else if (bHaveCancel && (!uCancelSequence || uAccountSequenceNext - 1 <= uCancelSequence))
|
||||||
|
{
|
||||||
|
if (m_journal.debug) m_journal.debug <<
|
||||||
|
"uAccountSequenceNext=" << uAccountSequenceNext <<
|
||||||
|
" uOfferSequence=" << uCancelSequence;
|
||||||
|
|
||||||
|
terResult = temBAD_SEQUENCE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (terResult != tesSUCCESS)
|
||||||
|
{
|
||||||
|
if (m_journal.debug) m_journal.debug <<
|
||||||
|
"final terResult=" << transToken (terResult);
|
||||||
|
|
||||||
|
return terResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process a cancellation request that's passed along with an offer.
|
||||||
|
if ((terResult == tesSUCCESS) && bHaveCancel)
|
||||||
|
{
|
||||||
|
uint256 const uCancelIndex (
|
||||||
|
Ledger::getOfferIndex (mTxnAccountID, uCancelSequence));
|
||||||
|
SLE::pointer sleCancel = mEngine->entryCache (ltOFFER, uCancelIndex);
|
||||||
|
|
||||||
|
// It's not an error to not find the offer to cancel: it might have
|
||||||
|
// been consumed or removed as we are processing.
|
||||||
|
if (sleCancel)
|
||||||
|
{
|
||||||
|
m_journal.warning <<
|
||||||
|
"Cancelling order with sequence " << uCancelSequence;
|
||||||
|
|
||||||
|
terResult = view.offerDelete (sleCancel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Expiration is defined in terms of the close time of the parent ledger,
|
||||||
|
// because we definitively know the time that it closed but we do not
|
||||||
|
// know the closing time of the ledger that is under construction.
|
||||||
|
if (bHaveExpiration &&
|
||||||
|
(mEngine->getLedger ()->getParentCloseTimeNC () >= uExpiration))
|
||||||
|
{
|
||||||
|
return tesSUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure that we are authorized to hold what the taker will pay us.
|
||||||
|
if (terResult == tesSUCCESS && !saTakerPays.isNative ())
|
||||||
|
terResult = checkAcceptAsset (core::Asset (uPaysCurrency, uPaysIssuerID));
|
||||||
|
|
||||||
|
bool crossed = false;
|
||||||
|
bool const bOpenLedger = is_bit_set (mParams, tapOPEN_LEDGER);
|
||||||
|
|
||||||
|
if (terResult == tesSUCCESS)
|
||||||
|
{
|
||||||
|
// We reverse gets and pays because during offer crossing we are taking.
|
||||||
|
core::Amounts const taker_amount (saTakerGets, saTakerPays);
|
||||||
|
|
||||||
|
// The amount of the offer that we will need to place, after we finish
|
||||||
|
// offer crossing processing. It may be equal to the original amount,
|
||||||
|
// empty (fully crossed), or something in-between.
|
||||||
|
core::Amounts place_offer;
|
||||||
|
|
||||||
|
std::tie(terResult, place_offer) = crossOffers (view, taker_amount);
|
||||||
|
|
||||||
|
if (terResult == tecFAILED_PROCESSING && bOpenLedger)
|
||||||
|
terResult = telFAILED_PROCESSING;
|
||||||
|
|
||||||
|
if (terResult == tesSUCCESS)
|
||||||
|
{
|
||||||
|
// We now need to reduce the offer by the cross flow. We reverse
|
||||||
|
// in and out here, since during crossing we were takers.
|
||||||
|
assert (saTakerPays.getCurrency () == place_offer.out.getCurrency ());
|
||||||
|
assert (saTakerPays.getIssuer () == place_offer.out.getIssuer ());
|
||||||
|
assert (saTakerGets.getCurrency () == place_offer.in.getCurrency ());
|
||||||
|
assert (saTakerGets.getIssuer () == place_offer.in.getIssuer ());
|
||||||
|
|
||||||
|
if (taker_amount != place_offer)
|
||||||
|
crossed = true;
|
||||||
|
|
||||||
|
if (m_journal.debug)
|
||||||
|
{
|
||||||
|
m_journal.debug << "Offer Crossing: " << transToken (terResult);
|
||||||
|
|
||||||
|
if (terResult == tesSUCCESS)
|
||||||
|
{
|
||||||
|
m_journal.debug <<
|
||||||
|
" takerPays: " << saTakerPays.getFullText () <<
|
||||||
|
" -> " << place_offer.out.getFullText ();
|
||||||
|
m_journal.debug <<
|
||||||
|
" takerGets: " << saTakerGets.getFullText () <<
|
||||||
|
" -> " << place_offer.in.getFullText ();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
saTakerPays = place_offer.out;
|
||||||
|
saTakerGets = place_offer.in;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (terResult != tesSUCCESS)
|
||||||
|
{
|
||||||
|
m_journal.debug <<
|
||||||
|
"final terResult=" << transToken (terResult);
|
||||||
|
|
||||||
|
return terResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_journal.debug)
|
||||||
|
{
|
||||||
|
m_journal.debug <<
|
||||||
|
"takeOffers: saTakerPays=" <<saTakerPays.getFullText ();
|
||||||
|
m_journal.debug <<
|
||||||
|
"takeOffers: saTakerGets=" << saTakerGets.getFullText ();
|
||||||
|
m_journal.debug <<
|
||||||
|
"takeOffers: mTxnAccountID=" <<
|
||||||
|
RippleAddress::createHumanAccountID (mTxnAccountID);
|
||||||
|
m_journal.debug <<
|
||||||
|
"takeOffers: FUNDS=" <<
|
||||||
|
view.accountFunds (mTxnAccountID, saTakerGets).getFullText ();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bFillOrKill && (saTakerPays != zero || saTakerGets != zero))
|
||||||
|
{
|
||||||
|
// Fill or kill and have leftovers.
|
||||||
|
view.swapWith (view_checkpoint); // Restore with just fees paid.
|
||||||
|
return tesSUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
// What the reserve would be if this offer was placed.
|
||||||
|
auto const accountReserve (mEngine->getLedger ()->getReserve (
|
||||||
|
sleCreator->getFieldU32 (sfOwnerCount) + 1));
|
||||||
|
|
||||||
|
if (saTakerPays <= zero || // Wants nothing more.
|
||||||
|
saTakerGets <= zero || // Offering nothing more.
|
||||||
|
bImmediateOrCancel) // Do not persist.
|
||||||
|
{
|
||||||
|
// Complete as is.
|
||||||
|
nothing ();
|
||||||
|
}
|
||||||
|
else if (mPriorBalance.getNValue () < accountReserve)
|
||||||
|
{
|
||||||
|
// If we are here, the signing account had an insufficient reserve
|
||||||
|
// *prior* to our processing. We use the prior balance to simplify
|
||||||
|
// client writing and make the user experience better.
|
||||||
|
|
||||||
|
if (bOpenLedger) // Ledger is not final, can vote no.
|
||||||
|
{
|
||||||
|
// Hope for more reserve to come in or more offers to consume. If we
|
||||||
|
// specified a local error this transaction will not be retried, so
|
||||||
|
// specify a tec to distribute the transaction and allow it to be
|
||||||
|
// retried. In particular, it may have been successful to a
|
||||||
|
// degree (partially filled) and if it hasn't, it might succeed.
|
||||||
|
terResult = tecINSUF_RESERVE_OFFER;
|
||||||
|
}
|
||||||
|
else if (!crossed)
|
||||||
|
{
|
||||||
|
// Ledger is final, insufficent reserve to create offer, processed
|
||||||
|
// nothing.
|
||||||
|
terResult = tecINSUF_RESERVE_OFFER;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Ledger is final, insufficent reserve to create offer, processed
|
||||||
|
// something.
|
||||||
|
|
||||||
|
// Consider the offer unfunded. Treat as tesSUCCESS.
|
||||||
|
nothing ();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// We need to place the remainder of the offer into its order book.
|
||||||
|
if (m_journal.debug) m_journal.debug <<
|
||||||
|
"offer not fully consumed:" <<
|
||||||
|
" saTakerPays=" << saTakerPays.getFullText () <<
|
||||||
|
" saTakerGets=" << saTakerGets.getFullText ();
|
||||||
|
|
||||||
|
// Add offer to owner's directory.
|
||||||
|
terResult = view.dirAdd (uOwnerNode,
|
||||||
|
Ledger::getOwnerDirIndex (mTxnAccountID), uLedgerIndex,
|
||||||
|
std::bind (&Ledger::ownerDirDescriber, std::placeholders::_1,
|
||||||
|
std::placeholders::_2, mTxnAccountID));
|
||||||
|
|
||||||
|
if (tesSUCCESS == terResult)
|
||||||
|
{
|
||||||
|
// Update owner count.
|
||||||
|
view.ownerCountAdjust (mTxnAccountID, 1, sleCreator);
|
||||||
|
|
||||||
|
uint256 uBookBase = Ledger::getBookBase (
|
||||||
|
uPaysCurrency, uPaysIssuerID,
|
||||||
|
uGetsCurrency, uGetsIssuerID);
|
||||||
|
|
||||||
|
if (m_journal.debug) m_journal.debug <<
|
||||||
|
"adding to book: " << to_string (uBookBase) <<
|
||||||
|
" : " << saTakerPays.getHumanCurrency () <<
|
||||||
|
"/" << RippleAddress::createHumanAccountID (saTakerPays.getIssuer ()) <<
|
||||||
|
" -> " << saTakerGets.getHumanCurrency () <<
|
||||||
|
"/" << RippleAddress::createHumanAccountID (saTakerGets.getIssuer ());
|
||||||
|
|
||||||
|
// We use the original rate to place the offer.
|
||||||
|
uDirectory = Ledger::getQualityIndex (uBookBase, uRate);
|
||||||
|
|
||||||
|
// Add offer to order book.
|
||||||
|
terResult = view.dirAdd (uBookNode, uDirectory, uLedgerIndex,
|
||||||
|
std::bind (&Ledger::qualityDirDescriber, std::placeholders::_1,
|
||||||
|
std::placeholders::_2, saTakerPays.getCurrency (),
|
||||||
|
uPaysIssuerID, saTakerGets.getCurrency (),
|
||||||
|
uGetsIssuerID, uRate));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tesSUCCESS == terResult)
|
||||||
|
{
|
||||||
|
if (m_journal.debug)
|
||||||
|
{
|
||||||
|
m_journal.debug <<
|
||||||
|
"sfAccount=" <<
|
||||||
|
RippleAddress::createHumanAccountID (mTxnAccountID);
|
||||||
|
m_journal.debug <<
|
||||||
|
"uPaysIssuerID=" <<
|
||||||
|
RippleAddress::createHumanAccountID (uPaysIssuerID);
|
||||||
|
m_journal.debug <<
|
||||||
|
"uGetsIssuerID=" <<
|
||||||
|
RippleAddress::createHumanAccountID (uGetsIssuerID);
|
||||||
|
m_journal.debug <<
|
||||||
|
"saTakerPays.isNative()=" <<
|
||||||
|
saTakerPays.isNative ();
|
||||||
|
m_journal.debug <<
|
||||||
|
"saTakerGets.isNative()=" <<
|
||||||
|
saTakerGets.isNative ();
|
||||||
|
m_journal.debug <<
|
||||||
|
"uPaysCurrency=" <<
|
||||||
|
saTakerPays.getHumanCurrency ();
|
||||||
|
m_journal.debug <<
|
||||||
|
"uGetsCurrency=" <<
|
||||||
|
saTakerGets.getHumanCurrency ();
|
||||||
|
}
|
||||||
|
|
||||||
|
SLE::pointer sleOffer (mEngine->entryCreate (ltOFFER, uLedgerIndex));
|
||||||
|
|
||||||
|
sleOffer->setFieldAccount (sfAccount, mTxnAccountID);
|
||||||
|
sleOffer->setFieldU32 (sfSequence, uSequence);
|
||||||
|
sleOffer->setFieldH256 (sfBookDirectory, uDirectory);
|
||||||
|
sleOffer->setFieldAmount (sfTakerPays, saTakerPays);
|
||||||
|
sleOffer->setFieldAmount (sfTakerGets, saTakerGets);
|
||||||
|
sleOffer->setFieldU64 (sfOwnerNode, uOwnerNode);
|
||||||
|
sleOffer->setFieldU64 (sfBookNode, uBookNode);
|
||||||
|
|
||||||
|
if (uExpiration)
|
||||||
|
sleOffer->setFieldU32 (sfExpiration, uExpiration);
|
||||||
|
|
||||||
|
if (bPassive)
|
||||||
|
sleOffer->setFlag (lsfPassive);
|
||||||
|
|
||||||
|
if (bSell)
|
||||||
|
sleOffer->setFlag (lsfSell);
|
||||||
|
|
||||||
|
if (m_journal.debug) m_journal.debug <<
|
||||||
|
"final terResult=" << transToken (terResult) <<
|
||||||
|
" sleOffer=" << sleOffer->getJson (0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (terResult != tesSUCCESS)
|
||||||
|
{
|
||||||
|
m_journal.debug <<
|
||||||
|
"final terResult=" << transToken (terResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
return terResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr <Transactor> make_CreateOffer (
|
||||||
SerializedTransaction const& txn,
|
SerializedTransaction const& txn,
|
||||||
TransactionEngineParams params,
|
TransactionEngineParams params,
|
||||||
TransactionEngine* engine)
|
TransactionEngine* engine)
|
||||||
{
|
{
|
||||||
#if RIPPLE_USE_OLD_CREATE_TRANSACTOR
|
#if RIPPLE_USE_OLD_CREATE_TRANSACTOR
|
||||||
return std::make_unique <ClassicOfferCreateTransactor> (txn, params, engine);
|
return std::make_unique <CreateOfferLegacy> (txn, params, engine);
|
||||||
#else
|
#else
|
||||||
STAmount const& amount_in = txn.getFieldAmount (sfTakerPays);
|
STAmount const& amount_in = txn.getFieldAmount (sfTakerPays);
|
||||||
STAmount const& amount_out = txn.getFieldAmount (sfTakerGets);
|
STAmount const& amount_out = txn.getFieldAmount (sfTakerGets);
|
||||||
|
|
||||||
// Autobridging is only in effect when an offer does not involve XRP
|
// Autobridging is only in effect when an offer does not involve XRP
|
||||||
if (!amount_in.isNative() && !amount_out.isNative ())
|
if (!amount_in.isNative() && !amount_out.isNative ())
|
||||||
{
|
return std::make_unique <CreateOfferBridged> (txn, params, engine);
|
||||||
// no autobridging transactor exists yet - we create a regular, direct
|
|
||||||
// transactor for now.
|
|
||||||
return std::make_unique <DirectOfferCreateTransactor> (txn, params, engine);
|
|
||||||
}
|
|
||||||
|
|
||||||
return std::make_unique <DirectOfferCreateTransactor> (txn, params, engine);
|
return std::make_unique <CreateOfferDirect> (txn, params, engine);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -20,45 +20,53 @@
|
|||||||
#ifndef RIPPLE_TX_OFFERCREATE_H_INCLUDED
|
#ifndef RIPPLE_TX_OFFERCREATE_H_INCLUDED
|
||||||
#define RIPPLE_TX_OFFERCREATE_H_INCLUDED
|
#define RIPPLE_TX_OFFERCREATE_H_INCLUDED
|
||||||
|
|
||||||
|
#include "../book/Amounts.h"
|
||||||
|
#include "../book/Types.h"
|
||||||
|
|
||||||
#include "../../beast/beast/cxx14/memory.h"
|
#include "../../beast/beast/cxx14/memory.h"
|
||||||
|
|
||||||
namespace ripple {
|
namespace ripple {
|
||||||
|
|
||||||
class OfferCreateTransactorLog;
|
class CreateOfferLog;
|
||||||
|
|
||||||
template <>
|
template <>
|
||||||
char const*
|
char const*
|
||||||
LogPartition::getPartitionName <OfferCreateTransactorLog> ()
|
LogPartition::getPartitionName <CreateOfferLog> ()
|
||||||
{
|
{
|
||||||
return "Tx/OfferCreate";
|
return "Tx/OfferCreate";
|
||||||
}
|
}
|
||||||
|
|
||||||
class OfferCreateTransactor
|
class CreateOffer
|
||||||
: public Transactor
|
: public Transactor
|
||||||
{
|
{
|
||||||
private:
|
protected:
|
||||||
template <class T>
|
/** Determine if we are authorized to hold the asset we want to get */
|
||||||
static std::string
|
TER checkAcceptAsset(core::AssetRef asset) const;
|
||||||
get_compare_sign (T const& lhs, T const& rhs)
|
|
||||||
{
|
|
||||||
if (lhs > rhs)
|
|
||||||
return ">";
|
|
||||||
|
|
||||||
if (rhs > lhs)
|
/** Fill offer as much as possible by consuming offers already on the books.
|
||||||
return "<";
|
We adjusts account balances and charges fees on top to taker.
|
||||||
|
|
||||||
// If neither is bigger than the other, they must be equal
|
@param taker_amount.in How much the taker offers
|
||||||
return "=";
|
@param taker_amount.out How much the taker wants
|
||||||
}
|
|
||||||
|
@return result.first crossing operation success/failure indicator.
|
||||||
|
result.second amount of offer left unfilled - only meaningful
|
||||||
|
if result.first is tesSUCCESS.
|
||||||
|
*/
|
||||||
|
virtual std::pair<TER, core::Amounts> crossOffers (
|
||||||
|
core::LedgerView& view,
|
||||||
|
core::Amounts const& taker_amount) = 0;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
OfferCreateTransactor (
|
CreateOffer (
|
||||||
SerializedTransaction const& txn,
|
SerializedTransaction const& txn,
|
||||||
TransactionEngineParams params,
|
TransactionEngineParams params,
|
||||||
TransactionEngine* engine);
|
TransactionEngine* engine);
|
||||||
|
|
||||||
|
TER doApply () override;
|
||||||
};
|
};
|
||||||
|
|
||||||
std::unique_ptr <Transactor> make_OfferCreateTransactor (
|
std::unique_ptr <Transactor> make_CreateOffer (
|
||||||
SerializedTransaction const& txn,
|
SerializedTransaction const& txn,
|
||||||
TransactionEngineParams params,
|
TransactionEngineParams params,
|
||||||
TransactionEngine* engine);
|
TransactionEngine* engine);
|
||||||
|
|||||||
@@ -17,3 +17,175 @@
|
|||||||
*/
|
*/
|
||||||
//==============================================================================
|
//==============================================================================
|
||||||
|
|
||||||
|
#include "../book/OfferStream.h"
|
||||||
|
#include "../book/Taker.h"
|
||||||
|
#include "../book/Quality.h"
|
||||||
|
#include "../../beast/beast/streams/debug_ostream.h"
|
||||||
|
|
||||||
|
namespace ripple {
|
||||||
|
|
||||||
|
std::pair<TER, core::Amounts>
|
||||||
|
CreateOfferBridged::crossOffers (
|
||||||
|
core::LedgerView& view,
|
||||||
|
core::Amounts const& taker_amount)
|
||||||
|
{
|
||||||
|
assert (!taker_amount.in.isNative () && !taker_amount.out.isNative ());
|
||||||
|
|
||||||
|
if (taker_amount.in.isNative () || taker_amount.out.isNative ())
|
||||||
|
return std::make_pair (tefINTERNAL, core::Amounts ());
|
||||||
|
|
||||||
|
core::Clock::time_point const when (
|
||||||
|
mEngine->getLedger ()->getParentCloseTimeNC ());
|
||||||
|
|
||||||
|
core::Taker::Options const options (mTxn.getFlags());
|
||||||
|
|
||||||
|
core::LedgerView view_cancel (view.duplicate());
|
||||||
|
|
||||||
|
core::AssetRef const asset_in (
|
||||||
|
taker_amount.in.getCurrency(), taker_amount.in.getIssuer());
|
||||||
|
|
||||||
|
core::AssetRef const asset_out (
|
||||||
|
taker_amount.out.getCurrency(), taker_amount.out.getIssuer());
|
||||||
|
|
||||||
|
core::OfferStream offers_direct (view, view_cancel,
|
||||||
|
core::Book (asset_in, asset_out), when, m_journal);
|
||||||
|
|
||||||
|
core::OfferStream offers_leg1 (view, view_cancel,
|
||||||
|
core::Book (asset_in, xrp_asset ()), when, m_journal);
|
||||||
|
|
||||||
|
core::OfferStream offers_leg2 (view, view_cancel,
|
||||||
|
core::Book (xrp_asset (), asset_out), when, m_journal);
|
||||||
|
|
||||||
|
core::Taker taker (view, mTxnAccountID, taker_amount, options);
|
||||||
|
|
||||||
|
if (m_journal.debug) m_journal.debug <<
|
||||||
|
"process_order: " <<
|
||||||
|
(options.sell? "sell" : "buy") << " " <<
|
||||||
|
(options.passive? "passive" : "") << std::endl <<
|
||||||
|
" taker: " << taker.account() << std::endl <<
|
||||||
|
" balances: " <<
|
||||||
|
view.accountFunds (taker.account(), taker_amount.in) << ", " <<
|
||||||
|
view.accountFunds (taker.account(), taker_amount.out);
|
||||||
|
|
||||||
|
TER cross_result (tesSUCCESS);
|
||||||
|
|
||||||
|
bool have_bridged (
|
||||||
|
offers_leg1.step_account (taker.account()) &&
|
||||||
|
offers_leg2.step_account (taker.account()));
|
||||||
|
bool have_direct (offers_direct.step_account (taker.account()));
|
||||||
|
bool place_order (true);
|
||||||
|
|
||||||
|
while (have_direct || have_bridged)
|
||||||
|
{
|
||||||
|
core::Quality quality;
|
||||||
|
bool use_direct;
|
||||||
|
bool leg1_consumed(false);
|
||||||
|
bool leg2_consumed(false);
|
||||||
|
bool direct_consumed(false);
|
||||||
|
|
||||||
|
// Logic:
|
||||||
|
// We calculate the qualities of any direct and bridged offers at the
|
||||||
|
// tip of the order book, and choose the best one of the two.
|
||||||
|
|
||||||
|
if (have_direct)
|
||||||
|
{
|
||||||
|
core::Quality const direct_quality (offers_direct.tip ().quality ());
|
||||||
|
|
||||||
|
if (have_bridged)
|
||||||
|
{
|
||||||
|
core::Quality const bridged_quality (core::composed_quality (
|
||||||
|
offers_leg1.tip ().quality (),
|
||||||
|
offers_leg2.tip ().quality ()));
|
||||||
|
|
||||||
|
if (bridged_quality < direct_quality)
|
||||||
|
{
|
||||||
|
use_direct = true;
|
||||||
|
quality = direct_quality;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
use_direct = false;
|
||||||
|
quality = bridged_quality;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
use_direct = true;
|
||||||
|
quality = offers_direct.tip ().quality ();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
use_direct = false;
|
||||||
|
quality = core::composed_quality (
|
||||||
|
offers_leg1.tip ().quality (),
|
||||||
|
offers_leg2.tip ().quality ());
|
||||||
|
}
|
||||||
|
|
||||||
|
// We are always looking at the best quality available, so if we reject
|
||||||
|
// that, we know that we are done.
|
||||||
|
if (taker.reject(quality))
|
||||||
|
break;
|
||||||
|
|
||||||
|
if (use_direct)
|
||||||
|
{
|
||||||
|
if (m_journal.debug) m_journal.debug <<
|
||||||
|
" Offer: " << offers_direct.tip () << std::endl <<
|
||||||
|
" " << offers_direct.tip ().amount().in <<
|
||||||
|
" : " << offers_direct.tip ().amount ().out;
|
||||||
|
|
||||||
|
cross_result = taker.cross(offers_direct.tip ());
|
||||||
|
|
||||||
|
if (offers_direct.tip ().fully_consumed ())
|
||||||
|
{
|
||||||
|
direct_consumed = true;
|
||||||
|
have_direct = offers_direct.step_account (taker.account());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (m_journal.debug) m_journal.debug <<
|
||||||
|
" Offer1: " << offers_leg1.tip () << std::endl <<
|
||||||
|
" " << offers_leg1.tip ().amount().in <<
|
||||||
|
" : " << offers_leg1.tip ().amount ().out << std::endl <<
|
||||||
|
" Offer2: " << offers_leg2.tip () << std::endl <<
|
||||||
|
" " << offers_leg2.tip ().amount ().in <<
|
||||||
|
" : " << offers_leg2.tip ().amount ().out;
|
||||||
|
|
||||||
|
cross_result = taker.cross(offers_leg1.tip (), offers_leg2.tip ());
|
||||||
|
|
||||||
|
if (offers_leg1.tip ().fully_consumed ())
|
||||||
|
{
|
||||||
|
leg1_consumed = true;
|
||||||
|
have_bridged = offers_leg1.step_account (taker.account ());
|
||||||
|
}
|
||||||
|
if (have_bridged && offers_leg2.tip ().fully_consumed ())
|
||||||
|
{
|
||||||
|
leg2_consumed = true;
|
||||||
|
have_bridged = offers_leg2.step_account (taker.account ());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cross_result != tesSUCCESS)
|
||||||
|
{
|
||||||
|
cross_result = tecFAILED_PROCESSING;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (taker.done())
|
||||||
|
{
|
||||||
|
m_journal.debug << "The taker reports he's done during crossing!";
|
||||||
|
place_order = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Postcondition: If we aren't done, then we must have consumed at
|
||||||
|
// least one offer fully.
|
||||||
|
assert (direct_consumed || leg1_consumed || leg2_consumed);
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::make_pair(cross_result, taker.remaining_offer ());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -20,4 +20,34 @@
|
|||||||
#ifndef RIPPLE_TX_BRIDGE_OFFERCREATE_H_INCLUDED
|
#ifndef RIPPLE_TX_BRIDGE_OFFERCREATE_H_INCLUDED
|
||||||
#define RIPPLE_TX_BRIDGE_OFFERCREATE_H_INCLUDED
|
#define RIPPLE_TX_BRIDGE_OFFERCREATE_H_INCLUDED
|
||||||
|
|
||||||
|
#include "../book/Amounts.h"
|
||||||
|
|
||||||
|
#include <unordered_set>
|
||||||
|
|
||||||
|
namespace ripple {
|
||||||
|
|
||||||
|
class CreateOfferBridged
|
||||||
|
: public CreateOffer
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
CreateOfferBridged (
|
||||||
|
SerializedTransaction const& txn,
|
||||||
|
TransactionEngineParams params,
|
||||||
|
TransactionEngine* engine)
|
||||||
|
: CreateOffer (
|
||||||
|
txn,
|
||||||
|
params,
|
||||||
|
engine)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::pair<TER, core::Amounts> crossOffers (
|
||||||
|
core::LedgerView& view,
|
||||||
|
core::Amounts const& taker_amount) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -23,38 +23,38 @@
|
|||||||
|
|
||||||
namespace ripple {
|
namespace ripple {
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
/** Fill offer as much as possible by consuming offers already on the books.
|
||||||
|
We adjusts account balances and charges fees on top to taker.
|
||||||
|
|
||||||
// NIKB Move this in the right place
|
@param taker_amount.in How much the taker offers
|
||||||
std::pair<TER,bool>
|
@param taker_amount.out How much the taker wants
|
||||||
process_order (
|
@param taker_flow.in What the taker actually paid, not including fees.
|
||||||
|
@param taker_flow.out What the taker actually got, not including fees.
|
||||||
|
|
||||||
|
@return tesSUCCESS, terNO_ACCOUNT, telFAILED_PROCESSING, or
|
||||||
|
tecFAILED_PROCESSING
|
||||||
|
*/
|
||||||
|
std::pair<TER, core::Amounts>
|
||||||
|
CreateOfferDirect::crossOffers (
|
||||||
core::LedgerView& view,
|
core::LedgerView& view,
|
||||||
core::BookRef const book,
|
core::Amounts const& taker_amount)
|
||||||
core::Account const& account,
|
|
||||||
core::Amounts const& amount,
|
|
||||||
core::Amounts& cross_flow,
|
|
||||||
core::Taker::Options const options,
|
|
||||||
core::Clock::time_point const when,
|
|
||||||
beast::Journal& journal)
|
|
||||||
{
|
{
|
||||||
TER result (tesSUCCESS);
|
core::Taker::Options const options (mTxn.getFlags());
|
||||||
|
|
||||||
|
core::Clock::time_point const when (
|
||||||
|
mEngine->getLedger ()->getParentCloseTimeNC ());
|
||||||
|
|
||||||
core::LedgerView view_cancel (view.duplicate());
|
core::LedgerView view_cancel (view.duplicate());
|
||||||
core::OfferStream offers (view, view_cancel, book, when, journal);
|
core::OfferStream offers (view, view_cancel,
|
||||||
core::Taker taker (offers.view(), book, account, amount, options);
|
core::Book (
|
||||||
|
core::AssetRef (
|
||||||
|
taker_amount.in.getCurrency(), taker_amount.in.getIssuer()),
|
||||||
|
core::AssetRef (
|
||||||
|
taker_amount.out.getCurrency(), taker_amount.out.getIssuer())),
|
||||||
|
when, m_journal);
|
||||||
|
core::Taker taker (offers.view(), mTxnAccountID, taker_amount, options);
|
||||||
|
|
||||||
if (journal.debug) journal.debug <<
|
TER cross_result (tesSUCCESS);
|
||||||
"process_order: " <<
|
|
||||||
(options.sell? "sell" : "buy") << " " <<
|
|
||||||
(options.passive? "passive" : "") << std::endl <<
|
|
||||||
" taker: " << taker.account() << std::endl <<
|
|
||||||
" balances: " <<
|
|
||||||
view.accountFunds (taker.account(), amount.in) << ", " <<
|
|
||||||
view.accountFunds (taker.account(), amount.out);
|
|
||||||
|
|
||||||
cross_flow.in.clear (amount.in);
|
|
||||||
cross_flow.out.clear (amount.out);
|
|
||||||
|
|
||||||
bool place_order (true);
|
|
||||||
|
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
@@ -67,27 +67,22 @@ process_order (
|
|||||||
|
|
||||||
if (taker.done())
|
if (taker.done())
|
||||||
{
|
{
|
||||||
journal.debug << "The taker reports he's done during crossing!";
|
m_journal.debug << "The taker reports he's done during crossing!";
|
||||||
place_order = false;
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NIKB CHECKME Investigate whether we can use offer.step_account() here
|
||||||
|
// or whether doing so would cause a protocol-breaking
|
||||||
|
// change.
|
||||||
if (! offers.step ())
|
if (! offers.step ())
|
||||||
{
|
{
|
||||||
// Place the order since there are no
|
// Place the order since there are no
|
||||||
// more offers and the order has a balance.
|
// more offers and the order has a balance.
|
||||||
journal.debug << "No more offers to consider during crossing!";
|
m_journal.debug << "No more offers to consider during crossing!";
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto const offer (offers.tip());
|
auto const& offer (offers.tip());
|
||||||
|
|
||||||
if (journal.debug) journal.debug <<
|
|
||||||
"Considering offer: " << std::endl <<
|
|
||||||
" Id: " << offer.entry()->getIndex() << std::endl <<
|
|
||||||
" In: " << offer.amount().in << std::endl <<
|
|
||||||
" Out: " << offer.amount().out << std::endl <<
|
|
||||||
" By: " << offer.account();
|
|
||||||
|
|
||||||
if (taker.reject (offer.quality()))
|
if (taker.reject (offer.quality()))
|
||||||
{
|
{
|
||||||
@@ -98,592 +93,25 @@ process_order (
|
|||||||
|
|
||||||
if (offer.account() == taker.account())
|
if (offer.account() == taker.account())
|
||||||
{
|
{
|
||||||
if (journal.debug) journal.debug <<
|
// Skip offer from self. The offer will be considered expired and
|
||||||
" skipping self-offer " << offer.entry()->getIndex() << std::endl <<
|
// will get deleted.
|
||||||
" pays/gets " << offer.amount().in << ", " << offer.amount().out << std::endl <<
|
|
||||||
" during cross for " << std::endl <<
|
|
||||||
" pays/gets " << amount.in << ", " << amount.out;
|
|
||||||
;
|
|
||||||
|
|
||||||
// Skip offer from self.
|
|
||||||
// (Offer will be considered expired, and get deleted)
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (journal.debug) journal.debug <<
|
if (m_journal.debug) m_journal.debug <<
|
||||||
" offer " << offer.entry()->getIndex() << std::endl <<
|
" Offer: " << offer.entry()->getIndex() << std::endl <<
|
||||||
" pays/gets " << offer.amount().in << ", " << offer.amount().out
|
" " << offer.amount().in << " : " << offer.amount().out;
|
||||||
;
|
|
||||||
|
|
||||||
core::Amounts flow;
|
cross_result = taker.cross (offer);
|
||||||
bool consumed;
|
|
||||||
std::tie (flow, consumed) = taker.fill (offer);
|
|
||||||
|
|
||||||
result = taker.process (flow, offer);
|
if (cross_result != tesSUCCESS)
|
||||||
|
|
||||||
if (journal.debug) journal.debug <<
|
|
||||||
" flow " <<
|
|
||||||
flow.in << ", " << flow.out << std::endl <<
|
|
||||||
" balances " <<
|
|
||||||
view.accountFunds (taker.account(), amount.in) << ", " <<
|
|
||||||
view.accountFunds (taker.account(), amount.out)
|
|
||||||
;
|
|
||||||
|
|
||||||
if (result != tesSUCCESS)
|
|
||||||
{
|
{
|
||||||
// VFALCO TODO Return the tec and let a caller higher
|
cross_result = tecFAILED_PROCESSING;
|
||||||
// up convert the error if the ledger is open.
|
|
||||||
//result = bOpenLedger ?
|
|
||||||
// telFAILED_PROCESSING : tecFAILED_PROCESSING;
|
|
||||||
result = tecFAILED_PROCESSING;
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
cross_flow.in += flow.in;
|
|
||||||
cross_flow.out += flow.out;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (result == tesSUCCESS)
|
return std::make_pair(cross_result, taker.remaining_offer ());
|
||||||
{
|
|
||||||
// No point in placing an offer for a fill-or-kill offer - the offer
|
|
||||||
// will not succeed, since it wasn't filled.
|
|
||||||
if (options.fill_or_kill)
|
|
||||||
place_order = false;
|
|
||||||
|
|
||||||
// An immediate or cancel order will fill however much it is possible
|
|
||||||
// to fill and the remainder is not filled.
|
|
||||||
if (options.immediate_or_cancel)
|
|
||||||
place_order = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (result == tesSUCCESS)
|
|
||||||
{
|
|
||||||
if (place_order)
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
return std::make_pair(result,place_order);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Take as much as possible.
|
|
||||||
We adjusts account balances and charges fees on top to taker.
|
|
||||||
|
|
||||||
@param saTakerPays What the taker offers (w/ issuer)
|
|
||||||
@param saTakerGets What the taker wanted (w/ issuer)
|
|
||||||
@param saTakerPaid What taker could have paid including saved not including
|
|
||||||
fees. To reduce an offer.
|
|
||||||
@param saTakerGot What taker got not including fees. To reduce an offer.
|
|
||||||
@return tesSUCCESS, terNO_ACCOUNT, telFAILED_PROCESSING, or
|
|
||||||
tecFAILED_PROCESSING
|
|
||||||
*/
|
|
||||||
std::pair<TER,bool> DirectOfferCreateTransactor::crossOffers (
|
|
||||||
core::LedgerView& view,
|
|
||||||
const STAmount& saTakerPays,
|
|
||||||
const STAmount& saTakerGets,
|
|
||||||
STAmount& saTakerPaid,
|
|
||||||
STAmount& saTakerGot)
|
|
||||||
{
|
|
||||||
if (m_journal.debug) m_journal.debug << "takeOffers: ";
|
|
||||||
|
|
||||||
core::Book book (
|
|
||||||
core::AssetRef (
|
|
||||||
saTakerPays.getCurrency(), saTakerPays.getIssuer()),
|
|
||||||
core::AssetRef (
|
|
||||||
saTakerGets.getCurrency(), saTakerGets.getIssuer()));
|
|
||||||
|
|
||||||
core::Amounts cross_flow (
|
|
||||||
core::Amount (saTakerPays.getCurrency(), saTakerPays.getIssuer()),
|
|
||||||
core::Amount (saTakerGets.getCurrency(), saTakerGets.getIssuer()));
|
|
||||||
|
|
||||||
auto const result (process_order (
|
|
||||||
view, book, mTxnAccountID,
|
|
||||||
core::Amounts (saTakerPays, saTakerGets), cross_flow,
|
|
||||||
core::Taker::Options (mTxn.getFlags()),
|
|
||||||
mEngine->getLedger ()->getParentCloseTimeNC (),
|
|
||||||
m_journal));
|
|
||||||
|
|
||||||
core::Amounts const funds (
|
|
||||||
view.accountFunds (mTxnAccountID, saTakerPays),
|
|
||||||
view.accountFunds (mTxnAccountID, saTakerGets));
|
|
||||||
|
|
||||||
if (m_journal.debug) m_journal.debug << " cross_flow: " <<
|
|
||||||
cross_flow.in << ", " << cross_flow.out;
|
|
||||||
|
|
||||||
if (m_journal.debug) m_journal.debug << " balances: " <<
|
|
||||||
funds.in << ", " << funds.out;
|
|
||||||
|
|
||||||
saTakerPaid = cross_flow.in;
|
|
||||||
saTakerGot = cross_flow.out;
|
|
||||||
|
|
||||||
if (m_journal.debug) m_journal.debug <<
|
|
||||||
" result: " << transToken (result.first) <<
|
|
||||||
(result.second ? " (consumed)" : "");
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
TER DirectOfferCreateTransactor::doApply ()
|
|
||||||
{
|
|
||||||
if (m_journal.debug) m_journal.debug <<
|
|
||||||
"OfferCreate> " << mTxn.getJson (0);
|
|
||||||
|
|
||||||
std::uint32_t const uTxFlags = mTxn.getFlags ();
|
|
||||||
|
|
||||||
bool const bPassive = is_bit_set (uTxFlags, tfPassive);
|
|
||||||
bool const bImmediateOrCancel = is_bit_set (uTxFlags, tfImmediateOrCancel);
|
|
||||||
bool const bFillOrKill = is_bit_set (uTxFlags, tfFillOrKill);
|
|
||||||
bool const bSell = is_bit_set (uTxFlags, tfSell);
|
|
||||||
|
|
||||||
STAmount saTakerPays = mTxn.getFieldAmount (sfTakerPays);
|
|
||||||
STAmount saTakerGets = mTxn.getFieldAmount (sfTakerGets);
|
|
||||||
|
|
||||||
if (!saTakerPays.isLegalNet () || !saTakerGets.isLegalNet ())
|
|
||||||
return temBAD_AMOUNT;
|
|
||||||
|
|
||||||
uint160 const uPaysIssuerID = saTakerPays.getIssuer ();
|
|
||||||
uint160 const uGetsIssuerID = saTakerGets.getIssuer ();
|
|
||||||
|
|
||||||
bool const bHaveExpiration (mTxn.isFieldPresent (sfExpiration));
|
|
||||||
bool const bHaveCancel (mTxn.isFieldPresent (sfOfferSequence));
|
|
||||||
|
|
||||||
std::uint32_t const uExpiration = mTxn.getFieldU32 (sfExpiration);
|
|
||||||
std::uint32_t const uCancelSequence = mTxn.getFieldU32 (sfOfferSequence);
|
|
||||||
|
|
||||||
// FIXME understand why we use SequenceNext instead of current transaction
|
|
||||||
// sequence to determine the transaction. Why is the offer seuqnce
|
|
||||||
// number insufficient?
|
|
||||||
|
|
||||||
std::uint32_t const uAccountSequenceNext = mTxnAccount->getFieldU32 (sfSequence);
|
|
||||||
std::uint32_t const uSequence = mTxn.getSequence ();
|
|
||||||
|
|
||||||
const uint256 uLedgerIndex = Ledger::getOfferIndex (mTxnAccountID, uSequence);
|
|
||||||
|
|
||||||
if (m_journal.debug)
|
|
||||||
{
|
|
||||||
m_journal.debug <<
|
|
||||||
"Creating offer node: " << to_string (uLedgerIndex) <<
|
|
||||||
" uSequence=" << uSequence;
|
|
||||||
|
|
||||||
if (bImmediateOrCancel)
|
|
||||||
m_journal.debug << "Transaction: IoC set.";
|
|
||||||
|
|
||||||
if (bFillOrKill)
|
|
||||||
m_journal.debug << "Transaction: FoK set.";
|
|
||||||
}
|
|
||||||
|
|
||||||
uint160 const uPaysCurrency = saTakerPays.getCurrency ();
|
|
||||||
uint160 const uGetsCurrency = saTakerGets.getCurrency ();
|
|
||||||
std::uint64_t const uRate = STAmount::getRate (saTakerGets, saTakerPays);
|
|
||||||
|
|
||||||
TER terResult (tesSUCCESS);
|
|
||||||
uint256 uDirectory; // Delete hints.
|
|
||||||
std::uint64_t uOwnerNode;
|
|
||||||
std::uint64_t uBookNode;
|
|
||||||
|
|
||||||
// This is the ledger view that we work against. Transactions are applied
|
|
||||||
// as we go on processing transactions.
|
|
||||||
core::LedgerView& view (mEngine->view ());
|
|
||||||
|
|
||||||
// This is a checkpoint with just the fees paid. If something goes wrong
|
|
||||||
// with this transaction, we roll back to this ledger.
|
|
||||||
core::LedgerView view_checkpoint (view);
|
|
||||||
|
|
||||||
view.bumpSeq (); // Begin ledger variance.
|
|
||||||
|
|
||||||
SLE::pointer sleCreator = mEngine->entryCache (
|
|
||||||
ltACCOUNT_ROOT, Ledger::getAccountRootIndex (mTxnAccountID));
|
|
||||||
|
|
||||||
if (uTxFlags & tfOfferCreateMask)
|
|
||||||
{
|
|
||||||
if (m_journal.debug) m_journal.debug <<
|
|
||||||
"Malformed transaction: Invalid flags set.";
|
|
||||||
|
|
||||||
return temINVALID_FLAG;
|
|
||||||
}
|
|
||||||
else if (bImmediateOrCancel && bFillOrKill)
|
|
||||||
{
|
|
||||||
if (m_journal.debug) m_journal.debug <<
|
|
||||||
"Malformed transaction: both IoC and FoK set.";
|
|
||||||
|
|
||||||
return temINVALID_FLAG;
|
|
||||||
}
|
|
||||||
else if (bHaveExpiration && !uExpiration)
|
|
||||||
{
|
|
||||||
m_journal.warning <<
|
|
||||||
"Malformed offer: bad expiration";
|
|
||||||
|
|
||||||
terResult = temBAD_EXPIRATION;
|
|
||||||
}
|
|
||||||
else if (saTakerPays.isNative () && saTakerGets.isNative ())
|
|
||||||
{
|
|
||||||
m_journal.warning <<
|
|
||||||
"Malformed offer: XRP for XRP";
|
|
||||||
|
|
||||||
terResult = temBAD_OFFER;
|
|
||||||
}
|
|
||||||
else if (saTakerPays <= zero || saTakerGets <= zero)
|
|
||||||
{
|
|
||||||
m_journal.warning <<
|
|
||||||
"Malformed offer: bad amount";
|
|
||||||
|
|
||||||
terResult = temBAD_OFFER;
|
|
||||||
}
|
|
||||||
else if (uPaysCurrency == uGetsCurrency && uPaysIssuerID == uGetsIssuerID)
|
|
||||||
{
|
|
||||||
m_journal.warning <<
|
|
||||||
"Malformed offer: redundant offer";
|
|
||||||
|
|
||||||
terResult = temREDUNDANT;
|
|
||||||
}
|
|
||||||
// We don't allow a non-native currency to use the currency code XRP.
|
|
||||||
else if (CURRENCY_BAD == uPaysCurrency || CURRENCY_BAD == uGetsCurrency)
|
|
||||||
{
|
|
||||||
m_journal.warning <<
|
|
||||||
"Malformed offer: Bad currency.";
|
|
||||||
|
|
||||||
terResult = temBAD_CURRENCY;
|
|
||||||
}
|
|
||||||
else if (saTakerPays.isNative () != !uPaysIssuerID || saTakerGets.isNative () != !uGetsIssuerID)
|
|
||||||
{
|
|
||||||
m_journal.warning <<
|
|
||||||
"Malformed offer: bad issuer";
|
|
||||||
|
|
||||||
terResult = temBAD_ISSUER;
|
|
||||||
}
|
|
||||||
else if (view.accountFunds (mTxnAccountID, saTakerGets) <= zero)
|
|
||||||
{
|
|
||||||
m_journal.warning <<
|
|
||||||
"delay: Offers must be at least partially funded.";
|
|
||||||
|
|
||||||
terResult = tecUNFUNDED_OFFER;
|
|
||||||
}
|
|
||||||
// This can probably be simplified to make sure that you cancel sequences
|
|
||||||
// before the transaction sequence number.
|
|
||||||
else if (bHaveCancel && (!uCancelSequence || uAccountSequenceNext - 1 <= uCancelSequence))
|
|
||||||
{
|
|
||||||
if (m_journal.debug) m_journal.debug <<
|
|
||||||
"uAccountSequenceNext=" << uAccountSequenceNext <<
|
|
||||||
" uOfferSequence=" << uCancelSequence;
|
|
||||||
|
|
||||||
terResult = temBAD_SEQUENCE;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tesSUCCESS != terResult)
|
|
||||||
{
|
|
||||||
if (m_journal.debug) m_journal.debug <<
|
|
||||||
"final terResult=" << transToken (terResult);
|
|
||||||
|
|
||||||
return terResult;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Process a cancellation request that's passed along with an offer.
|
|
||||||
if ((tesSUCCESS == terResult) && bHaveCancel)
|
|
||||||
{
|
|
||||||
uint256 const uCancelIndex (
|
|
||||||
Ledger::getOfferIndex (mTxnAccountID, uCancelSequence));
|
|
||||||
SLE::pointer sleCancel = mEngine->entryCache (ltOFFER, uCancelIndex);
|
|
||||||
|
|
||||||
// It's not an error to not find the offer to cancel: it might have
|
|
||||||
// been consumed or removed as we are processing. Additionally, it
|
|
||||||
// might not even have been an offer - we don't care.
|
|
||||||
if (sleCancel)
|
|
||||||
{
|
|
||||||
m_journal.warning <<
|
|
||||||
"Cancelling order with sequence " << uCancelSequence;
|
|
||||||
|
|
||||||
terResult = view.offerDelete (sleCancel);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Expiration is defined in terms of the close time of the parent ledger,
|
|
||||||
// because we definitively know the time that it closed but we do not
|
|
||||||
// know the closing time of the ledger that is under construction.
|
|
||||||
if (bHaveExpiration &&
|
|
||||||
(mEngine->getLedger ()->getParentCloseTimeNC () >= uExpiration))
|
|
||||||
{
|
|
||||||
return tesSUCCESS;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If all is well and this isn't an offer to XRP, then we make sure we are
|
|
||||||
// authorized to hold what the taker will pay.
|
|
||||||
if (tesSUCCESS == terResult && !saTakerPays.isNative ())
|
|
||||||
{
|
|
||||||
SLE::pointer sleTakerPays = mEngine->entryCache (
|
|
||||||
ltACCOUNT_ROOT, Ledger::getAccountRootIndex (uPaysIssuerID));
|
|
||||||
|
|
||||||
if (!sleTakerPays)
|
|
||||||
{
|
|
||||||
m_journal.warning <<
|
|
||||||
"delay: can't receive IOUs from non-existent issuer: " <<
|
|
||||||
RippleAddress::createHumanAccountID (uPaysIssuerID);
|
|
||||||
|
|
||||||
return is_bit_set (mParams, tapRETRY)
|
|
||||||
? terNO_ACCOUNT
|
|
||||||
: tecNO_ISSUER;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (is_bit_set (sleTakerPays->getFieldU32 (sfFlags), lsfRequireAuth))
|
|
||||||
{
|
|
||||||
SLE::pointer sleRippleState (mEngine->entryCache (
|
|
||||||
ltRIPPLE_STATE,
|
|
||||||
Ledger::getRippleStateIndex (
|
|
||||||
mTxnAccountID, uPaysIssuerID, uPaysCurrency)));
|
|
||||||
|
|
||||||
if (!sleRippleState)
|
|
||||||
{
|
|
||||||
return is_bit_set (mParams, tapRETRY)
|
|
||||||
? terNO_LINE
|
|
||||||
: tecNO_LINE;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Entries have a canonical representation, determined by a
|
|
||||||
// lexicographical "greater than" comparison employing strict weak
|
|
||||||
// ordering. Determine which entry we need to access.
|
|
||||||
bool const canonical_gt (mTxnAccountID > uPaysIssuerID);
|
|
||||||
|
|
||||||
bool const need_auth (is_bit_set (
|
|
||||||
sleRippleState->getFieldU32 (sfFlags),
|
|
||||||
(canonical_gt ? lsfLowAuth : lsfHighAuth)));
|
|
||||||
|
|
||||||
if (need_auth)
|
|
||||||
{
|
|
||||||
if (m_journal.debug) m_journal.debug <<
|
|
||||||
"delay: can't receive IOUs from issuer without auth.";
|
|
||||||
|
|
||||||
return is_bit_set (mParams, tapRETRY)
|
|
||||||
? terNO_AUTH
|
|
||||||
: tecNO_AUTH;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
STAmount saPaid;
|
|
||||||
STAmount saGot;
|
|
||||||
bool const bOpenLedger = is_bit_set (mParams, tapOPEN_LEDGER);
|
|
||||||
bool const placeOffer = true;
|
|
||||||
|
|
||||||
if (tesSUCCESS == terResult)
|
|
||||||
{
|
|
||||||
// Take using the parameters of the offer.
|
|
||||||
if (m_journal.debug) m_journal.debug <<
|
|
||||||
"takeOffers: BEFORE saTakerGets=" << saTakerGets.getFullText ();
|
|
||||||
|
|
||||||
auto ret = crossOffers (
|
|
||||||
view,
|
|
||||||
saTakerGets, // Reverse as we are the taker for taking.
|
|
||||||
saTakerPays,
|
|
||||||
saPaid, // Buy semantics: how much would have sold at full price. Sell semantics: how much was sold.
|
|
||||||
saGot); // How much was got.
|
|
||||||
|
|
||||||
terResult = ret.first;
|
|
||||||
|
|
||||||
if (terResult == tecFAILED_PROCESSING && bOpenLedger)
|
|
||||||
terResult = telFAILED_PROCESSING;
|
|
||||||
|
|
||||||
if (m_journal.debug)
|
|
||||||
{
|
|
||||||
m_journal.debug << "takeOffers=" << terResult;
|
|
||||||
m_journal.debug << "takeOffers: saPaid=" << saPaid.getFullText ();
|
|
||||||
m_journal.debug << "takeOffers: saGot=" << saGot.getFullText ();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tesSUCCESS == terResult)
|
|
||||||
{
|
|
||||||
// Reduce pay in from takers by what offer just got.
|
|
||||||
saTakerPays -= saGot;
|
|
||||||
|
|
||||||
// Reduce pay out to takers by what srcAccount just paid.
|
|
||||||
saTakerGets -= saPaid;
|
|
||||||
|
|
||||||
if (m_journal.debug)
|
|
||||||
{
|
|
||||||
m_journal.debug <<
|
|
||||||
"takeOffers: AFTER saTakerPays=" <<
|
|
||||||
saTakerPays.getFullText ();
|
|
||||||
m_journal.debug <<
|
|
||||||
"takeOffers: AFTER saTakerGets=" <<
|
|
||||||
saTakerGets.getFullText ();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (m_journal.debug)
|
|
||||||
{
|
|
||||||
m_journal.debug <<
|
|
||||||
"takeOffers: saTakerPays=" <<saTakerPays.getFullText ();
|
|
||||||
m_journal.debug <<
|
|
||||||
"takeOffers: saTakerGets=" << saTakerGets.getFullText ();
|
|
||||||
m_journal.debug <<
|
|
||||||
"takeOffers: mTxnAccountID=" <<
|
|
||||||
RippleAddress::createHumanAccountID (mTxnAccountID);
|
|
||||||
m_journal.debug <<
|
|
||||||
"takeOffers: FUNDS=" <<
|
|
||||||
view.accountFunds (mTxnAccountID, saTakerGets).getFullText ();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tesSUCCESS != terResult)
|
|
||||||
{
|
|
||||||
m_journal.debug <<
|
|
||||||
"final terResult=" << transToken (terResult);
|
|
||||||
|
|
||||||
return terResult;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (bFillOrKill && (saTakerPays || saTakerGets))
|
|
||||||
{
|
|
||||||
// Fill or kill and have leftovers.
|
|
||||||
view.swapWith (view_checkpoint); // Restore with just fees paid.
|
|
||||||
return tesSUCCESS;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!placeOffer
|
|
||||||
|| saTakerPays <= zero // Wants nothing more.
|
|
||||||
|| saTakerGets <= zero // Offering nothing more.
|
|
||||||
|| bImmediateOrCancel // Do not persist.
|
|
||||||
|| view.accountFunds (mTxnAccountID, saTakerGets) <= zero) // Not funded.
|
|
||||||
{
|
|
||||||
// Complete as is.
|
|
||||||
nothing ();
|
|
||||||
}
|
|
||||||
else if (mPriorBalance.getNValue () < mEngine->getLedger ()->getReserve (sleCreator->getFieldU32 (sfOwnerCount) + 1))
|
|
||||||
{
|
|
||||||
// If we are here, the signing account had an insufficient reserve
|
|
||||||
// *prior* to our processing. We use the prior balance to simplify
|
|
||||||
// client writing and make the user experience better.
|
|
||||||
|
|
||||||
if (bOpenLedger) // Ledger is not final, can vote no.
|
|
||||||
{
|
|
||||||
// Hope for more reserve to come in or more offers to consume. If we
|
|
||||||
// specified a local error this transaction will not be retried, so
|
|
||||||
// specify a tec to distribute the transaction and allow it to be
|
|
||||||
// retried. In particular, it may have been successful to a
|
|
||||||
// degree (partially filled) and if it hasn't, it might succeed.
|
|
||||||
terResult = tecINSUF_RESERVE_OFFER;
|
|
||||||
}
|
|
||||||
else if (!saPaid && !saGot)
|
|
||||||
{
|
|
||||||
// Ledger is final, insufficent reserve to create offer, processed
|
|
||||||
// nothing.
|
|
||||||
|
|
||||||
terResult = tecINSUF_RESERVE_OFFER;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Ledger is final, insufficent reserve to create offer, processed
|
|
||||||
// something.
|
|
||||||
|
|
||||||
// Consider the offer unfunded. Treat as tesSUCCESS.
|
|
||||||
nothing ();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// We need to place the remainder of the offer into its order book.
|
|
||||||
if (m_journal.debug) m_journal.debug <<
|
|
||||||
"offer not fully consumed:" <<
|
|
||||||
" saTakerPays=" << saTakerPays.getFullText () <<
|
|
||||||
" saTakerGets=" << saTakerGets.getFullText ();
|
|
||||||
|
|
||||||
// Add offer to owner's directory.
|
|
||||||
terResult = view.dirAdd (uOwnerNode,
|
|
||||||
Ledger::getOwnerDirIndex (mTxnAccountID), uLedgerIndex,
|
|
||||||
std::bind (&Ledger::ownerDirDescriber, std::placeholders::_1,
|
|
||||||
std::placeholders::_2, mTxnAccountID));
|
|
||||||
|
|
||||||
if (tesSUCCESS == terResult)
|
|
||||||
{
|
|
||||||
// Update owner count.
|
|
||||||
view.ownerCountAdjust (mTxnAccountID, 1, sleCreator);
|
|
||||||
|
|
||||||
uint256 uBookBase = Ledger::getBookBase (
|
|
||||||
uPaysCurrency,
|
|
||||||
uPaysIssuerID,
|
|
||||||
uGetsCurrency,
|
|
||||||
uGetsIssuerID);
|
|
||||||
|
|
||||||
if (m_journal.debug) m_journal.debug <<
|
|
||||||
"adding to book: " << to_string (uBookBase) <<
|
|
||||||
" : " << saTakerPays.getHumanCurrency () <<
|
|
||||||
"/" << RippleAddress::createHumanAccountID (saTakerPays.getIssuer ()) <<
|
|
||||||
" -> " << saTakerGets.getHumanCurrency () <<
|
|
||||||
"/" << RippleAddress::createHumanAccountID (saTakerGets.getIssuer ());
|
|
||||||
|
|
||||||
// We use the original rate to place the offer.
|
|
||||||
uDirectory = Ledger::getQualityIndex (uBookBase, uRate);
|
|
||||||
|
|
||||||
// Add offer to order book.
|
|
||||||
terResult = view.dirAdd (uBookNode, uDirectory, uLedgerIndex,
|
|
||||||
std::bind (&Ledger::qualityDirDescriber, std::placeholders::_1,
|
|
||||||
std::placeholders::_2, saTakerPays.getCurrency (),
|
|
||||||
uPaysIssuerID, saTakerGets.getCurrency (),
|
|
||||||
uGetsIssuerID, uRate));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tesSUCCESS == terResult)
|
|
||||||
{
|
|
||||||
if (m_journal.debug)
|
|
||||||
{
|
|
||||||
m_journal.debug <<
|
|
||||||
"sfAccount=" <<
|
|
||||||
RippleAddress::createHumanAccountID (mTxnAccountID);
|
|
||||||
m_journal.debug <<
|
|
||||||
"uPaysIssuerID=" <<
|
|
||||||
RippleAddress::createHumanAccountID (uPaysIssuerID);
|
|
||||||
m_journal.debug <<
|
|
||||||
"uGetsIssuerID=" <<
|
|
||||||
RippleAddress::createHumanAccountID (uGetsIssuerID);
|
|
||||||
m_journal.debug <<
|
|
||||||
"saTakerPays.isNative()=" <<
|
|
||||||
saTakerPays.isNative ();
|
|
||||||
m_journal.debug <<
|
|
||||||
"saTakerGets.isNative()=" <<
|
|
||||||
saTakerGets.isNative ();
|
|
||||||
m_journal.debug <<
|
|
||||||
"uPaysCurrency=" <<
|
|
||||||
saTakerPays.getHumanCurrency ();
|
|
||||||
m_journal.debug <<
|
|
||||||
"uGetsCurrency=" <<
|
|
||||||
saTakerGets.getHumanCurrency ();
|
|
||||||
}
|
|
||||||
|
|
||||||
SLE::pointer sleOffer (mEngine->entryCreate (ltOFFER, uLedgerIndex));
|
|
||||||
|
|
||||||
sleOffer->setFieldAccount (sfAccount, mTxnAccountID);
|
|
||||||
sleOffer->setFieldU32 (sfSequence, uSequence);
|
|
||||||
sleOffer->setFieldH256 (sfBookDirectory, uDirectory);
|
|
||||||
sleOffer->setFieldAmount (sfTakerPays, saTakerPays);
|
|
||||||
sleOffer->setFieldAmount (sfTakerGets, saTakerGets);
|
|
||||||
sleOffer->setFieldU64 (sfOwnerNode, uOwnerNode);
|
|
||||||
sleOffer->setFieldU64 (sfBookNode, uBookNode);
|
|
||||||
|
|
||||||
if (uExpiration)
|
|
||||||
sleOffer->setFieldU32 (sfExpiration, uExpiration);
|
|
||||||
|
|
||||||
if (bPassive)
|
|
||||||
sleOffer->setFlag (lsfPassive);
|
|
||||||
|
|
||||||
if (bSell)
|
|
||||||
sleOffer->setFlag (lsfSell);
|
|
||||||
|
|
||||||
if (m_journal.debug) m_journal.debug <<
|
|
||||||
"final terResult=" << transToken (terResult) <<
|
|
||||||
" sleOffer=" << sleOffer->getJson (0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tesSUCCESS != terResult)
|
|
||||||
{
|
|
||||||
m_journal.debug <<
|
|
||||||
"final terResult=" << transToken (terResult);
|
|
||||||
}
|
|
||||||
|
|
||||||
return terResult;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,22 +20,21 @@
|
|||||||
#ifndef RIPPLE_TX_DIRECT_OFFERCREATE_H_INCLUDED
|
#ifndef RIPPLE_TX_DIRECT_OFFERCREATE_H_INCLUDED
|
||||||
#define RIPPLE_TX_DIRECT_OFFERCREATE_H_INCLUDED
|
#define RIPPLE_TX_DIRECT_OFFERCREATE_H_INCLUDED
|
||||||
|
|
||||||
#include "../book/OfferStream.h"
|
#include "../book/Amounts.h"
|
||||||
#include "../book/Taker.h"
|
|
||||||
|
|
||||||
#include <unordered_set>
|
#include <unordered_set>
|
||||||
|
|
||||||
namespace ripple {
|
namespace ripple {
|
||||||
|
|
||||||
class DirectOfferCreateTransactor
|
class CreateOfferDirect
|
||||||
: public OfferCreateTransactor
|
: public CreateOffer
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
DirectOfferCreateTransactor (
|
CreateOfferDirect (
|
||||||
SerializedTransaction const& txn,
|
SerializedTransaction const& txn,
|
||||||
TransactionEngineParams params,
|
TransactionEngineParams params,
|
||||||
TransactionEngine* engine)
|
TransactionEngine* engine)
|
||||||
: OfferCreateTransactor (
|
: CreateOffer (
|
||||||
txn,
|
txn,
|
||||||
params,
|
params,
|
||||||
engine)
|
engine)
|
||||||
@@ -43,15 +42,10 @@ public:
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TER doApply () override;
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::pair<TER,bool> crossOffers (
|
std::pair<TER, core::Amounts> crossOffers (
|
||||||
core::LedgerView& view,
|
core::LedgerView& view,
|
||||||
STAmount const& saTakerPays,
|
core::Amounts const& taker_amount) override;
|
||||||
STAmount const& saTakerGets,
|
|
||||||
STAmount& saTakerPaid,
|
|
||||||
STAmount& saTakerGot);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ namespace ripple {
|
|||||||
/** Determine if an order is still valid
|
/** Determine if an order is still valid
|
||||||
If the order is not valid it will be marked as unfunded.
|
If the order is not valid it will be marked as unfunded.
|
||||||
*/
|
*/
|
||||||
bool ClassicOfferCreateTransactor::isValidOffer (
|
bool CreateOfferLegacy::isValidOffer (
|
||||||
SLE::ref sleOffer,
|
SLE::ref sleOffer,
|
||||||
uint160 const& uOfferOwnerID,
|
uint160 const& uOfferOwnerID,
|
||||||
STAmount const& saOfferPays,
|
STAmount const& saOfferPays,
|
||||||
@@ -97,7 +97,7 @@ bool ClassicOfferCreateTransactor::isValidOffer (
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
*/
|
*/
|
||||||
bool ClassicOfferCreateTransactor::canCross (
|
bool CreateOfferLegacy::canCross (
|
||||||
STAmount const& saTakerFunds,
|
STAmount const& saTakerFunds,
|
||||||
STAmount const& saSubTakerPays,
|
STAmount const& saSubTakerPays,
|
||||||
STAmount const& saSubTakerGets,
|
STAmount const& saSubTakerGets,
|
||||||
@@ -189,7 +189,7 @@ bool ClassicOfferCreateTransactor::canCross (
|
|||||||
books as: "give 0.57 BTC for 400 USD" which is what is left to sell at the
|
books as: "give 0.57 BTC for 400 USD" which is what is left to sell at the
|
||||||
original rate.
|
original rate.
|
||||||
*/
|
*/
|
||||||
bool ClassicOfferCreateTransactor::applyOffer (
|
bool CreateOfferLegacy::applyOffer (
|
||||||
const bool bSell,
|
const bool bSell,
|
||||||
const std::uint32_t uTakerPaysRate, const std::uint32_t uOfferPaysRate,
|
const std::uint32_t uTakerPaysRate, const std::uint32_t uOfferPaysRate,
|
||||||
const STAmount& saOfferRate,
|
const STAmount& saOfferRate,
|
||||||
@@ -327,7 +327,7 @@ bool ClassicOfferCreateTransactor::applyOffer (
|
|||||||
@return tesSUCCESS, terNO_ACCOUNT, telFAILED_PROCESSING, or
|
@return tesSUCCESS, terNO_ACCOUNT, telFAILED_PROCESSING, or
|
||||||
tecFAILED_PROCESSING
|
tecFAILED_PROCESSING
|
||||||
*/
|
*/
|
||||||
TER ClassicOfferCreateTransactor::takeOffers (
|
TER CreateOfferLegacy::takeOffers (
|
||||||
const bool bOpenLedger,
|
const bool bOpenLedger,
|
||||||
const bool bPassive,
|
const bool bPassive,
|
||||||
const bool bSell,
|
const bool bSell,
|
||||||
@@ -671,7 +671,7 @@ TER ClassicOfferCreateTransactor::takeOffers (
|
|||||||
return terResult;
|
return terResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
TER ClassicOfferCreateTransactor::doApply ()
|
TER CreateOfferLegacy::doApply ()
|
||||||
{
|
{
|
||||||
if (m_journal.debug) m_journal.debug <<
|
if (m_journal.debug) m_journal.debug <<
|
||||||
"OfferCreate> " << mTxn.getJson (0);
|
"OfferCreate> " << mTxn.getJson (0);
|
||||||
|
|||||||
@@ -24,30 +24,15 @@
|
|||||||
|
|
||||||
namespace ripple {
|
namespace ripple {
|
||||||
|
|
||||||
class ClassicOfferCreateTransactor
|
class CreateOfferLegacy
|
||||||
: public OfferCreateTransactor
|
: public CreateOffer
|
||||||
{
|
{
|
||||||
private:
|
|
||||||
template <class T>
|
|
||||||
static std::string
|
|
||||||
get_compare_sign (T const& lhs, T const& rhs)
|
|
||||||
{
|
|
||||||
if (lhs > rhs)
|
|
||||||
return ">";
|
|
||||||
|
|
||||||
if (rhs > lhs)
|
|
||||||
return "<";
|
|
||||||
|
|
||||||
// If neither is bigger than the other, they must be equal
|
|
||||||
return "=";
|
|
||||||
}
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
ClassicOfferCreateTransactor (
|
CreateOfferLegacy (
|
||||||
SerializedTransaction const& txn,
|
SerializedTransaction const& txn,
|
||||||
TransactionEngineParams params,
|
TransactionEngineParams params,
|
||||||
TransactionEngine* engine)
|
TransactionEngine* engine)
|
||||||
: OfferCreateTransactor (
|
: CreateOffer (
|
||||||
txn,
|
txn,
|
||||||
params,
|
params,
|
||||||
engine)
|
engine)
|
||||||
@@ -57,6 +42,13 @@ public:
|
|||||||
|
|
||||||
TER doApply () override;
|
TER doApply () override;
|
||||||
|
|
||||||
|
virtual std::pair<TER, core::Amounts> crossOffers (
|
||||||
|
core::LedgerView& view,
|
||||||
|
core::Amounts const& taker_amount)
|
||||||
|
{
|
||||||
|
return std::make_pair (tesSUCCESS, core::Amounts ());
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool isValidOffer (
|
bool isValidOffer (
|
||||||
SLE::ref sleOfferDir,
|
SLE::ref sleOfferDir,
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ namespace ripple {
|
|||||||
|
|
||||||
// See https://ripple.com/wiki/Transaction_Format#Payment_.280.29
|
// See https://ripple.com/wiki/Transaction_Format#Payment_.280.29
|
||||||
|
|
||||||
TER PaymentTransactor::doApply ()
|
TER Payment::doApply ()
|
||||||
{
|
{
|
||||||
// Ripple if source or destination is non-native or if there are paths.
|
// Ripple if source or destination is non-native or if there are paths.
|
||||||
std::uint32_t const uTxFlags = mTxn.getFlags ();
|
std::uint32_t const uTxFlags = mTxn.getFlags ();
|
||||||
|
|||||||
@@ -22,23 +22,23 @@
|
|||||||
|
|
||||||
namespace ripple {
|
namespace ripple {
|
||||||
|
|
||||||
class PaymentTransactorLog;
|
class PaymentLog;
|
||||||
|
|
||||||
template <>
|
template <>
|
||||||
char const*
|
char const*
|
||||||
LogPartition::getPartitionName <PaymentTransactorLog> ()
|
LogPartition::getPartitionName <PaymentLog> ()
|
||||||
{
|
{
|
||||||
return "Tx/Payment";
|
return "Tx/Payment";
|
||||||
}
|
}
|
||||||
|
|
||||||
class PaymentTransactor
|
class Payment
|
||||||
: public Transactor
|
: public Transactor
|
||||||
{
|
{
|
||||||
/* The largest number of paths we allow */
|
/* The largest number of paths we allow */
|
||||||
static std::size_t const MaxPathSize = 6;
|
static std::size_t const MaxPathSize = 6;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
PaymentTransactor (
|
Payment (
|
||||||
SerializedTransaction const& txn,
|
SerializedTransaction const& txn,
|
||||||
TransactionEngineParams params,
|
TransactionEngineParams params,
|
||||||
TransactionEngine* engine)
|
TransactionEngine* engine)
|
||||||
@@ -46,7 +46,7 @@ public:
|
|||||||
txn,
|
txn,
|
||||||
params,
|
params,
|
||||||
engine,
|
engine,
|
||||||
LogPartition::getJournal <PaymentTransactorLog> ())
|
LogPartition::getJournal <PaymentLog> ())
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -54,6 +54,16 @@ public:
|
|||||||
TER doApply ();
|
TER doApply ();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
inline
|
||||||
|
std::unique_ptr <Transactor>
|
||||||
|
make_Payment (
|
||||||
|
SerializedTransaction const& txn,
|
||||||
|
TransactionEngineParams params,
|
||||||
|
TransactionEngine* engine)
|
||||||
|
{
|
||||||
|
return std::make_unique <Payment> (txn, params, engine);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
|
|
||||||
namespace ripple {
|
namespace ripple {
|
||||||
|
|
||||||
TER AccountSetTransactor::doApply ()
|
TER SetAccount::doApply ()
|
||||||
{
|
{
|
||||||
std::uint32_t const uTxFlags = mTxn.getFlags ();
|
std::uint32_t const uTxFlags = mTxn.getFlags ();
|
||||||
|
|
||||||
|
|||||||
@@ -22,20 +22,20 @@
|
|||||||
|
|
||||||
namespace ripple {
|
namespace ripple {
|
||||||
|
|
||||||
class AccountSetTransactorLog;
|
class SetAccountLog;
|
||||||
|
|
||||||
template <>
|
template <>
|
||||||
char const*
|
char const*
|
||||||
LogPartition::getPartitionName <AccountSetTransactorLog> ()
|
LogPartition::getPartitionName <SetAccountLog> ()
|
||||||
{
|
{
|
||||||
return "Tx/AccountSet";
|
return "Tx/AccountSet";
|
||||||
}
|
}
|
||||||
|
|
||||||
class AccountSetTransactor
|
class SetAccount
|
||||||
: public Transactor
|
: public Transactor
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
AccountSetTransactor (
|
SetAccount (
|
||||||
SerializedTransaction const& txn,
|
SerializedTransaction const& txn,
|
||||||
TransactionEngineParams params,
|
TransactionEngineParams params,
|
||||||
TransactionEngine* engine)
|
TransactionEngine* engine)
|
||||||
@@ -43,7 +43,7 @@ public:
|
|||||||
txn,
|
txn,
|
||||||
params,
|
params,
|
||||||
engine,
|
engine,
|
||||||
LogPartition::getJournal <AccountSetTransactorLog> ())
|
LogPartition::getJournal <SetAccountLog> ())
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -51,6 +51,16 @@ public:
|
|||||||
TER doApply () override;
|
TER doApply () override;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
inline
|
||||||
|
std::unique_ptr <Transactor>
|
||||||
|
make_SetAccount (
|
||||||
|
SerializedTransaction const& txn,
|
||||||
|
TransactionEngineParams params,
|
||||||
|
TransactionEngine* engine)
|
||||||
|
{
|
||||||
|
return std::make_unique <SetAccount> (txn, params, engine);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
|
|
||||||
namespace ripple {
|
namespace ripple {
|
||||||
|
|
||||||
std::uint64_t RegularKeySetTransactor::calculateBaseFee ()
|
std::uint64_t SetRegularKey::calculateBaseFee ()
|
||||||
{
|
{
|
||||||
if ( mTxnAccount
|
if ( mTxnAccount
|
||||||
&& (! (mTxnAccount->getFlags () & lsfPasswordSpent))
|
&& (! (mTxnAccount->getFlags () & lsfPasswordSpent))
|
||||||
@@ -33,7 +33,7 @@ std::uint64_t RegularKeySetTransactor::calculateBaseFee ()
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
TER RegularKeySetTransactor::doApply ()
|
TER SetRegularKey::doApply ()
|
||||||
{
|
{
|
||||||
std::uint32_t const uTxFlags = mTxn.getFlags ();
|
std::uint32_t const uTxFlags = mTxn.getFlags ();
|
||||||
|
|
||||||
|
|||||||
@@ -22,22 +22,22 @@
|
|||||||
|
|
||||||
namespace ripple {
|
namespace ripple {
|
||||||
|
|
||||||
class RegularKeySetTransactorLog;
|
class SetRegularKeyLog;
|
||||||
|
|
||||||
template <>
|
template <>
|
||||||
char const*
|
char const*
|
||||||
LogPartition::getPartitionName <RegularKeySetTransactorLog> ()
|
LogPartition::getPartitionName <SetRegularKeyLog> ()
|
||||||
{
|
{
|
||||||
return "Tx/RegularKeySet";
|
return "Tx/RegularKeySet";
|
||||||
}
|
}
|
||||||
|
|
||||||
class RegularKeySetTransactor
|
class SetRegularKey
|
||||||
: public Transactor
|
: public Transactor
|
||||||
{
|
{
|
||||||
std::uint64_t calculateBaseFee ();
|
std::uint64_t calculateBaseFee ();
|
||||||
|
|
||||||
public:
|
public:
|
||||||
RegularKeySetTransactor (
|
SetRegularKey (
|
||||||
SerializedTransaction const& txn,
|
SerializedTransaction const& txn,
|
||||||
TransactionEngineParams params,
|
TransactionEngineParams params,
|
||||||
TransactionEngine* engine)
|
TransactionEngine* engine)
|
||||||
@@ -45,7 +45,7 @@ public:
|
|||||||
txn,
|
txn,
|
||||||
params,
|
params,
|
||||||
engine,
|
engine,
|
||||||
LogPartition::getJournal <RegularKeySetTransactorLog> ())
|
LogPartition::getJournal <SetRegularKeyLog> ())
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -54,6 +54,16 @@ public:
|
|||||||
TER doApply ();
|
TER doApply ();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
inline
|
||||||
|
std::unique_ptr <Transactor>
|
||||||
|
make_SetRegularKey (
|
||||||
|
SerializedTransaction const& txn,
|
||||||
|
TransactionEngineParams params,
|
||||||
|
TransactionEngine* engine)
|
||||||
|
{
|
||||||
|
return std::make_unique <SetRegularKey> (txn, params, engine);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
|
|
||||||
namespace ripple {
|
namespace ripple {
|
||||||
|
|
||||||
TER TrustSetTransactor::doApply ()
|
TER SetTrust::doApply ()
|
||||||
{
|
{
|
||||||
TER terResult = tesSUCCESS;
|
TER terResult = tesSUCCESS;
|
||||||
|
|
||||||
|
|||||||
@@ -22,20 +22,20 @@
|
|||||||
|
|
||||||
namespace ripple {
|
namespace ripple {
|
||||||
|
|
||||||
class TrustSetTransactorLog;
|
class SetTrustLog;
|
||||||
|
|
||||||
template <>
|
template <>
|
||||||
char const*
|
char const*
|
||||||
LogPartition::getPartitionName <TrustSetTransactorLog> ()
|
LogPartition::getPartitionName <SetTrustLog> ()
|
||||||
{
|
{
|
||||||
return "Tx/TrustSet";
|
return "Tx/TrustSet";
|
||||||
}
|
}
|
||||||
|
|
||||||
class TrustSetTransactor
|
class SetTrust
|
||||||
: public Transactor
|
: public Transactor
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
TrustSetTransactor (
|
SetTrust (
|
||||||
SerializedTransaction const& txn,
|
SerializedTransaction const& txn,
|
||||||
TransactionEngineParams params,
|
TransactionEngineParams params,
|
||||||
TransactionEngine* engine)
|
TransactionEngine* engine)
|
||||||
@@ -43,13 +43,23 @@ public:
|
|||||||
txn,
|
txn,
|
||||||
params,
|
params,
|
||||||
engine,
|
engine,
|
||||||
LogPartition::getJournal <TrustSetTransactorLog> ())
|
LogPartition::getJournal <SetTrustLog> ())
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
TER doApply ();
|
TER doApply ();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
inline
|
||||||
|
std::unique_ptr <Transactor>
|
||||||
|
make_SetTrust (
|
||||||
|
SerializedTransaction const& txn,
|
||||||
|
TransactionEngineParams params,
|
||||||
|
TransactionEngine* engine)
|
||||||
|
{
|
||||||
|
return std::make_unique <SetTrust> (txn, params, engine);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -37,36 +37,29 @@ std::unique_ptr<Transactor> Transactor::makeTransactor (
|
|||||||
switch (txn.getTxnType ())
|
switch (txn.getTxnType ())
|
||||||
{
|
{
|
||||||
case ttPAYMENT:
|
case ttPAYMENT:
|
||||||
return std::unique_ptr<Transactor> (
|
return make_Payment (txn, params, engine);
|
||||||
new PaymentTransactor (txn, params, engine));
|
|
||||||
|
|
||||||
case ttACCOUNT_SET:
|
case ttACCOUNT_SET:
|
||||||
return std::unique_ptr<Transactor> (
|
return make_SetAccount (txn, params, engine);
|
||||||
new AccountSetTransactor (txn, params, engine));
|
|
||||||
|
|
||||||
case ttREGULAR_KEY_SET:
|
case ttREGULAR_KEY_SET:
|
||||||
return std::unique_ptr<Transactor> (
|
return make_SetRegularKey (txn, params, engine);
|
||||||
new RegularKeySetTransactor (txn, params, engine));
|
|
||||||
|
|
||||||
case ttTRUST_SET:
|
case ttTRUST_SET:
|
||||||
return std::unique_ptr<Transactor> (
|
return make_SetTrust (txn, params, engine);
|
||||||
new TrustSetTransactor (txn, params, engine));
|
|
||||||
|
|
||||||
case ttOFFER_CREATE:
|
case ttOFFER_CREATE:
|
||||||
return make_OfferCreateTransactor (txn, params, engine);
|
return make_CreateOffer (txn, params, engine);
|
||||||
|
|
||||||
case ttOFFER_CANCEL:
|
case ttOFFER_CANCEL:
|
||||||
return std::unique_ptr<Transactor> (
|
return make_CancelOffer (txn, params, engine);
|
||||||
new OfferCancelTransactor (txn, params, engine));
|
|
||||||
|
|
||||||
case ttWALLET_ADD:
|
case ttWALLET_ADD:
|
||||||
return std::unique_ptr<Transactor> (
|
return make_AddWallet (txn, params, engine);
|
||||||
new WalletAddTransactor (txn, params, engine));
|
|
||||||
|
|
||||||
case ttAMENDMENT:
|
case ttAMENDMENT:
|
||||||
case ttFEE:
|
case ttFEE:
|
||||||
return std::unique_ptr<Transactor> (
|
return make_Change (txn, params, engine);
|
||||||
new ChangeTransactor (txn, params, engine));
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return std::unique_ptr<Transactor> ();
|
return std::unique_ptr<Transactor> ();
|
||||||
@@ -229,9 +222,10 @@ TER Transactor::preCheck ()
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Extract signing key
|
// Extract signing key
|
||||||
// Transactions contain a signing key. This allows us to trivially verify a transaction has at least been properly signed
|
// Transactions contain a signing key. This allows us to trivially verify a
|
||||||
// without going to disk. Each transaction also notes a source account id. This is used to verify that the signing key is
|
// transaction has at least been properly signed without going to disk.
|
||||||
// associated with the account.
|
// Each transaction also notes a source account id. This is used to verify
|
||||||
|
// that the signing key is associated with the account.
|
||||||
// XXX This could be a lot cleaner to prevent unnecessary copying.
|
// XXX This could be a lot cleaner to prevent unnecessary copying.
|
||||||
mSigningPubKey = RippleAddress::createAccountPublic (mTxn.getSigningPubKey ());
|
mSigningPubKey = RippleAddress::createAccountPublic (mTxn.getSigningPubKey ());
|
||||||
|
|
||||||
|
|||||||
@@ -262,25 +262,6 @@ bool parseUrl (const std::string& strUrl, std::string& strScheme, std::string& s
|
|||||||
return bMatch;
|
return bMatch;
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
|
||||||
// Quality parsing
|
|
||||||
// - integers as is.
|
|
||||||
// - floats multiplied by a billion
|
|
||||||
bool parseQuality (const std::string& strSource, std::uint32_t& uQuality)
|
|
||||||
{
|
|
||||||
uQuality = beast::lexicalCast <std::uint32_t> (strSource);
|
|
||||||
|
|
||||||
if (!uQuality)
|
|
||||||
{
|
|
||||||
float fQuality = beast::lexicalCast <float> (strSource);
|
|
||||||
|
|
||||||
if (fQuality)
|
|
||||||
uQuality = (std::uint32_t) (QUALITY_ONE * fQuality);
|
|
||||||
}
|
|
||||||
|
|
||||||
return !!uQuality;
|
|
||||||
}
|
|
||||||
|
|
||||||
beast::StringPairArray parseDelimitedKeyValueString (beast::String parameters,
|
beast::StringPairArray parseDelimitedKeyValueString (beast::String parameters,
|
||||||
beast::beast_wchar delimiter)
|
beast::beast_wchar delimiter)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -111,7 +111,6 @@ Blob strCopy (const std::string& strSrc);
|
|||||||
std::string strCopy (Blob const& vucSrc);
|
std::string strCopy (Blob const& vucSrc);
|
||||||
|
|
||||||
bool parseIpPort (const std::string& strSource, std::string& strIP, int& iPort);
|
bool parseIpPort (const std::string& strSource, std::string& strIP, int& iPort);
|
||||||
bool parseQuality (const std::string& strSource, std::uint32_t& uQuality);
|
|
||||||
|
|
||||||
inline std::string strGetEnv (const std::string& strKey)
|
inline std::string strGetEnv (const std::string& strKey)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -920,6 +920,7 @@ STAmount operator- (const STAmount& v1, const STAmount& v2)
|
|||||||
return STAmount (v1.getFName (), v1.mCurrency, v1.mIssuer, -fv, ov1, true);
|
return STAmount (v1.getFName (), v1.mCurrency, v1.mIssuer, -fv, ov1, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NIKB TODO Make Amount::divide skip math if den == QUALITY_ONE
|
||||||
STAmount STAmount::divide (const STAmount& num, const STAmount& den, const uint160& uCurrencyID, const uint160& uIssuerID)
|
STAmount STAmount::divide (const STAmount& num, const STAmount& den, const uint160& uCurrencyID, const uint160& uIssuerID)
|
||||||
{
|
{
|
||||||
if (den == zero)
|
if (den == zero)
|
||||||
|
|||||||
Reference in New Issue
Block a user