mirror of
https://github.com/XRPLF/rippled.git
synced 2025-11-20 11:05:54 +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
|
||||
#define RIPPLE_TYPES_RIPPLEASSETS_H_INCLUDED
|
||||
|
||||
#include <cassert>
|
||||
#include <functional>
|
||||
#include <type_traits>
|
||||
|
||||
@@ -66,6 +67,9 @@ public:
|
||||
: currency (currency_)
|
||||
, 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>
|
||||
@@ -87,6 +91,7 @@ public:
|
||||
|
||||
bool is_xrp () const
|
||||
{
|
||||
assert (currency.isZero () == issuer.isZero ());
|
||||
if (currency.isZero ())
|
||||
return true;
|
||||
return false;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
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
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
@@ -191,18 +191,14 @@ public:
|
||||
|
||||
if (sig == 0)
|
||||
return os << "0";
|
||||
else if (sig < 0)
|
||||
|
||||
if (sig < 0)
|
||||
os << "-";
|
||||
|
||||
if (m_integral)
|
||||
return os << m_mantissa;
|
||||
|
||||
if (m_exponent != 0 && (m_exponent < -25 || m_exponent > -5))
|
||||
return os << m_mantissa << "e" << m_exponent;
|
||||
|
||||
//if (m_exponent > 0)
|
||||
// return os <<
|
||||
|
||||
return os;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
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
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
@@ -22,6 +22,8 @@
|
||||
|
||||
#include "Amount.h"
|
||||
|
||||
#include "../../beast/beast/utility/noexcept.h"
|
||||
|
||||
namespace ripple {
|
||||
namespace core {
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
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
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
@@ -25,8 +25,7 @@
|
||||
|
||||
#include "../../beast/beast/utility/noexcept.h"
|
||||
|
||||
#include <ostream>
|
||||
#include <utility>
|
||||
#include <functional>
|
||||
|
||||
namespace ripple {
|
||||
namespace core {
|
||||
@@ -54,15 +53,7 @@ private:
|
||||
|
||||
public:
|
||||
/** Create the iterator. */
|
||||
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))
|
||||
{
|
||||
}
|
||||
BookTip (LedgerView& view, BookRef book);
|
||||
|
||||
uint256 const&
|
||||
dir() const noexcept
|
||||
@@ -93,49 +84,7 @@ public:
|
||||
@return `true` if there is a next offer
|
||||
*/
|
||||
bool
|
||||
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;
|
||||
}
|
||||
step ();
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
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
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
@@ -27,6 +27,8 @@
|
||||
#include "../misc/SerializedLedger.h"
|
||||
#include "../../ripple_data/protocol/FieldNames.h"
|
||||
|
||||
#include "../../beast/beast/utility/noexcept.h"
|
||||
|
||||
#include <ostream>
|
||||
|
||||
namespace ripple {
|
||||
@@ -76,12 +78,23 @@ public:
|
||||
Some or all of the out amount may be unfunded.
|
||||
*/
|
||||
Amounts const
|
||||
amount() const noexcept
|
||||
amount() const
|
||||
{
|
||||
return Amounts (m_entry->getFieldAmount (sfTakerPays),
|
||||
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. */
|
||||
// AVOID USING THIS
|
||||
SLE::pointer
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
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
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
@@ -27,8 +27,7 @@
|
||||
|
||||
#include "../../beast/beast/utility/noexcept.h"
|
||||
|
||||
#include <ostream>
|
||||
#include <utility>
|
||||
#include <functional>
|
||||
|
||||
namespace ripple {
|
||||
namespace core {
|
||||
@@ -49,12 +48,10 @@ namespace core {
|
||||
|
||||
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.
|
||||
|
||||
TODO: Remove offers belonging to the taker
|
||||
*/
|
||||
class OfferStream
|
||||
{
|
||||
protected:
|
||||
private:
|
||||
beast::Journal m_journal;
|
||||
std::reference_wrapper <LedgerView> m_view;
|
||||
std::reference_wrapper <LedgerView> m_view_cancel;
|
||||
@@ -63,90 +60,37 @@ protected:
|
||||
BookTip m_tip;
|
||||
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
|
||||
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();
|
||||
}
|
||||
erase (LedgerView& view);
|
||||
|
||||
public:
|
||||
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)
|
||||
{
|
||||
}
|
||||
Clock::time_point when, beast::Journal journal);
|
||||
|
||||
LedgerView&
|
||||
view() noexcept
|
||||
view () noexcept
|
||||
{
|
||||
return m_view;
|
||||
}
|
||||
|
||||
LedgerView&
|
||||
view_cancel() noexcept
|
||||
view_cancel () noexcept
|
||||
{
|
||||
return m_view_cancel;
|
||||
}
|
||||
|
||||
Book const&
|
||||
book() const noexcept
|
||||
book () const noexcept
|
||||
{
|
||||
return m_book;
|
||||
}
|
||||
|
||||
uint256 const&
|
||||
dir() const noexcept
|
||||
{
|
||||
return m_tip.dir();
|
||||
}
|
||||
|
||||
/** Returns the offer at the tip of the order book.
|
||||
Offers are always presented in decreasing quality.
|
||||
Only valid if step() returned `true`.
|
||||
*/
|
||||
Offer const&
|
||||
tip() const
|
||||
tip () const
|
||||
{
|
||||
return m_offer;
|
||||
}
|
||||
@@ -159,166 +103,22 @@ dir() const noexcept
|
||||
@return `true` if there is a valid offer.
|
||||
*/
|
||||
bool
|
||||
step()
|
||||
{
|
||||
// Modifying the order or logic of these
|
||||
// operations causes a protocol breaking change.
|
||||
step ();
|
||||
|
||||
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
|
||||
// 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.
|
||||
/** Advance to the next valid offer that is not from the specified account.
|
||||
This automatically removes:
|
||||
- Offers with missing ledger entries
|
||||
- Offers found unfunded
|
||||
- Offers from the same account
|
||||
- Expired offers
|
||||
@return `true` if there is a valid offer.
|
||||
*/
|
||||
bool
|
||||
fill (Amounts const& remaining_funds)
|
||||
{
|
||||
// Erase the offer if it is fully consumed (in==0 || out==0)
|
||||
// This is the same as becoming unfunded
|
||||
return false;
|
||||
}
|
||||
step_account (Account const& account);
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
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
|
||||
|
||||
|
||||
@@ -23,9 +23,7 @@
|
||||
#include "Amount.h"
|
||||
#include "Amounts.h"
|
||||
|
||||
#include <cassert>
|
||||
#include <cstdint>
|
||||
#include <limits>
|
||||
#include <ostream>
|
||||
|
||||
namespace ripple {
|
||||
@@ -49,57 +47,53 @@ private:
|
||||
public:
|
||||
Quality() = default;
|
||||
|
||||
/** Create a quality from the integer encoding of an Amount */
|
||||
explicit
|
||||
Quality (std::uint64_t value)
|
||||
: m_value (value)
|
||||
{
|
||||
}
|
||||
Quality (std::uint64_t value);
|
||||
|
||||
/** Create a quality from the ratio of two amounts. */
|
||||
explicit
|
||||
Quality (Amounts const& amount)
|
||||
: m_value (Amount::getRate (amount.out, amount.in))
|
||||
{
|
||||
}
|
||||
Quality (Amounts const& amount);
|
||||
|
||||
/** Advances to the next higher quality level. */
|
||||
/** @{ */
|
||||
Quality&
|
||||
operator++()
|
||||
{
|
||||
assert (m_value > 0);
|
||||
--m_value;
|
||||
return *this;
|
||||
}
|
||||
operator++();
|
||||
|
||||
Quality
|
||||
operator++ (int)
|
||||
{
|
||||
Quality prev (*this);
|
||||
--*this;
|
||||
return prev;
|
||||
}
|
||||
operator++ (int);
|
||||
/** @} */
|
||||
|
||||
/** Advances to the next lower quality level. */
|
||||
/** @{ */
|
||||
Quality&
|
||||
operator--()
|
||||
{
|
||||
assert (m_value < std::numeric_limits<value_type>::max());
|
||||
++m_value;
|
||||
return *this;
|
||||
}
|
||||
operator--();
|
||||
|
||||
Quality
|
||||
operator-- (int)
|
||||
{
|
||||
Quality prev (*this);
|
||||
++*this;
|
||||
return prev;
|
||||
}
|
||||
operator-- (int);
|
||||
/** @} */
|
||||
|
||||
/** 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`.
|
||||
Lower quality means the taker receives a worse deal.
|
||||
Higher quality is better for the taker.
|
||||
@@ -118,6 +112,13 @@ public:
|
||||
return lhs.m_value == rhs.m_value;
|
||||
}
|
||||
|
||||
friend
|
||||
bool
|
||||
operator!= (Quality const& lhs, Quality const& rhs) noexcept
|
||||
{
|
||||
return ! (lhs == rhs);
|
||||
}
|
||||
|
||||
friend
|
||||
std::ostream&
|
||||
operator<< (std::ostream& os, Quality const& quality)
|
||||
@@ -125,72 +126,14 @@ public:
|
||||
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
|
||||
operator!= (Quality const& lhs, Quality const& rhs) noexcept
|
||||
{
|
||||
return ! (lhs == rhs);
|
||||
}
|
||||
/** 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
|
||||
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
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
@@ -22,11 +22,14 @@
|
||||
|
||||
#include "Amounts.h"
|
||||
#include "Quality.h"
|
||||
#include "Offer.h"
|
||||
#include "Types.h"
|
||||
|
||||
#include "../../beast/beast/streams/debug_ostream.h"
|
||||
#include "../../beast/beast/utility/noexcept.h"
|
||||
|
||||
#include <utility>
|
||||
#include <functional>
|
||||
//#include <utility>
|
||||
|
||||
namespace ripple {
|
||||
namespace core {
|
||||
@@ -37,7 +40,7 @@ class Taker
|
||||
public:
|
||||
struct Options
|
||||
{
|
||||
Options() = default;
|
||||
Options() = delete;
|
||||
|
||||
explicit
|
||||
Options (std::uint32_t tx_flags)
|
||||
@@ -56,99 +59,54 @@ public:
|
||||
|
||||
private:
|
||||
std::reference_wrapper <LedgerView> m_view;
|
||||
Book m_book;
|
||||
Account m_account;
|
||||
Options m_options;
|
||||
Quality m_quality;
|
||||
Quality m_threshold;
|
||||
|
||||
// The original in and out quantities
|
||||
Amounts m_amount;
|
||||
// The original in and out quantities.
|
||||
Amounts const m_amount;
|
||||
|
||||
// Amount of input currency remaining.
|
||||
Amount m_in;
|
||||
// The amounts still left over for us to try and take.
|
||||
Amounts m_remain;
|
||||
|
||||
// Amount of output currency we have received.
|
||||
Amount m_out;
|
||||
private:
|
||||
Amounts
|
||||
flow (Amounts amount, Offer const& offer, Account const& taker);
|
||||
|
||||
// Returns the balance of the taker's input currency,
|
||||
Amount
|
||||
funds() const
|
||||
{
|
||||
return view().accountFunds (account(), m_in);
|
||||
}
|
||||
TER
|
||||
fill (Offer const& offer, Amounts const& amount);
|
||||
|
||||
TER
|
||||
fill (Offer const& leg1, Amounts const& amount1,
|
||||
Offer const& leg2, Amounts const& amount2);
|
||||
|
||||
public:
|
||||
Taker (LedgerView& view, BookRef const& book,
|
||||
Account const& account, Amounts const& amount,
|
||||
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;
|
||||
}
|
||||
Taker (LedgerView& view, Account const& account,
|
||||
Amounts const& amount, Options const& options);
|
||||
|
||||
LedgerView&
|
||||
view() const noexcept
|
||||
view () const noexcept
|
||||
{
|
||||
return m_view;
|
||||
}
|
||||
|
||||
/** Returns the input and output asset pair identifier. */
|
||||
Book const&
|
||||
book() const noexcept
|
||||
{
|
||||
return m_book;
|
||||
}
|
||||
/** Returns the amount remaining on the offer.
|
||||
This is the amount at which the offer should be placed. It may either
|
||||
be for the full amount when there were no crossing offers, or for zero
|
||||
when the offer fully crossed, or any amount in between.
|
||||
It is always at the original offer quality (m_quality)
|
||||
*/
|
||||
Amounts
|
||||
remaining_offer () const;
|
||||
|
||||
/** Returns the account identifier of the taker. */
|
||||
Account const&
|
||||
account() const noexcept
|
||||
account () const noexcept
|
||||
{
|
||||
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. */
|
||||
bool
|
||||
reject (Quality const& quality) const noexcept
|
||||
@@ -156,138 +114,26 @@ public:
|
||||
return quality < m_threshold;
|
||||
}
|
||||
|
||||
/** Calcualtes the result of applying the taker's funds to the offer.
|
||||
@return The flow and flag indicating if the order was consumed.
|
||||
/** 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.
|
||||
*/
|
||||
std::pair <Amounts, bool>
|
||||
fill (Offer const& offer) const
|
||||
{
|
||||
// Best flow the owner can get.
|
||||
// Start out assuming entire offer will flow.
|
||||
Amounts owner_amount (offer.amount());
|
||||
bool
|
||||
done () const;
|
||||
|
||||
// Limit owner's output by available funds less fees
|
||||
|
||||
// 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).
|
||||
/** Perform direct crossing through given offer.
|
||||
@return tesSUCCESS on success, error code otherwise.
|
||||
*/
|
||||
TER
|
||||
process (Amounts const& flow, Offer const& offer)
|
||||
{
|
||||
TER result (tesSUCCESS);
|
||||
cross (Offer const& offer);
|
||||
|
||||
// VFALCO For the case of !sell, is it possible for the taker
|
||||
// to get a tiny bit more than he asked for?
|
||||
// DAVIDS Can you verify?
|
||||
assert (m_options.sell || flow.out <= m_amount.out);
|
||||
|
||||
// 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;
|
||||
}
|
||||
/** Perform bridged crossing through given offers.
|
||||
@return tesSUCCESS on success, error code otherwise.
|
||||
*/
|
||||
TER
|
||||
cross (Offer const& leg1, Offer const& leg2);
|
||||
};
|
||||
|
||||
|
||||
inline
|
||||
std::ostream&
|
||||
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
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
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
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
@@ -22,7 +22,7 @@
|
||||
|
||||
#include "../ledger/LedgerEntrySet.h"
|
||||
#include "../../ripple/types/api/RippleAssets.h"
|
||||
#include "../../ripple/types/api/UInt160.h"
|
||||
#include "../../ripple/types/api/base_uint.h"
|
||||
|
||||
#include <chrono>
|
||||
#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;
|
||||
}
|
||||
|
||||
if (isTefFailure (result) || isTemMalformed
|
||||
(result) || isTelLocal (result))
|
||||
if (isTefFailure (result) || isTemMalformed (result) ||
|
||||
isTelLocal (result))
|
||||
{
|
||||
// failure
|
||||
WriteLog (lsDEBUG, LedgerConsensus)
|
||||
|
||||
@@ -21,6 +21,11 @@
|
||||
|
||||
#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/Change.cpp"
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
|
||||
namespace ripple {
|
||||
|
||||
TER WalletAddTransactor::doApply ()
|
||||
TER AddWallet::doApply ()
|
||||
{
|
||||
std::uint32_t const uTxFlags = mTxn.getFlags ();
|
||||
|
||||
|
||||
@@ -22,20 +22,20 @@
|
||||
|
||||
namespace ripple {
|
||||
|
||||
class WalletAddTransactorLog;
|
||||
class AddWalletLog;
|
||||
|
||||
template <>
|
||||
char const*
|
||||
LogPartition::getPartitionName <WalletAddTransactorLog> ()
|
||||
LogPartition::getPartitionName <AddWalletLog> ()
|
||||
{
|
||||
return "Tx/WalletAdd";
|
||||
}
|
||||
|
||||
class WalletAddTransactor
|
||||
class AddWallet
|
||||
: public Transactor
|
||||
{
|
||||
public:
|
||||
WalletAddTransactor (
|
||||
AddWallet (
|
||||
SerializedTransaction const& txn,
|
||||
TransactionEngineParams params,
|
||||
TransactionEngine* engine)
|
||||
@@ -43,13 +43,23 @@ public:
|
||||
txn,
|
||||
params,
|
||||
engine,
|
||||
LogPartition::getJournal <WalletAddTransactorLog> ())
|
||||
LogPartition::getJournal <AddWalletLog> ())
|
||||
{
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
namespace ripple {
|
||||
|
||||
|
||||
TER OfferCancelTransactor::doApply ()
|
||||
TER CancelOffer::doApply ()
|
||||
{
|
||||
std::uint32_t const uOfferSequence = mTxn.getFieldU32 (sfOfferSequence);
|
||||
std::uint32_t const uAccountSequenceNext = mTxnAccount->getFieldU32 (sfSequence);
|
||||
|
||||
@@ -22,20 +22,20 @@
|
||||
|
||||
namespace ripple {
|
||||
|
||||
class OfferCancelTransactorLog;
|
||||
class CancelOfferLog;
|
||||
|
||||
template <>
|
||||
char const*
|
||||
LogPartition::getPartitionName <OfferCancelTransactorLog> ()
|
||||
LogPartition::getPartitionName <CancelOfferLog> ()
|
||||
{
|
||||
return "Tx/OfferCancel";
|
||||
}
|
||||
|
||||
class OfferCancelTransactor
|
||||
class CancelOffer
|
||||
: public Transactor
|
||||
{
|
||||
public:
|
||||
OfferCancelTransactor (
|
||||
CancelOffer (
|
||||
SerializedTransaction const& txn,
|
||||
TransactionEngineParams params,
|
||||
TransactionEngine* engine)
|
||||
@@ -43,7 +43,7 @@ public:
|
||||
txn,
|
||||
params,
|
||||
engine,
|
||||
LogPartition::getJournal <OfferCancelTransactorLog> ())
|
||||
LogPartition::getJournal <CancelOfferLog> ())
|
||||
{
|
||||
|
||||
}
|
||||
@@ -51,6 +51,16 @@ public:
|
||||
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
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
|
||||
namespace ripple {
|
||||
|
||||
TER ChangeTransactor::doApply ()
|
||||
TER Change::doApply ()
|
||||
{
|
||||
if (mTxn.getTxnType () == ttAMENDMENT)
|
||||
return applyAmendment ();
|
||||
@@ -30,7 +30,7 @@ TER ChangeTransactor::doApply ()
|
||||
return temUNKNOWN;
|
||||
}
|
||||
|
||||
TER ChangeTransactor::checkSig ()
|
||||
TER Change::checkSig ()
|
||||
{
|
||||
if (mTxn.getFieldAccount160 (sfAccount).isNonZero ())
|
||||
{
|
||||
@@ -47,7 +47,7 @@ TER ChangeTransactor::checkSig ()
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
TER ChangeTransactor::checkSeq ()
|
||||
TER Change::checkSeq ()
|
||||
{
|
||||
if ((mTxn.getSequence () != 0) || mTxn.isFieldPresent (sfPreviousTxnID))
|
||||
{
|
||||
@@ -58,7 +58,7 @@ TER ChangeTransactor::checkSeq ()
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
TER ChangeTransactor::payFee ()
|
||||
TER Change::payFee ()
|
||||
{
|
||||
if (mTxn.getTransactionFee () != STAmount ())
|
||||
{
|
||||
@@ -69,7 +69,7 @@ TER ChangeTransactor::payFee ()
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
TER ChangeTransactor::preCheck ()
|
||||
TER Change::preCheck ()
|
||||
{
|
||||
mTxnAccountID = mTxn.getSourceAccount ().getAccountID ();
|
||||
|
||||
@@ -89,7 +89,7 @@ TER ChangeTransactor::preCheck ()
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
TER ChangeTransactor::applyAmendment ()
|
||||
TER Change::applyAmendment ()
|
||||
{
|
||||
uint256 amendment (mTxn.getFieldH256 (sfAmendment));
|
||||
|
||||
@@ -119,7 +119,7 @@ TER ChangeTransactor::applyAmendment ()
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
TER ChangeTransactor::applyFee ()
|
||||
TER Change::applyFee ()
|
||||
{
|
||||
|
||||
SLE::pointer feeObject = mEngine->entryCache (
|
||||
|
||||
@@ -22,20 +22,20 @@
|
||||
|
||||
namespace ripple {
|
||||
|
||||
class ChangeTransactorLog;
|
||||
class ChangeLog;
|
||||
|
||||
template <>
|
||||
char const*
|
||||
LogPartition::getPartitionName <ChangeTransactorLog> ()
|
||||
LogPartition::getPartitionName <ChangeLog> ()
|
||||
{
|
||||
return "Tx/Change";
|
||||
}
|
||||
|
||||
class ChangeTransactor
|
||||
class Change
|
||||
: public Transactor
|
||||
{
|
||||
public:
|
||||
ChangeTransactor (
|
||||
Change (
|
||||
SerializedTransaction const& txn,
|
||||
TransactionEngineParams params,
|
||||
TransactionEngine* engine)
|
||||
@@ -43,7 +43,7 @@ public:
|
||||
txn,
|
||||
params,
|
||||
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
|
||||
|
||||
@@ -26,10 +26,11 @@
|
||||
|
||||
#include "../book/OfferStream.h"
|
||||
#include "../book/Taker.h"
|
||||
#include "../book/Types.h"
|
||||
|
||||
namespace ripple {
|
||||
|
||||
OfferCreateTransactor::OfferCreateTransactor (
|
||||
CreateOffer::CreateOffer (
|
||||
SerializedTransaction const& txn,
|
||||
TransactionEngineParams params,
|
||||
TransactionEngine* engine)
|
||||
@@ -37,31 +38,495 @@ OfferCreateTransactor::OfferCreateTransactor (
|
||||
txn,
|
||||
params,
|
||||
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,
|
||||
TransactionEngineParams params,
|
||||
TransactionEngine* engine)
|
||||
{
|
||||
#if RIPPLE_USE_OLD_CREATE_TRANSACTOR
|
||||
return std::make_unique <ClassicOfferCreateTransactor> (txn, params, engine);
|
||||
return std::make_unique <CreateOfferLegacy> (txn, params, engine);
|
||||
#else
|
||||
STAmount const& amount_in = txn.getFieldAmount (sfTakerPays);
|
||||
STAmount const& amount_out = txn.getFieldAmount (sfTakerGets);
|
||||
|
||||
// Autobridging is only in effect when an offer does not involve XRP
|
||||
if (!amount_in.isNative() && !amount_out.isNative ())
|
||||
{
|
||||
// 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 <CreateOfferBridged> (txn, params, engine);
|
||||
|
||||
return std::make_unique <DirectOfferCreateTransactor> (txn, params, engine);
|
||||
return std::make_unique <CreateOfferDirect> (txn, params, engine);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
@@ -20,45 +20,53 @@
|
||||
#ifndef 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"
|
||||
|
||||
namespace ripple {
|
||||
|
||||
class OfferCreateTransactorLog;
|
||||
class CreateOfferLog;
|
||||
|
||||
template <>
|
||||
char const*
|
||||
LogPartition::getPartitionName <OfferCreateTransactorLog> ()
|
||||
LogPartition::getPartitionName <CreateOfferLog> ()
|
||||
{
|
||||
return "Tx/OfferCreate";
|
||||
}
|
||||
|
||||
class OfferCreateTransactor
|
||||
class CreateOffer
|
||||
: public Transactor
|
||||
{
|
||||
private:
|
||||
template <class T>
|
||||
static std::string
|
||||
get_compare_sign (T const& lhs, T const& rhs)
|
||||
{
|
||||
if (lhs > rhs)
|
||||
return ">";
|
||||
protected:
|
||||
/** Determine if we are authorized to hold the asset we want to get */
|
||||
TER checkAcceptAsset(core::AssetRef asset) const;
|
||||
|
||||
if (rhs > lhs)
|
||||
return "<";
|
||||
/** Fill offer as much as possible by consuming offers already on the books.
|
||||
We adjusts account balances and charges fees on top to taker.
|
||||
|
||||
// If neither is bigger than the other, they must be equal
|
||||
return "=";
|
||||
}
|
||||
@param taker_amount.in How much the taker offers
|
||||
@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:
|
||||
OfferCreateTransactor (
|
||||
CreateOffer (
|
||||
SerializedTransaction const& txn,
|
||||
TransactionEngineParams params,
|
||||
TransactionEngine* engine);
|
||||
|
||||
TER doApply () override;
|
||||
};
|
||||
|
||||
std::unique_ptr <Transactor> make_OfferCreateTransactor (
|
||||
std::unique_ptr <Transactor> make_CreateOffer (
|
||||
SerializedTransaction const& txn,
|
||||
TransactionEngineParams params,
|
||||
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
|
||||
#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
|
||||
|
||||
@@ -23,38 +23,38 @@
|
||||
|
||||
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
|
||||
std::pair<TER,bool>
|
||||
process_order (
|
||||
@param taker_amount.in How much the taker offers
|
||||
@param taker_amount.out How much the taker wants
|
||||
@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::BookRef const book,
|
||||
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)
|
||||
core::Amounts const& taker_amount)
|
||||
{
|
||||
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::OfferStream offers (view, view_cancel, book, when, journal);
|
||||
core::Taker taker (offers.view(), book, account, amount, options);
|
||||
core::OfferStream offers (view, view_cancel,
|
||||
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 <<
|
||||
"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);
|
||||
TER cross_result (tesSUCCESS);
|
||||
|
||||
while (true)
|
||||
{
|
||||
@@ -67,27 +67,22 @@ process_order (
|
||||
|
||||
if (taker.done())
|
||||
{
|
||||
journal.debug << "The taker reports he's done during crossing!";
|
||||
place_order = false;
|
||||
m_journal.debug << "The taker reports he's done during crossing!";
|
||||
break;
|
||||
}
|
||||
|
||||
if (! offers.step())
|
||||
// NIKB CHECKME Investigate whether we can use offer.step_account() here
|
||||
// or whether doing so would cause a protocol-breaking
|
||||
// change.
|
||||
if (! offers.step ())
|
||||
{
|
||||
// Place the order since there are no
|
||||
// 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;
|
||||
}
|
||||
|
||||
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();
|
||||
auto const& offer (offers.tip());
|
||||
|
||||
if (taker.reject (offer.quality()))
|
||||
{
|
||||
@@ -98,592 +93,25 @@ process_order (
|
||||
|
||||
if (offer.account() == taker.account())
|
||||
{
|
||||
if (journal.debug) journal.debug <<
|
||||
" skipping self-offer " << offer.entry()->getIndex() << std::endl <<
|
||||
" 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)
|
||||
// Skip offer from self. The offer will be considered expired and
|
||||
// will get deleted.
|
||||
continue;
|
||||
}
|
||||
|
||||
if (journal.debug) journal.debug <<
|
||||
" offer " << offer.entry()->getIndex() << std::endl <<
|
||||
" pays/gets " << offer.amount().in << ", " << offer.amount().out
|
||||
;
|
||||
if (m_journal.debug) m_journal.debug <<
|
||||
" Offer: " << offer.entry()->getIndex() << std::endl <<
|
||||
" " << offer.amount().in << " : " << offer.amount().out;
|
||||
|
||||
core::Amounts flow;
|
||||
bool consumed;
|
||||
std::tie (flow, consumed) = taker.fill (offer);
|
||||
cross_result = taker.cross (offer);
|
||||
|
||||
result = taker.process (flow, offer);
|
||||
|
||||
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)
|
||||
if (cross_result != tesSUCCESS)
|
||||
{
|
||||
// VFALCO TODO Return the tec and let a caller higher
|
||||
// up convert the error if the ledger is open.
|
||||
//result = bOpenLedger ?
|
||||
// telFAILED_PROCESSING : tecFAILED_PROCESSING;
|
||||
result = tecFAILED_PROCESSING;
|
||||
cross_result = tecFAILED_PROCESSING;
|
||||
break;
|
||||
}
|
||||
|
||||
cross_flow.in += flow.in;
|
||||
cross_flow.out += flow.out;
|
||||
}
|
||||
|
||||
if (result == tesSUCCESS)
|
||||
{
|
||||
// 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;
|
||||
return std::make_pair(cross_result, taker.remaining_offer ());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -20,22 +20,21 @@
|
||||
#ifndef RIPPLE_TX_DIRECT_OFFERCREATE_H_INCLUDED
|
||||
#define RIPPLE_TX_DIRECT_OFFERCREATE_H_INCLUDED
|
||||
|
||||
#include "../book/OfferStream.h"
|
||||
#include "../book/Taker.h"
|
||||
|
||||
#include "../book/Amounts.h"
|
||||
|
||||
#include <unordered_set>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
class DirectOfferCreateTransactor
|
||||
: public OfferCreateTransactor
|
||||
class CreateOfferDirect
|
||||
: public CreateOffer
|
||||
{
|
||||
public:
|
||||
DirectOfferCreateTransactor (
|
||||
CreateOfferDirect (
|
||||
SerializedTransaction const& txn,
|
||||
TransactionEngineParams params,
|
||||
TransactionEngine* engine)
|
||||
: OfferCreateTransactor (
|
||||
: CreateOffer (
|
||||
txn,
|
||||
params,
|
||||
engine)
|
||||
@@ -43,15 +42,10 @@ public:
|
||||
|
||||
}
|
||||
|
||||
TER doApply () override;
|
||||
|
||||
private:
|
||||
std::pair<TER,bool> crossOffers (
|
||||
std::pair<TER, core::Amounts> crossOffers (
|
||||
core::LedgerView& view,
|
||||
STAmount const& saTakerPays,
|
||||
STAmount const& saTakerGets,
|
||||
STAmount& saTakerPaid,
|
||||
STAmount& saTakerGot);
|
||||
core::Amounts const& taker_amount) override;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ namespace ripple {
|
||||
/** Determine if an order is still valid
|
||||
If the order is not valid it will be marked as unfunded.
|
||||
*/
|
||||
bool ClassicOfferCreateTransactor::isValidOffer (
|
||||
bool CreateOfferLegacy::isValidOffer (
|
||||
SLE::ref sleOffer,
|
||||
uint160 const& uOfferOwnerID,
|
||||
STAmount const& saOfferPays,
|
||||
@@ -97,7 +97,7 @@ bool ClassicOfferCreateTransactor::isValidOffer (
|
||||
|
||||
/**
|
||||
*/
|
||||
bool ClassicOfferCreateTransactor::canCross (
|
||||
bool CreateOfferLegacy::canCross (
|
||||
STAmount const& saTakerFunds,
|
||||
STAmount const& saSubTakerPays,
|
||||
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
|
||||
original rate.
|
||||
*/
|
||||
bool ClassicOfferCreateTransactor::applyOffer (
|
||||
bool CreateOfferLegacy::applyOffer (
|
||||
const bool bSell,
|
||||
const std::uint32_t uTakerPaysRate, const std::uint32_t uOfferPaysRate,
|
||||
const STAmount& saOfferRate,
|
||||
@@ -327,7 +327,7 @@ bool ClassicOfferCreateTransactor::applyOffer (
|
||||
@return tesSUCCESS, terNO_ACCOUNT, telFAILED_PROCESSING, or
|
||||
tecFAILED_PROCESSING
|
||||
*/
|
||||
TER ClassicOfferCreateTransactor::takeOffers (
|
||||
TER CreateOfferLegacy::takeOffers (
|
||||
const bool bOpenLedger,
|
||||
const bool bPassive,
|
||||
const bool bSell,
|
||||
@@ -671,7 +671,7 @@ TER ClassicOfferCreateTransactor::takeOffers (
|
||||
return terResult;
|
||||
}
|
||||
|
||||
TER ClassicOfferCreateTransactor::doApply ()
|
||||
TER CreateOfferLegacy::doApply ()
|
||||
{
|
||||
if (m_journal.debug) m_journal.debug <<
|
||||
"OfferCreate> " << mTxn.getJson (0);
|
||||
|
||||
@@ -24,30 +24,15 @@
|
||||
|
||||
namespace ripple {
|
||||
|
||||
class ClassicOfferCreateTransactor
|
||||
: public OfferCreateTransactor
|
||||
class CreateOfferLegacy
|
||||
: 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:
|
||||
ClassicOfferCreateTransactor (
|
||||
CreateOfferLegacy (
|
||||
SerializedTransaction const& txn,
|
||||
TransactionEngineParams params,
|
||||
TransactionEngine* engine)
|
||||
: OfferCreateTransactor (
|
||||
: CreateOffer (
|
||||
txn,
|
||||
params,
|
||||
engine)
|
||||
@@ -57,6 +42,13 @@ public:
|
||||
|
||||
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:
|
||||
bool isValidOffer (
|
||||
SLE::ref sleOfferDir,
|
||||
|
||||
@@ -21,7 +21,7 @@ namespace ripple {
|
||||
|
||||
// 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.
|
||||
std::uint32_t const uTxFlags = mTxn.getFlags ();
|
||||
|
||||
@@ -22,23 +22,23 @@
|
||||
|
||||
namespace ripple {
|
||||
|
||||
class PaymentTransactorLog;
|
||||
class PaymentLog;
|
||||
|
||||
template <>
|
||||
char const*
|
||||
LogPartition::getPartitionName <PaymentTransactorLog> ()
|
||||
LogPartition::getPartitionName <PaymentLog> ()
|
||||
{
|
||||
return "Tx/Payment";
|
||||
}
|
||||
|
||||
class PaymentTransactor
|
||||
class Payment
|
||||
: public Transactor
|
||||
{
|
||||
/* The largest number of paths we allow */
|
||||
static std::size_t const MaxPathSize = 6;
|
||||
|
||||
public:
|
||||
PaymentTransactor (
|
||||
Payment (
|
||||
SerializedTransaction const& txn,
|
||||
TransactionEngineParams params,
|
||||
TransactionEngine* engine)
|
||||
@@ -46,7 +46,7 @@ public:
|
||||
txn,
|
||||
params,
|
||||
engine,
|
||||
LogPartition::getJournal <PaymentTransactorLog> ())
|
||||
LogPartition::getJournal <PaymentLog> ())
|
||||
{
|
||||
|
||||
}
|
||||
@@ -54,6 +54,16 @@ public:
|
||||
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
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
|
||||
namespace ripple {
|
||||
|
||||
TER AccountSetTransactor::doApply ()
|
||||
TER SetAccount::doApply ()
|
||||
{
|
||||
std::uint32_t const uTxFlags = mTxn.getFlags ();
|
||||
|
||||
|
||||
@@ -22,20 +22,20 @@
|
||||
|
||||
namespace ripple {
|
||||
|
||||
class AccountSetTransactorLog;
|
||||
class SetAccountLog;
|
||||
|
||||
template <>
|
||||
char const*
|
||||
LogPartition::getPartitionName <AccountSetTransactorLog> ()
|
||||
LogPartition::getPartitionName <SetAccountLog> ()
|
||||
{
|
||||
return "Tx/AccountSet";
|
||||
}
|
||||
|
||||
class AccountSetTransactor
|
||||
class SetAccount
|
||||
: public Transactor
|
||||
{
|
||||
public:
|
||||
AccountSetTransactor (
|
||||
SetAccount (
|
||||
SerializedTransaction const& txn,
|
||||
TransactionEngineParams params,
|
||||
TransactionEngine* engine)
|
||||
@@ -43,7 +43,7 @@ public:
|
||||
txn,
|
||||
params,
|
||||
engine,
|
||||
LogPartition::getJournal <AccountSetTransactorLog> ())
|
||||
LogPartition::getJournal <SetAccountLog> ())
|
||||
{
|
||||
|
||||
}
|
||||
@@ -51,6 +51,16 @@ public:
|
||||
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
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
|
||||
namespace ripple {
|
||||
|
||||
std::uint64_t RegularKeySetTransactor::calculateBaseFee ()
|
||||
std::uint64_t SetRegularKey::calculateBaseFee ()
|
||||
{
|
||||
if ( mTxnAccount
|
||||
&& (! (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 ();
|
||||
|
||||
|
||||
@@ -22,22 +22,22 @@
|
||||
|
||||
namespace ripple {
|
||||
|
||||
class RegularKeySetTransactorLog;
|
||||
class SetRegularKeyLog;
|
||||
|
||||
template <>
|
||||
char const*
|
||||
LogPartition::getPartitionName <RegularKeySetTransactorLog> ()
|
||||
LogPartition::getPartitionName <SetRegularKeyLog> ()
|
||||
{
|
||||
return "Tx/RegularKeySet";
|
||||
}
|
||||
|
||||
class RegularKeySetTransactor
|
||||
class SetRegularKey
|
||||
: public Transactor
|
||||
{
|
||||
std::uint64_t calculateBaseFee ();
|
||||
|
||||
public:
|
||||
RegularKeySetTransactor (
|
||||
SetRegularKey (
|
||||
SerializedTransaction const& txn,
|
||||
TransactionEngineParams params,
|
||||
TransactionEngine* engine)
|
||||
@@ -45,7 +45,7 @@ public:
|
||||
txn,
|
||||
params,
|
||||
engine,
|
||||
LogPartition::getJournal <RegularKeySetTransactorLog> ())
|
||||
LogPartition::getJournal <SetRegularKeyLog> ())
|
||||
{
|
||||
|
||||
}
|
||||
@@ -54,6 +54,16 @@ public:
|
||||
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
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
|
||||
namespace ripple {
|
||||
|
||||
TER TrustSetTransactor::doApply ()
|
||||
TER SetTrust::doApply ()
|
||||
{
|
||||
TER terResult = tesSUCCESS;
|
||||
|
||||
|
||||
@@ -22,20 +22,20 @@
|
||||
|
||||
namespace ripple {
|
||||
|
||||
class TrustSetTransactorLog;
|
||||
class SetTrustLog;
|
||||
|
||||
template <>
|
||||
char const*
|
||||
LogPartition::getPartitionName <TrustSetTransactorLog> ()
|
||||
LogPartition::getPartitionName <SetTrustLog> ()
|
||||
{
|
||||
return "Tx/TrustSet";
|
||||
}
|
||||
|
||||
class TrustSetTransactor
|
||||
class SetTrust
|
||||
: public Transactor
|
||||
{
|
||||
public:
|
||||
TrustSetTransactor (
|
||||
SetTrust (
|
||||
SerializedTransaction const& txn,
|
||||
TransactionEngineParams params,
|
||||
TransactionEngine* engine)
|
||||
@@ -43,13 +43,23 @@ public:
|
||||
txn,
|
||||
params,
|
||||
engine,
|
||||
LogPartition::getJournal <TrustSetTransactorLog> ())
|
||||
LogPartition::getJournal <SetTrustLog> ())
|
||||
{
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
@@ -37,36 +37,29 @@ std::unique_ptr<Transactor> Transactor::makeTransactor (
|
||||
switch (txn.getTxnType ())
|
||||
{
|
||||
case ttPAYMENT:
|
||||
return std::unique_ptr<Transactor> (
|
||||
new PaymentTransactor (txn, params, engine));
|
||||
return make_Payment (txn, params, engine);
|
||||
|
||||
case ttACCOUNT_SET:
|
||||
return std::unique_ptr<Transactor> (
|
||||
new AccountSetTransactor (txn, params, engine));
|
||||
return make_SetAccount (txn, params, engine);
|
||||
|
||||
case ttREGULAR_KEY_SET:
|
||||
return std::unique_ptr<Transactor> (
|
||||
new RegularKeySetTransactor (txn, params, engine));
|
||||
return make_SetRegularKey (txn, params, engine);
|
||||
|
||||
case ttTRUST_SET:
|
||||
return std::unique_ptr<Transactor> (
|
||||
new TrustSetTransactor (txn, params, engine));
|
||||
return make_SetTrust (txn, params, engine);
|
||||
|
||||
case ttOFFER_CREATE:
|
||||
return make_OfferCreateTransactor (txn, params, engine);
|
||||
return make_CreateOffer (txn, params, engine);
|
||||
|
||||
case ttOFFER_CANCEL:
|
||||
return std::unique_ptr<Transactor> (
|
||||
new OfferCancelTransactor (txn, params, engine));
|
||||
return make_CancelOffer (txn, params, engine);
|
||||
|
||||
case ttWALLET_ADD:
|
||||
return std::unique_ptr<Transactor> (
|
||||
new WalletAddTransactor (txn, params, engine));
|
||||
return make_AddWallet (txn, params, engine);
|
||||
|
||||
case ttAMENDMENT:
|
||||
case ttFEE:
|
||||
return std::unique_ptr<Transactor> (
|
||||
new ChangeTransactor (txn, params, engine));
|
||||
return make_Change (txn, params, engine);
|
||||
|
||||
default:
|
||||
return std::unique_ptr<Transactor> ();
|
||||
@@ -229,11 +222,12 @@ TER Transactor::preCheck ()
|
||||
}
|
||||
|
||||
// Extract signing key
|
||||
// Transactions contain a signing key. This allows us to trivially verify a transaction has at least been properly signed
|
||||
// without going to disk. Each transaction also notes a source account id. This is used to verify that the signing key is
|
||||
// associated with the account.
|
||||
// Transactions contain a signing key. This allows us to trivially verify a
|
||||
// transaction has at least been properly signed without going to disk.
|
||||
// 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.
|
||||
mSigningPubKey = RippleAddress::createAccountPublic (mTxn.getSigningPubKey ());
|
||||
mSigningPubKey = RippleAddress::createAccountPublic (mTxn.getSigningPubKey ());
|
||||
|
||||
// Consistency: really signed.
|
||||
if (!mTxn.isKnownGood ())
|
||||
|
||||
@@ -262,25 +262,6 @@ bool parseUrl (const std::string& strUrl, std::string& strScheme, std::string& s
|
||||
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::beast_wchar delimiter)
|
||||
{
|
||||
|
||||
@@ -111,7 +111,6 @@ Blob strCopy (const std::string& strSrc);
|
||||
std::string strCopy (Blob const& vucSrc);
|
||||
|
||||
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)
|
||||
{
|
||||
|
||||
@@ -920,6 +920,7 @@ STAmount operator- (const STAmount& v1, const STAmount& v2)
|
||||
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)
|
||||
{
|
||||
if (den == zero)
|
||||
|
||||
Reference in New Issue
Block a user