mirror of
https://github.com/XRPLF/rippled.git
synced 2025-12-06 17:27:55 +00:00
372 lines
16 KiB
C++
372 lines
16 KiB
C++
//------------------------------------------------------------------------------
|
|
/*
|
|
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 <ripple/app/tx/impl/Taker.h>
|
|
#include <ripple/beast/unit_test.h>
|
|
#include <ripple/beast/core/LexicalCast.h>
|
|
#include <type_traits>
|
|
|
|
namespace ripple {
|
|
|
|
class Taker_test : public beast::unit_test::suite
|
|
{
|
|
static bool const Buy = false;
|
|
static bool const Sell = true;
|
|
|
|
class TestTaker
|
|
: public BasicTaker
|
|
{
|
|
STAmount funds_;
|
|
STAmount cross_funds;
|
|
|
|
public:
|
|
TestTaker (
|
|
CrossType cross_type,
|
|
Amounts const& amount,
|
|
Quality const& quality,
|
|
STAmount const& funds,
|
|
std::uint32_t flags,
|
|
Rate const& rate_in,
|
|
Rate const& rate_out)
|
|
: BasicTaker (
|
|
cross_type,
|
|
AccountID(0x4701),
|
|
amount,
|
|
quality,
|
|
flags,
|
|
rate_in,
|
|
rate_out)
|
|
, funds_ (funds)
|
|
{
|
|
}
|
|
|
|
void
|
|
set_funds (STAmount const& funds)
|
|
{
|
|
cross_funds = funds;
|
|
}
|
|
|
|
STAmount
|
|
get_funds (AccountID const& owner, STAmount const& funds) const override
|
|
{
|
|
if (owner == account ())
|
|
return funds_;
|
|
|
|
return cross_funds;
|
|
}
|
|
|
|
Amounts
|
|
cross (Amounts offer, Quality quality)
|
|
{
|
|
if (reject (quality))
|
|
return Amounts (offer.in.zeroed (), offer.out.zeroed ());
|
|
|
|
// we need to emulate "unfunded offers" behavior
|
|
if (get_funds (AccountID (0x4702), offer.out) == beast::zero)
|
|
return Amounts (offer.in.zeroed (), offer.out.zeroed ());
|
|
|
|
if (done ())
|
|
return Amounts (offer.in.zeroed (), offer.out.zeroed ());
|
|
|
|
auto result = do_cross (offer, quality, AccountID (0x4702));
|
|
|
|
funds_ -= result.order.in;
|
|
|
|
return result.order;
|
|
}
|
|
|
|
std::pair<Amounts, Amounts>
|
|
cross (Amounts offer1, Quality quality1, Amounts offer2, Quality quality2)
|
|
{
|
|
/* check if composed quality should be rejected */
|
|
Quality const quality (composed_quality (
|
|
quality1, quality2));
|
|
|
|
if (reject (quality))
|
|
return std::make_pair(
|
|
Amounts { offer1.in.zeroed (), offer1.out.zeroed () },
|
|
Amounts { offer2.in.zeroed (), offer2.out.zeroed () });
|
|
|
|
if (done ())
|
|
return std::make_pair(
|
|
Amounts { offer1.in.zeroed (), offer1.out.zeroed () },
|
|
Amounts { offer2.in.zeroed (), offer2.out.zeroed () });
|
|
|
|
auto result = do_cross (
|
|
offer1, quality1, AccountID (0x4703),
|
|
offer2, quality2, AccountID (0x4704));
|
|
|
|
return std::make_pair (result.first.order, result.second.order);
|
|
}
|
|
};
|
|
|
|
private:
|
|
Issue const& usd () const
|
|
{
|
|
static Issue const issue (
|
|
Currency (0x5553440000000000), AccountID (0x4985601));
|
|
return issue;
|
|
}
|
|
|
|
Issue const& eur () const
|
|
{
|
|
static Issue const issue (
|
|
Currency (0x4555520000000000), AccountID (0x4985602));
|
|
return issue;
|
|
}
|
|
|
|
Issue const& xrp () const
|
|
{
|
|
static Issue const issue (
|
|
xrpCurrency (), xrpAccount ());
|
|
return issue;
|
|
}
|
|
|
|
STAmount parse_amount (std::string const& amount, Issue const& issue)
|
|
{
|
|
return amountFromString (issue, amount);
|
|
}
|
|
|
|
Amounts parse_amounts (
|
|
std::string const& amount_in, Issue const& issue_in,
|
|
std::string const& amount_out, Issue const& issue_out)
|
|
{
|
|
STAmount const in (parse_amount (amount_in, issue_in));
|
|
STAmount const out (parse_amount (amount_out, issue_out));
|
|
|
|
return { in, out };
|
|
}
|
|
|
|
struct cross_attempt_offer
|
|
{
|
|
cross_attempt_offer (std::string const& in_,
|
|
std::string const &out_)
|
|
: in (in_)
|
|
, out (out_)
|
|
{
|
|
}
|
|
|
|
std::string in;
|
|
std::string out;
|
|
};
|
|
|
|
private:
|
|
std::string
|
|
format_amount (STAmount const& amount)
|
|
{
|
|
std::string txt = amount.getText ();
|
|
txt += "/";
|
|
txt += to_string (amount.issue().currency);
|
|
return txt;
|
|
}
|
|
|
|
void
|
|
attempt (
|
|
bool sell,
|
|
std::string name,
|
|
Quality taker_quality,
|
|
cross_attempt_offer const offer,
|
|
std::string const funds,
|
|
Quality cross_quality,
|
|
cross_attempt_offer const cross,
|
|
std::string const cross_funds,
|
|
cross_attempt_offer const flow,
|
|
Issue const& issue_in,
|
|
Issue const& issue_out,
|
|
Rate rate_in = parityRate,
|
|
Rate rate_out = parityRate)
|
|
{
|
|
Amounts taker_offer (parse_amounts (
|
|
offer.in, issue_in,
|
|
offer.out, issue_out));
|
|
|
|
Amounts cross_offer (parse_amounts (
|
|
cross.in, issue_in,
|
|
cross.out, issue_out));
|
|
|
|
CrossType cross_type;
|
|
|
|
if (isXRP (issue_out))
|
|
cross_type = CrossType::IouToXrp;
|
|
else if (isXRP (issue_in))
|
|
cross_type = CrossType::XrpToIou;
|
|
else
|
|
cross_type = CrossType::IouToIou;
|
|
|
|
// FIXME: We are always invoking the IOU-to-IOU taker. We should select
|
|
// the correct type dynamically.
|
|
TestTaker taker (cross_type, taker_offer, taker_quality,
|
|
parse_amount (funds, issue_in), sell ? tfSell : 0,
|
|
rate_in, rate_out);
|
|
|
|
taker.set_funds (parse_amount (cross_funds, issue_out));
|
|
|
|
auto result = taker.cross (cross_offer, cross_quality);
|
|
|
|
Amounts const expected (parse_amounts (
|
|
flow.in, issue_in,
|
|
flow.out, issue_out));
|
|
|
|
BEAST_EXPECT(expected == result);
|
|
|
|
if (expected != result)
|
|
{
|
|
log <<
|
|
"Expected: " << format_amount (expected.in) <<
|
|
" : " << format_amount (expected.out) << '\n' <<
|
|
" Actual: " << format_amount (result.in) <<
|
|
" : " << format_amount (result.out) << std::endl;
|
|
}
|
|
}
|
|
|
|
Quality get_quality(std::string in, std::string out)
|
|
{
|
|
return Quality (parse_amounts (in, xrp(), out, xrp ()));
|
|
}
|
|
|
|
public:
|
|
// Notation for clamp scenario descriptions:
|
|
//
|
|
// IN:OUT (with the last in the list being limiting factor)
|
|
// N = Nothing
|
|
// T = Taker Offer Balance
|
|
// A = Taker Account Balance
|
|
// B = Owner Account Balance
|
|
//
|
|
// (s) = sell semantics: taker wants unlimited output
|
|
// (b) = buy semantics: taker wants a limited amount out
|
|
|
|
// NIKB TODO: Augment TestTaker so currencies and rates can be specified
|
|
// once without need for repetition.
|
|
void
|
|
test_xrp_to_iou ()
|
|
{
|
|
testcase ("XRP Quantization: input");
|
|
|
|
Quality q1 = get_quality ("1", "1");
|
|
|
|
// TAKER OWNER
|
|
// QUAL OFFER FUNDS QUAL OFFER FUNDS EXPECTED
|
|
// XRP USD
|
|
attempt (Sell, "N:N", q1, { "2", "2" }, "2", q1, { "2", "2" }, "2", { "2", "2" }, xrp(), usd());
|
|
attempt (Sell, "N:B", q1, { "2", "2" }, "2", q1, { "2", "2" }, "1.8", { "1", "1.8" }, xrp(), usd());
|
|
attempt (Buy, "N:T", q1, { "1", "1" }, "2", q1, { "2", "2" }, "2", { "1", "1" }, xrp(), usd());
|
|
attempt (Buy, "N:BT", q1, { "1", "1" }, "2", q1, { "2", "2" }, "1.8", { "1", "1" }, xrp(), usd());
|
|
attempt (Buy, "N:TB", q1, { "1", "1" }, "2", q1, { "2", "2" }, "0.8", { "0", "0.8" }, xrp(), usd());
|
|
|
|
attempt (Sell, "T:N", q1, { "1", "1" }, "2", q1, { "2", "2" }, "2", { "1", "1" }, xrp(), usd());
|
|
attempt (Sell, "T:B", q1, { "1", "1" }, "2", q1, { "2", "2" }, "1.8", { "1", "1.8" }, xrp(), usd());
|
|
attempt (Buy, "T:T", q1, { "1", "1" }, "2", q1, { "2", "2" }, "2", { "1", "1" }, xrp(), usd());
|
|
attempt (Buy, "T:BT", q1, { "1", "1" }, "2", q1, { "2", "2" }, "1.8", { "1", "1" }, xrp(), usd());
|
|
attempt (Buy, "T:TB", q1, { "1", "1" }, "2", q1, { "2", "2" }, "0.8", { "0", "0.8" }, xrp(), usd());
|
|
|
|
attempt (Sell, "A:N", q1, { "2", "2" }, "1", q1, { "2", "2" }, "2", { "1", "1" }, xrp(), usd());
|
|
attempt (Sell, "A:B", q1, { "2", "2" }, "1", q1, { "2", "2" }, "1.8", { "1", "1.8" }, xrp(), usd());
|
|
attempt (Buy, "A:T", q1, { "2", "2" }, "1", q1, { "3", "3" }, "3", { "1", "1" }, xrp(), usd());
|
|
attempt (Buy, "A:BT", q1, { "2", "2" }, "1", q1, { "3", "3" }, "2.4", { "1", "1" }, xrp(), usd());
|
|
attempt (Buy, "A:TB", q1, { "2", "2" }, "1", q1, { "3", "3" }, "0.8", { "0", "0.8" }, xrp(), usd());
|
|
|
|
attempt (Sell, "TA:N", q1, { "2", "2" }, "1", q1, { "2", "2" }, "2", { "1", "1" }, xrp(), usd());
|
|
attempt (Sell, "TA:B", q1, { "2", "2" }, "1", q1, { "3", "3" }, "1.8", { "1", "1.8" }, xrp(), usd());
|
|
attempt (Buy, "TA:T", q1, { "2", "2" }, "1", q1, { "3", "3" }, "3", { "1", "1" }, xrp(), usd());
|
|
attempt (Buy, "TA:BT", q1, { "2", "2" }, "1", q1, { "3", "3" }, "1.8", { "1", "1.8" }, xrp(), usd());
|
|
attempt (Buy, "TA:TB", q1, { "2", "2" }, "1", q1, { "3", "3" }, "1.8", { "1", "1.8" }, xrp(), usd());
|
|
|
|
attempt (Sell, "AT:N", q1, { "2", "2" }, "1", q1, { "3", "3" }, "3", { "1", "1" }, xrp(), usd());
|
|
attempt (Sell, "AT:B", q1, { "2", "2" }, "1", q1, { "3", "3" }, "1.8", { "1", "1.8" }, xrp(), usd());
|
|
attempt (Buy, "AT:T", q1, { "2", "2" }, "1", q1, { "3", "3" }, "3", { "1", "1" }, xrp(), usd());
|
|
attempt (Buy, "AT:BT", q1, { "2", "2" }, "1", q1, { "3", "3" }, "1.8", { "1", "1.8" }, xrp(), usd());
|
|
attempt (Buy, "AT:TB", q1, { "2", "2" }, "1", q1, { "3", "3" }, "0.8", { "0", "0.8" }, xrp(), usd());
|
|
}
|
|
|
|
void
|
|
test_iou_to_xrp ()
|
|
{
|
|
testcase ("XRP Quantization: output");
|
|
|
|
Quality q1 = get_quality ("1", "1");
|
|
|
|
// TAKER OWNER
|
|
// QUAL OFFER FUNDS QUAL OFFER FUNDS EXPECTED
|
|
// USD XRP
|
|
attempt (Sell, "N:N", q1, { "3", "3" }, "3", q1, { "3", "3" }, "3", { "3", "3" }, usd(), xrp());
|
|
attempt (Sell, "N:B", q1, { "3", "3" }, "3", q1, { "3", "3" }, "2", { "2", "2" }, usd(), xrp());
|
|
attempt (Buy, "N:T", q1, { "3", "3" }, "2.5", q1, { "5", "5" }, "5", { "2.5", "2" }, usd(), xrp());
|
|
attempt (Buy, "N:BT", q1, { "3", "3" }, "1.5", q1, { "5", "5" }, "4", { "1.5", "1" }, usd(), xrp());
|
|
attempt (Buy, "N:TB", q1, { "3", "3" }, "2.2", q1, { "5", "5" }, "1", { "1", "1" }, usd(), xrp());
|
|
|
|
attempt (Sell, "T:N", q1, { "1", "1" }, "2", q1, { "2", "2" }, "2", { "1", "1" }, usd(), xrp());
|
|
attempt (Sell, "T:B", q1, { "2", "2" }, "2", q1, { "3", "3" }, "1", { "1", "1" }, usd(), xrp());
|
|
attempt (Buy, "T:T", q1, { "1", "1" }, "2", q1, { "2", "2" }, "2", { "1", "1" }, usd(), xrp());
|
|
attempt (Buy, "T:BT", q1, { "1", "1" }, "2", q1, { "3", "3" }, "2", { "1", "1" }, usd(), xrp());
|
|
attempt (Buy, "T:TB", q1, { "2", "2" }, "2", q1, { "3", "3" }, "1", { "1", "1" }, usd(), xrp());
|
|
|
|
attempt (Sell, "A:N", q1, { "2", "2" }, "1.5", q1, { "2", "2" }, "2", { "1.5", "1" }, usd(), xrp());
|
|
attempt (Sell, "A:B", q1, { "2", "2" }, "1.8", q1, { "3", "3" }, "2", { "1.8", "1" }, usd(), xrp());
|
|
attempt (Buy, "A:T", q1, { "2", "2" }, "1.2", q1, { "3", "3" }, "3", { "1.2", "1" }, usd(), xrp());
|
|
attempt (Buy, "A:BT", q1, { "2", "2" }, "1.5", q1, { "4", "4" }, "3", { "1.5", "1" }, usd(), xrp());
|
|
attempt (Buy, "A:TB", q1, { "2", "2" }, "1.5", q1, { "4", "4" }, "1", { "1", "1" }, usd(), xrp());
|
|
|
|
attempt (Sell, "TA:N", q1, { "2", "2" }, "1.5", q1, { "2", "2" }, "2", { "1.5", "1" }, usd(), xrp());
|
|
attempt (Sell, "TA:B", q1, { "2", "2" }, "1.5", q1, { "3", "3" }, "1", { "1", "1" }, usd(), xrp());
|
|
attempt (Buy, "TA:T", q1, { "2", "2" }, "1.5", q1, { "3", "3" }, "3", { "1.5", "1" }, usd(), xrp());
|
|
attempt (Buy, "TA:BT", q1, { "2", "2" }, "1.8", q1, { "4", "4" }, "3", { "1.8", "1" }, usd(), xrp());
|
|
attempt (Buy, "TA:TB", q1, { "2", "2" }, "1.2", q1, { "3", "3" }, "1", { "1", "1" }, usd(), xrp());
|
|
|
|
attempt (Sell, "AT:N", q1, { "2", "2" }, "2.5", q1, { "4", "4" }, "4", { "2", "2" }, usd(), xrp());
|
|
attempt (Sell, "AT:B", q1, { "2", "2" }, "2.5", q1, { "3", "3" }, "1", { "1", "1" }, usd(), xrp());
|
|
attempt (Buy, "AT:T", q1, { "2", "2" }, "2.5", q1, { "3", "3" }, "3", { "2", "2" }, usd(), xrp());
|
|
attempt (Buy, "AT:BT", q1, { "2", "2" }, "2.5", q1, { "4", "4" }, "3", { "2", "2" }, usd(), xrp());
|
|
attempt (Buy, "AT:TB", q1, { "2", "2" }, "2.5", q1, { "3", "3" }, "1", { "1", "1" }, usd(), xrp());
|
|
}
|
|
|
|
void
|
|
test_iou_to_iou ()
|
|
{
|
|
testcase ("IOU to IOU");
|
|
|
|
Quality q1 = get_quality ("1", "1");
|
|
|
|
// Highly exaggerated 50% transfer rate for the input and output:
|
|
Rate const rate { parityRate.value + (parityRate.value / 2) };
|
|
|
|
// TAKER OWNER
|
|
// QUAL OFFER FUNDS QUAL OFFER FUNDS EXPECTED
|
|
// EUR USD
|
|
attempt (Sell, "N:N", q1, { "2", "2" }, "10", q1, { "2", "2" }, "10", { "2", "2" }, eur(), usd(), rate, rate);
|
|
attempt (Sell, "N:B", q1, { "4", "4" }, "10", q1, { "4", "4" }, "4", { "2.666666666666666", "2.666666666666666" }, eur(), usd(), rate, rate);
|
|
attempt (Buy, "N:T", q1, { "1", "1" }, "10", q1, { "2", "2" }, "10", { "1", "1" }, eur(), usd(), rate, rate);
|
|
attempt (Buy, "N:BT", q1, { "2", "2" }, "10", q1, { "6", "6" }, "5", { "2", "2" }, eur(), usd(), rate, rate);
|
|
attempt (Buy, "N:TB", q1, { "2", "2" }, "2", q1, { "6", "6" }, "1", { "0.6666666666666667", "0.6666666666666667" }, eur(), usd(), rate, rate);
|
|
attempt (Sell, "A:N", q1, { "2", "2" }, "2.5", q1, { "2", "2" }, "10", { "1.666666666666666", "1.666666666666666" }, eur(), usd(), rate, rate);
|
|
}
|
|
|
|
void
|
|
run() override
|
|
{
|
|
test_xrp_to_iou ();
|
|
test_iou_to_xrp ();
|
|
test_iou_to_iou ();
|
|
}
|
|
};
|
|
|
|
BEAST_DEFINE_TESTSUITE(Taker,tx,ripple);
|
|
|
|
}
|