From 433eeabfa561751f78873dafc3918f8d066c42a0 Mon Sep 17 00:00:00 2001 From: Vlad <129996061+vvysokikh1@users.noreply.github.com> Date: Wed, 23 Jul 2025 14:57:51 +0100 Subject: [PATCH] chore: Remove unused code after flow cross retirement (#5575) After the `FlowCross` amendment was retired (#5562), there was still some unused code left. This change removes the remaining remnants. --- src/test/app/Taker_test.cpp | 1394 ----------------------- src/xrpld/app/tx/detail/CreateOffer.cpp | 415 +------ src/xrpld/app/tx/detail/CreateOffer.h | 65 +- src/xrpld/app/tx/detail/Taker.cpp | 863 -------------- src/xrpld/app/tx/detail/Taker.h | 341 ------ 5 files changed, 14 insertions(+), 3064 deletions(-) delete mode 100644 src/test/app/Taker_test.cpp delete mode 100644 src/xrpld/app/tx/detail/Taker.cpp delete mode 100644 src/xrpld/app/tx/detail/Taker.h diff --git a/src/test/app/Taker_test.cpp b/src/test/app/Taker_test.cpp deleted file mode 100644 index 3b3f338625..0000000000 --- a/src/test/app/Taker_test.cpp +++ /dev/null @@ -1,1394 +0,0 @@ -//------------------------------------------------------------------------------ -/* - 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 - -#include - -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 - 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"); - - for (auto NumberSwitchOver : {false, true}) - { - NumberSO stNumberSO{NumberSwitchOver}; - // 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()); - if (NumberSwitchOver) - { - attempt( - Sell, - "N:B", - q1, - {"2", "2"}, - "2", - q1, - {"2", "2"}, - "1.8", - {"2", "1.8"}, - xrp(), - usd()); - } - else - { - 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()); - if (NumberSwitchOver) - { - attempt( - Buy, - "N:TB", - q1, - {"1", "1"}, - "2", - q1, - {"2", "2"}, - "0.8", - {"1", "0.8"}, - xrp(), - usd()); - } - else - { - 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()); - if (NumberSwitchOver) - { - attempt( - Sell, - "T:B", - q1, - {"1", "1"}, - "2", - q1, - {"2", "2"}, - "1.8", - {"1", "1"}, - xrp(), - usd()); - } - else - { - 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()); - if (NumberSwitchOver) - { - attempt( - Buy, - "T:TB", - q1, - {"1", "1"}, - "2", - q1, - {"2", "2"}, - "0.8", - {"1", "0.8"}, - xrp(), - usd()); - } - else - { - 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()); - if (NumberSwitchOver) - { - attempt( - Sell, - "A:B", - q1, - {"2", "2"}, - "1", - q1, - {"2", "2"}, - "1.8", - {"1", "1"}, - xrp(), - usd()); - } - else - { - 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()); - if (NumberSwitchOver) - { - attempt( - Buy, - "A:TB", - q1, - {"2", "2"}, - "1", - q1, - {"3", "3"}, - "0.8", - {"1", "0.8"}, - xrp(), - usd()); - } - else - { - 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()); - if (NumberSwitchOver) - { - attempt( - Sell, - "TA:B", - q1, - {"2", "2"}, - "1", - q1, - {"3", "3"}, - "1.8", - {"1", "1"}, - xrp(), - usd()); - } - else - { - 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()); - if (NumberSwitchOver) - { - attempt( - Buy, - "TA:BT", - q1, - {"2", "2"}, - "1", - q1, - {"3", "3"}, - "1.8", - {"1", "1"}, - xrp(), - usd()); - attempt( - Buy, - "TA:TB", - q1, - {"2", "2"}, - "1", - q1, - {"3", "3"}, - "1.8", - {"1", "1"}, - xrp(), - usd()); - } - else - { - 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()); - if (NumberSwitchOver) - { - attempt( - Sell, - "AT:B", - q1, - {"2", "2"}, - "1", - q1, - {"3", "3"}, - "1.8", - {"1", "1"}, - xrp(), - usd()); - } - else - { - 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()); - if (NumberSwitchOver) - { - attempt( - Buy, - "AT:BT", - q1, - {"2", "2"}, - "1", - q1, - {"3", "3"}, - "1.8", - {"1", "1"}, - xrp(), - usd()); - attempt( - Buy, - "AT:TB", - q1, - {"2", "2"}, - "1", - q1, - {"3", "3"}, - "0.8", - {"1", "0.8"}, - xrp(), - usd()); - } - else - { - 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"); - - for (auto NumberSwitchOver : {false, true}) - { - NumberSO stNumberSO{NumberSwitchOver}; - 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()); - if (NumberSwitchOver) - { - attempt( - Buy, - "N:T", - q1, - {"3", "3"}, - "2.5", - q1, - {"5", "5"}, - "5", - {"2.5", "3"}, - usd(), - xrp()); - attempt( - Buy, - "N:BT", - q1, - {"3", "3"}, - "1.5", - q1, - {"5", "5"}, - "4", - {"1.5", "2"}, - usd(), - xrp()); - } - else - { - 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()); - - if (NumberSwitchOver) - { - attempt( - Sell, - "A:N", - q1, - {"2", "2"}, - "1.5", - q1, - {"2", "2"}, - "2", - {"1.5", "2"}, - usd(), - xrp()); - attempt( - Sell, - "A:B", - q1, - {"2", "2"}, - "1.8", - q1, - {"3", "3"}, - "2", - {"1.8", "2"}, - usd(), - xrp()); - } - else - { - 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()); - if (NumberSwitchOver) - { - attempt( - Buy, - "A:BT", - q1, - {"2", "2"}, - "1.5", - q1, - {"4", "4"}, - "3", - {"1.5", "2"}, - usd(), - xrp()); - } - else - { - 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()); - - if (NumberSwitchOver) - { - attempt( - Sell, - "TA:N", - q1, - {"2", "2"}, - "1.5", - q1, - {"2", "2"}, - "2", - {"1.5", "2"}, - usd(), - xrp()); - } - else - { - 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()); - if (NumberSwitchOver) - { - attempt( - Buy, - "TA:T", - q1, - {"2", "2"}, - "1.5", - q1, - {"3", "3"}, - "3", - {"1.5", "2"}, - usd(), - xrp()); - attempt( - Buy, - "TA:BT", - q1, - {"2", "2"}, - "1.8", - q1, - {"4", "4"}, - "3", - {"1.8", "2"}, - usd(), - xrp()); - } - else - { - 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"); - - for (auto NumberSwitchOver : {false, true}) - { - NumberSO stNumberSO{NumberSwitchOver}; - 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); - if (NumberSwitchOver) - { - attempt( - Sell, - "N:B", - q1, - {"4", "4"}, - "10", - q1, - {"4", "4"}, - "4", - {"2.666666666666667", "2.666666666666667"}, - eur(), - usd(), - rate, - rate); - } - else - { - 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); - if (NumberSwitchOver) - { - attempt( - Sell, - "A:N", - q1, - {"2", "2"}, - "2.5", - q1, - {"2", "2"}, - "10", - {"1.666666666666667", "1.666666666666667"}, - eur(), - usd(), - rate, - rate); - } - else - { - 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); - -} // namespace ripple diff --git a/src/xrpld/app/tx/detail/CreateOffer.cpp b/src/xrpld/app/tx/detail/CreateOffer.cpp index 9543a4fcd9..3cfae92cbd 100644 --- a/src/xrpld/app/tx/detail/CreateOffer.cpp +++ b/src/xrpld/app/tx/detail/CreateOffer.cpp @@ -26,9 +26,9 @@ #include #include #include -#include #include #include +#include #include namespace ripple { @@ -311,374 +311,6 @@ CreateOffer::checkAcceptAsset( return tesSUCCESS; } -bool -CreateOffer::dry_offer(ApplyView& view, Offer const& offer) -{ - if (offer.fully_consumed()) - return true; - auto const amount = accountFunds( - view, - offer.owner(), - offer.amount().out, - fhZERO_IF_FROZEN, - ctx_.app.journal("View")); - return (amount <= beast::zero); -} - -std::pair -CreateOffer::select_path( - bool have_direct, - OfferStream const& direct, - bool have_bridge, - OfferStream const& leg1, - OfferStream const& leg2) -{ - // If we don't have any viable path, why are we here?! - XRPL_ASSERT( - have_direct || have_bridge, - "ripple::CreateOffer::select_path : valid inputs"); - - // If there's no bridged path, the direct is the best by default. - if (!have_bridge) - return std::make_pair(true, direct.tip().quality()); - - Quality const bridged_quality( - composed_quality(leg1.tip().quality(), leg2.tip().quality())); - - if (have_direct) - { - // We compare the quality of the composed quality of the bridged - // offers and compare it against the direct offer to pick the best. - Quality const direct_quality(direct.tip().quality()); - - if (bridged_quality < direct_quality) - return std::make_pair(true, direct_quality); - } - - // Either there was no direct offer, or it didn't have a better quality - // than the bridge. - return std::make_pair(false, bridged_quality); -} - -bool -CreateOffer::reachedOfferCrossingLimit(Taker const& taker) const -{ - auto const crossings = - taker.get_direct_crossings() + (2 * taker.get_bridge_crossings()); - - // The crossing limit is part of the Ripple protocol and - // changing it is a transaction-processing change. - return crossings >= 850; -} - -std::pair -CreateOffer::bridged_cross( - Taker& taker, - ApplyView& view, - ApplyView& view_cancel, - NetClock::time_point const when) -{ - auto const& takerAmount = taker.original_offer(); - - XRPL_ASSERT( - !isXRP(takerAmount.in) && !isXRP(takerAmount.out), - "ripple::CreateOffer::bridged_cross : neither is XRP"); - - if (isXRP(takerAmount.in) || isXRP(takerAmount.out)) - Throw("Bridging with XRP and an endpoint."); - - OfferStream offers_direct( - view, - view_cancel, - Book(taker.issue_in(), taker.issue_out(), std::nullopt), - when, - stepCounter_, - j_); - - OfferStream offers_leg1( - view, - view_cancel, - Book(taker.issue_in(), xrpIssue(), std::nullopt), - when, - stepCounter_, - j_); - - OfferStream offers_leg2( - view, - view_cancel, - Book(xrpIssue(), taker.issue_out(), std::nullopt), - when, - stepCounter_, - j_); - - TER cross_result = tesSUCCESS; - - // Note the subtle distinction here: self-offers encountered in the - // bridge are taken, but self-offers encountered in the direct book - // are not. - bool have_bridge = offers_leg1.step() && offers_leg2.step(); - bool have_direct = step_account(offers_direct, taker); - int count = 0; - - auto viewJ = ctx_.app.journal("View"); - - // Modifying the order or logic of the operations in the loop will cause - // a protocol breaking change. - while (have_direct || have_bridge) - { - bool leg1_consumed = false; - bool leg2_consumed = false; - bool direct_consumed = false; - - auto const [use_direct, quality] = select_path( - have_direct, offers_direct, have_bridge, offers_leg1, offers_leg2); - - // We are always looking at the best quality; we are done with - // crossing as soon as we cross the quality boundary. - if (taker.reject(quality)) - break; - - count++; - - if (use_direct) - { - if (auto stream = j_.debug()) - { - stream << count << " Direct:"; - stream << " offer: " << offers_direct.tip(); - stream << " in: " << offers_direct.tip().amount().in; - stream << " out: " << offers_direct.tip().amount().out; - stream << " owner: " << offers_direct.tip().owner(); - stream << " funds: " - << accountFunds( - view, - offers_direct.tip().owner(), - offers_direct.tip().amount().out, - fhIGNORE_FREEZE, - viewJ); - } - - cross_result = taker.cross(offers_direct.tip()); - - JLOG(j_.debug()) << "Direct Result: " << transToken(cross_result); - - if (dry_offer(view, offers_direct.tip())) - { - direct_consumed = true; - have_direct = step_account(offers_direct, taker); - } - } - else - { - if (auto stream = j_.debug()) - { - auto const owner1_funds_before = accountFunds( - view, - offers_leg1.tip().owner(), - offers_leg1.tip().amount().out, - fhIGNORE_FREEZE, - viewJ); - - auto const owner2_funds_before = accountFunds( - view, - offers_leg2.tip().owner(), - offers_leg2.tip().amount().out, - fhIGNORE_FREEZE, - viewJ); - - stream << count << " Bridge:"; - stream << " offer1: " << offers_leg1.tip(); - stream << " in: " << offers_leg1.tip().amount().in; - stream << " out: " << offers_leg1.tip().amount().out; - stream << " owner: " << offers_leg1.tip().owner(); - stream << " funds: " << owner1_funds_before; - stream << " offer2: " << offers_leg2.tip(); - stream << " in: " << offers_leg2.tip().amount().in; - stream << " out: " << offers_leg2.tip().amount().out; - stream << " owner: " << offers_leg2.tip().owner(); - stream << " funds: " << owner2_funds_before; - } - - cross_result = taker.cross(offers_leg1.tip(), offers_leg2.tip()); - - JLOG(j_.debug()) << "Bridge Result: " << transToken(cross_result); - - if (view.rules().enabled(fixTakerDryOfferRemoval)) - { - // have_bridge can be true the next time 'round only if - // neither of the OfferStreams are dry. - leg1_consumed = dry_offer(view, offers_leg1.tip()); - if (leg1_consumed) - have_bridge &= offers_leg1.step(); - - leg2_consumed = dry_offer(view, offers_leg2.tip()); - if (leg2_consumed) - have_bridge &= offers_leg2.step(); - } - else - { - // This old behavior may leave an empty offer in the book for - // the second leg. - if (dry_offer(view, offers_leg1.tip())) - { - leg1_consumed = true; - have_bridge = (have_bridge && offers_leg1.step()); - } - if (dry_offer(view, offers_leg2.tip())) - { - leg2_consumed = true; - have_bridge = (have_bridge && offers_leg2.step()); - } - } - } - - if (cross_result != tesSUCCESS) - { - cross_result = tecFAILED_PROCESSING; - break; - } - - if (taker.done()) - { - JLOG(j_.debug()) << "The taker reports he's done during crossing!"; - break; - } - - if (reachedOfferCrossingLimit(taker)) - { - JLOG(j_.debug()) << "The offer crossing limit has been exceeded!"; - break; - } - - // Postcondition: If we aren't done, then we *must* have consumed at - // least one offer fully. - XRPL_ASSERT( - direct_consumed || leg1_consumed || leg2_consumed, - "ripple::CreateOffer::bridged_cross : consumed an offer"); - - if (!direct_consumed && !leg1_consumed && !leg2_consumed) - Throw( - "bridged crossing: nothing was fully consumed."); - } - - return std::make_pair(cross_result, taker.remaining_offer()); -} - -std::pair -CreateOffer::direct_cross( - Taker& taker, - ApplyView& view, - ApplyView& view_cancel, - NetClock::time_point const when) -{ - OfferStream offers( - view, - view_cancel, - Book(taker.issue_in(), taker.issue_out(), std::nullopt), - when, - stepCounter_, - j_); - - TER cross_result(tesSUCCESS); - int count = 0; - - bool have_offer = step_account(offers, taker); - - // Modifying the order or logic of the operations in the loop will cause - // a protocol breaking change. - while (have_offer) - { - bool direct_consumed = false; - auto& offer(offers.tip()); - - // We are done with crossing as soon as we cross the quality boundary - if (taker.reject(offer.quality())) - break; - - count++; - - if (auto stream = j_.debug()) - { - stream << count << " Direct:"; - stream << " offer: " << offer; - stream << " in: " << offer.amount().in; - stream << " out: " << offer.amount().out; - stream << "quality: " << offer.quality(); - stream << " owner: " << offer.owner(); - stream << " funds: " - << accountFunds( - view, - offer.owner(), - offer.amount().out, - fhIGNORE_FREEZE, - ctx_.app.journal("View")); - } - - cross_result = taker.cross(offer); - - JLOG(j_.debug()) << "Direct Result: " << transToken(cross_result); - - if (dry_offer(view, offer)) - { - direct_consumed = true; - have_offer = step_account(offers, taker); - } - - if (cross_result != tesSUCCESS) - { - cross_result = tecFAILED_PROCESSING; - break; - } - - if (taker.done()) - { - JLOG(j_.debug()) << "The taker reports he's done during crossing!"; - break; - } - - if (reachedOfferCrossingLimit(taker)) - { - JLOG(j_.debug()) << "The offer crossing limit has been exceeded!"; - break; - } - - // Postcondition: If we aren't done, then we *must* have consumed the - // offer on the books fully! - XRPL_ASSERT( - direct_consumed, - "ripple::CreateOffer::direct_cross : consumed an offer"); - - if (!direct_consumed) - Throw( - "direct crossing: nothing was fully consumed."); - } - - return std::make_pair(cross_result, taker.remaining_offer()); -} - -// Step through the stream for as long as possible, skipping any offers -// that are from the taker or which cross the taker's threshold. -// Return false if the is no offer in the book, true otherwise. -bool -CreateOffer::step_account(OfferStream& stream, Taker const& taker) -{ - while (stream.step()) - { - auto const& offer = stream.tip(); - - // This offer at the tip crosses the taker's threshold. We're done. - if (taker.reject(offer.quality())) - return true; - - // This offer at the tip is not from the taker. We're done. - if (offer.owner() != taker.account()) - return true; - } - - // We ran out of offers. Can't advance. - return false; -} - std::pair CreateOffer::flowCross( PaymentSandbox& psb, @@ -883,21 +515,6 @@ CreateOffer::flowCross( return {tecINTERNAL, takerAmount}; } -std::pair -CreateOffer::cross( - Sandbox& sb, - Sandbox& sbCancel, - Amounts const& takerAmount, - std::optional const& domainID) -{ - PaymentSandbox psbFlow{&sb}; - PaymentSandbox psbCancelFlow{&sbCancel}; - auto const ret = flowCross(psbFlow, psbCancelFlow, takerAmount, domainID); - psbFlow.apply(sb); - psbCancelFlow.apply(sbCancel); - return ret; -} - std::string CreateOffer::format_amount(STAmount const& amount) { @@ -907,20 +524,6 @@ CreateOffer::format_amount(STAmount const& amount) return txt; } -void -CreateOffer::preCompute() -{ - cross_type_ = CrossType::IouToIou; - bool const pays_xrp = ctx_.tx.getFieldAmount(sfTakerPays).native(); - bool const gets_xrp = ctx_.tx.getFieldAmount(sfTakerGets).native(); - if (pays_xrp && !gets_xrp) - cross_type_ = CrossType::IouToXrp; - else if (gets_xrp && !pays_xrp) - cross_type_ = CrossType::XrpToIou; - - return Transactor::preCompute(); -} - TER CreateOffer::applyHybrid( Sandbox& sb, @@ -1084,11 +687,6 @@ CreateOffer::applyGuts(Sandbox& sb, Sandbox& sbCancel) // We reverse pays and gets because during crossing we are taking. Amounts const takerAmount(saTakerGets, saTakerPays); - // The amount of the offer that is unfilled after crossing has been - // performed. It may be equal to the original amount (didn't cross), - // empty (fully crossed), or something in-between. - Amounts place_offer; - JLOG(j_.debug()) << "Attempting cross: " << to_string(takerAmount.in.issue()) << " -> " << to_string(takerAmount.out.issue()); @@ -1101,8 +699,17 @@ CreateOffer::applyGuts(Sandbox& sb, Sandbox& sbCancel) stream << " out: " << format_amount(takerAmount.out); } + // The amount of the offer that is unfilled after crossing has been + // performed. It may be equal to the original amount (didn't cross), + // empty (fully crossed), or something in-between. + Amounts place_offer; + PaymentSandbox psbFlow{&sb}; + PaymentSandbox psbCancelFlow{&sbCancel}; + std::tie(result, place_offer) = - cross(sb, sbCancel, takerAmount, domainID); + flowCross(psbFlow, psbCancelFlow, takerAmount, domainID); + psbFlow.apply(sb); + psbCancelFlow.apply(sbCancel); // We expect the implementation of cross to succeed // or give a tec. diff --git a/src/xrpld/app/tx/detail/CreateOffer.h b/src/xrpld/app/tx/detail/CreateOffer.h index f995f4a5d6..6e3d6145b1 100644 --- a/src/xrpld/app/tx/detail/CreateOffer.h +++ b/src/xrpld/app/tx/detail/CreateOffer.h @@ -20,10 +20,10 @@ #ifndef RIPPLE_TX_CREATEOFFER_H_INCLUDED #define RIPPLE_TX_CREATEOFFER_H_INCLUDED -#include -#include #include +#include + namespace ripple { class PaymentSandbox; @@ -36,8 +36,7 @@ public: static constexpr ConsequencesFactoryType ConsequencesFactory{Custom}; /** Construct a Transactor subclass that creates an offer in the ledger. */ - explicit CreateOffer(ApplyContext& ctx) - : Transactor(ctx), stepCounter_(1000, j_) + explicit CreateOffer(ApplyContext& ctx) : Transactor(ctx) { } @@ -52,10 +51,6 @@ public: static TER preclaim(PreclaimContext const& ctx); - /** Gather information beyond what the Transactor base class gathers. */ - void - preCompute() override; - /** Precondition: fee collection is likely. Attempt to create the offer. */ TER doApply() override; @@ -73,42 +68,6 @@ private: beast::Journal const j, Issue const& issue); - bool - dry_offer(ApplyView& view, Offer const& offer); - - static std::pair - select_path( - bool have_direct, - OfferStream const& direct, - bool have_bridge, - OfferStream const& leg1, - OfferStream const& leg2); - - std::pair - bridged_cross( - Taker& taker, - ApplyView& view, - ApplyView& view_cancel, - NetClock::time_point const when); - - std::pair - direct_cross( - Taker& taker, - ApplyView& view, - ApplyView& view_cancel, - NetClock::time_point const when); - - // Step through the stream for as long as possible, skipping any offers - // that are from the taker or which cross the taker's threshold. - // Return false if the is no offer in the book, true otherwise. - static bool - step_account(OfferStream& stream, Taker const& taker); - - // True if the number of offers that have been crossed - // exceeds the limit. - bool - reachedOfferCrossingLimit(Taker const& taker) const; - // Use the payment flow code to perform offer crossing. std::pair flowCross( @@ -117,17 +76,6 @@ private: Amounts const& takerAmount, std::optional const& domainID); - // Temporary - // This is a central location that invokes both versions of cross - // so the results can be compared. Eventually this layer will be - // removed once flowCross is determined to be stable. - std::pair - cross( - Sandbox& sb, - Sandbox& sbCancel, - Amounts const& takerAmount, - std::optional const& domainID); - static std::string format_amount(STAmount const& amount); @@ -139,13 +87,6 @@ private: STAmount const& saTakerPays, STAmount const& saTakerGets, std::function)> const& setDir); - -private: - // What kind of offer we are placing - CrossType cross_type_; - - // The number of steps to take through order books while crossing - OfferStream::StepCounter stepCounter_; }; using OfferCreate = CreateOffer; diff --git a/src/xrpld/app/tx/detail/Taker.cpp b/src/xrpld/app/tx/detail/Taker.cpp deleted file mode 100644 index 9bfd6dc1d3..0000000000 --- a/src/xrpld/app/tx/detail/Taker.cpp +++ /dev/null @@ -1,863 +0,0 @@ -//------------------------------------------------------------------------------ -/* - 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 - -#include -#include - -namespace ripple { - -static std::string -format_amount(STAmount const& amount) -{ - std::string txt = amount.getText(); - txt += "/"; - txt += to_string(amount.issue().currency); - return txt; -} - -BasicTaker::BasicTaker( - CrossType cross_type, - AccountID const& account, - Amounts const& amount, - Quality const& quality, - std::uint32_t flags, - Rate const& rate_in, - Rate const& rate_out, - beast::Journal journal) - : account_(account) - , quality_(quality) - , threshold_(quality_) - , sell_(flags & tfSell) - , original_(amount) - , remaining_(amount) - , issue_in_(remaining_.in.issue()) - , issue_out_(remaining_.out.issue()) - , m_rate_in(rate_in) - , m_rate_out(rate_out) - , cross_type_(cross_type) - , journal_(journal) -{ - XRPL_ASSERT( - remaining_.in > beast::zero, - "ripple::BasicTaker::BasicTaker : positive remaining in"); - XRPL_ASSERT( - remaining_.out > beast::zero, - "ripple::BasicTaker::BasicTaker : positive remaining out"); - - XRPL_ASSERT( - m_rate_in.value, "ripple::BasicTaker::BasicTaker : nonzero rate in"); - XRPL_ASSERT( - m_rate_out.value, "ripple::BasicTaker::BasicTaker : nonzero rate out"); - - // If we are dealing with a particular flavor, make sure that it's the - // flavor we expect: - XRPL_ASSERT( - cross_type != CrossType::XrpToIou || - (isXRP(issue_in()) && !isXRP(issue_out())), - "ripple::BasicTaker::BasicTaker : valid cross to IOU"); - - XRPL_ASSERT( - cross_type != CrossType::IouToXrp || - (!isXRP(issue_in()) && isXRP(issue_out())), - "ripple::BasicTaker::BasicTaker : valid cross to XRP"); - - // And make sure we're not crossing XRP for XRP - XRPL_ASSERT( - !isXRP(issue_in()) || !isXRP(issue_out()), - "ripple::BasicTaker::BasicTaker : not crossing XRP for XRP"); - - // If this is a passive order, we adjust the quality so as to prevent offers - // at the same quality level from being consumed. - if (flags & tfPassive) - ++threshold_; -} - -Rate -BasicTaker::effective_rate( - Rate const& rate, - Issue const& issue, - AccountID const& from, - AccountID const& to) -{ - // If there's a transfer rate, the issuer is not involved - // and the sender isn't the same as the recipient, return - // the actual transfer rate. - if (rate != parityRate && from != to && from != issue.account && - to != issue.account) - { - return rate; - } - - return parityRate; -} - -bool -BasicTaker::unfunded() const -{ - if (get_funds(account(), remaining_.in) > beast::zero) - return false; - - JLOG(journal_.debug()) << "Unfunded: taker is out of funds."; - return true; -} - -bool -BasicTaker::done() const -{ - // We are done if we have consumed all the input currency - if (remaining_.in <= beast::zero) - { - JLOG(journal_.debug()) - << "Done: all the input currency has been consumed."; - return true; - } - - // We are done if using buy semantics and we received the - // desired amount of output currency - if (!sell_ && (remaining_.out <= beast::zero)) - { - JLOG(journal_.debug()) << "Done: the desired amount has been received."; - return true; - } - - // We are done if the taker is out of funds - if (unfunded()) - { - JLOG(journal_.debug()) << "Done: taker out of funds."; - return true; - } - - return false; -} - -Amounts -BasicTaker::remaining_offer() const -{ - // If the taker is done, then there's no offer to place. - if (done()) - return Amounts(remaining_.in.zeroed(), remaining_.out.zeroed()); - - // Avoid math altogether if we didn't cross. - if (original_ == remaining_) - return original_; - - if (sell_) - { - XRPL_ASSERT( - remaining_.in > beast::zero, - "ripple::BasicTaker::remaining_offer : positive remaining in"); - - // We scale the output based on the remaining input: - return Amounts( - remaining_.in, - divRound(remaining_.in, quality_.rate(), issue_out_, true)); - } - - XRPL_ASSERT( - remaining_.out > beast::zero, - "ripple::BasicTaker::remaining_offer : positive remaining out"); - - // We scale the input based on the remaining output: - return Amounts( - mulRound(remaining_.out, quality_.rate(), issue_in_, true), - remaining_.out); -} - -Amounts const& -BasicTaker::original_offer() const -{ - return original_; -} - -// TODO: the presence of 'output' is an artifact caused by the fact that -// Amounts carry issue information which should be decoupled. -static STAmount -qual_div(STAmount const& amount, Quality const& quality, STAmount const& output) -{ - auto result = divide(amount, quality.rate(), output.issue()); - return std::min(result, output); -} - -static STAmount -qual_mul(STAmount const& amount, Quality const& quality, STAmount const& output) -{ - auto result = multiply(amount, quality.rate(), output.issue()); - return std::min(result, output); -} - -void -BasicTaker::log_flow(char const* description, Flow const& flow) -{ - auto stream = journal_.debug(); - if (!stream) - return; - - stream << description; - - if (isXRP(issue_in())) - stream << " order in: " << format_amount(flow.order.in); - else - stream << " order in: " << format_amount(flow.order.in) - << " (issuer: " << format_amount(flow.issuers.in) << ")"; - - if (isXRP(issue_out())) - stream << " order out: " << format_amount(flow.order.out); - else - stream << " order out: " << format_amount(flow.order.out) - << " (issuer: " << format_amount(flow.issuers.out) << ")"; -} - -BasicTaker::Flow -BasicTaker::flow_xrp_to_iou( - Amounts const& order, - Quality quality, - STAmount const& owner_funds, - STAmount const& taker_funds, - Rate const& rate_out) -{ - Flow f; - f.order = order; - f.issuers.out = multiply(f.order.out, rate_out); - - log_flow("flow_xrp_to_iou", f); - - // Clamp on owner balance - if (owner_funds < f.issuers.out) - { - f.issuers.out = owner_funds; - f.order.out = divide(f.issuers.out, rate_out); - f.order.in = qual_mul(f.order.out, quality, f.order.in); - log_flow("(clamped on owner balance)", f); - } - - // Clamp if taker wants to limit the output - if (!sell_ && remaining_.out < f.order.out) - { - f.order.out = remaining_.out; - f.order.in = qual_mul(f.order.out, quality, f.order.in); - f.issuers.out = multiply(f.order.out, rate_out); - log_flow("(clamped on taker output)", f); - } - - // Clamp on the taker's funds - if (taker_funds < f.order.in) - { - f.order.in = taker_funds; - f.order.out = qual_div(f.order.in, quality, f.order.out); - f.issuers.out = multiply(f.order.out, rate_out); - log_flow("(clamped on taker funds)", f); - } - - // Clamp on remaining offer if we are not handling the second leg - // of an autobridge. - if (cross_type_ == CrossType::XrpToIou && (remaining_.in < f.order.in)) - { - f.order.in = remaining_.in; - f.order.out = qual_div(f.order.in, quality, f.order.out); - f.issuers.out = multiply(f.order.out, rate_out); - log_flow("(clamped on taker input)", f); - } - - return f; -} - -BasicTaker::Flow -BasicTaker::flow_iou_to_xrp( - Amounts const& order, - Quality quality, - STAmount const& owner_funds, - STAmount const& taker_funds, - Rate const& rate_in) -{ - Flow f; - f.order = order; - f.issuers.in = multiply(f.order.in, rate_in); - - log_flow("flow_iou_to_xrp", f); - - // Clamp on owner's funds - if (owner_funds < f.order.out) - { - f.order.out = owner_funds; - f.order.in = qual_mul(f.order.out, quality, f.order.in); - f.issuers.in = multiply(f.order.in, rate_in); - log_flow("(clamped on owner funds)", f); - } - - // Clamp if taker wants to limit the output and we are not the - // first leg of an autobridge. - if (!sell_ && cross_type_ == CrossType::IouToXrp) - { - if (remaining_.out < f.order.out) - { - f.order.out = remaining_.out; - f.order.in = qual_mul(f.order.out, quality, f.order.in); - f.issuers.in = multiply(f.order.in, rate_in); - log_flow("(clamped on taker output)", f); - } - } - - // Clamp on the taker's input offer - if (remaining_.in < f.order.in) - { - f.order.in = remaining_.in; - f.issuers.in = multiply(f.order.in, rate_in); - f.order.out = qual_div(f.order.in, quality, f.order.out); - log_flow("(clamped on taker input)", f); - } - - // Clamp on the taker's input balance - if (taker_funds < f.issuers.in) - { - f.issuers.in = taker_funds; - f.order.in = divide(f.issuers.in, rate_in); - f.order.out = qual_div(f.order.in, quality, f.order.out); - log_flow("(clamped on taker funds)", f); - } - - return f; -} - -BasicTaker::Flow -BasicTaker::flow_iou_to_iou( - Amounts const& order, - Quality quality, - STAmount const& owner_funds, - STAmount const& taker_funds, - Rate const& rate_in, - Rate const& rate_out) -{ - Flow f; - f.order = order; - f.issuers.in = multiply(f.order.in, rate_in); - f.issuers.out = multiply(f.order.out, rate_out); - - log_flow("flow_iou_to_iou", f); - - // Clamp on owner balance - if (owner_funds < f.issuers.out) - { - f.issuers.out = owner_funds; - f.order.out = divide(f.issuers.out, rate_out); - f.order.in = qual_mul(f.order.out, quality, f.order.in); - f.issuers.in = multiply(f.order.in, rate_in); - log_flow("(clamped on owner funds)", f); - } - - // Clamp on taker's offer - if (!sell_ && remaining_.out < f.order.out) - { - f.order.out = remaining_.out; - f.order.in = qual_mul(f.order.out, quality, f.order.in); - f.issuers.out = multiply(f.order.out, rate_out); - f.issuers.in = multiply(f.order.in, rate_in); - log_flow("(clamped on taker output)", f); - } - - // Clamp on the taker's input offer - if (remaining_.in < f.order.in) - { - f.order.in = remaining_.in; - f.issuers.in = multiply(f.order.in, rate_in); - f.order.out = qual_div(f.order.in, quality, f.order.out); - f.issuers.out = multiply(f.order.out, rate_out); - log_flow("(clamped on taker input)", f); - } - - // Clamp on the taker's input balance - if (taker_funds < f.issuers.in) - { - f.issuers.in = taker_funds; - f.order.in = divide(f.issuers.in, rate_in); - f.order.out = qual_div(f.order.in, quality, f.order.out); - f.issuers.out = multiply(f.order.out, rate_out); - log_flow("(clamped on taker funds)", f); - } - - return f; -} - -// Calculates the direct flow through the specified offer -BasicTaker::Flow -BasicTaker::do_cross(Amounts offer, Quality quality, AccountID const& owner) -{ - auto const owner_funds = get_funds(owner, offer.out); - auto const taker_funds = get_funds(account(), offer.in); - - Flow result; - - if (cross_type_ == CrossType::XrpToIou) - { - result = flow_xrp_to_iou( - offer, - quality, - owner_funds, - taker_funds, - out_rate(owner, account())); - } - else if (cross_type_ == CrossType::IouToXrp) - { - result = flow_iou_to_xrp( - offer, - quality, - owner_funds, - taker_funds, - in_rate(owner, account())); - } - else - { - result = flow_iou_to_iou( - offer, - quality, - owner_funds, - taker_funds, - in_rate(owner, account()), - out_rate(owner, account())); - } - - if (!result.sanity_check()) - Throw("Computed flow fails sanity check."); - - remaining_.out -= result.order.out; - remaining_.in -= result.order.in; - - XRPL_ASSERT( - remaining_.in >= beast::zero, - "ripple::BasicTaker::do_cross : minimum remaining in"); - - return result; -} - -// Calculates the bridged flow through the specified offers -std::pair -BasicTaker::do_cross( - Amounts offer1, - Quality quality1, - AccountID const& owner1, - Amounts offer2, - Quality quality2, - AccountID const& owner2) -{ - XRPL_ASSERT( - !offer1.in.native(), - "ripple::BasicTaker::do_cross : offer1 in is not XRP"); - XRPL_ASSERT( - offer1.out.native(), - "ripple::BasicTaker::do_cross : offer1 out is XRP"); - XRPL_ASSERT( - offer2.in.native(), "ripple::BasicTaker::do_cross : offer2 in is XRP"); - XRPL_ASSERT( - !offer2.out.native(), - "ripple::BasicTaker::do_cross : offer2 out is not XRP"); - - // If the taker owns the first leg of the offer, then the taker's available - // funds aren't the limiting factor for the input - the offer itself is. - auto leg1_in_funds = get_funds(account(), offer1.in); - - if (account() == owner1) - { - JLOG(journal_.trace()) << "The taker owns the first leg of a bridge."; - leg1_in_funds = std::max(leg1_in_funds, offer1.in); - } - - // If the taker owns the second leg of the offer, then the taker's available - // funds are not the limiting factor for the output - the offer itself is. - auto leg2_out_funds = get_funds(owner2, offer2.out); - - if (account() == owner2) - { - JLOG(journal_.trace()) << "The taker owns the second leg of a bridge."; - leg2_out_funds = std::max(leg2_out_funds, offer2.out); - } - - // The amount available to flow via XRP is the amount that the owner of the - // first leg of the bridge has, up to the first leg's output. - // - // But, when both legs of a bridge are owned by the same person, the amount - // of XRP that can flow between the two legs is, essentially, infinite - // since all the owner is doing is taking out XRP of his left pocket - // and putting it in his right pocket. In that case, we set the available - // XRP to the largest of the two offers. - auto xrp_funds = get_funds(owner1, offer1.out); - - if (owner1 == owner2) - { - JLOG(journal_.trace()) - << "The bridge endpoints are owned by the same account."; - xrp_funds = std::max(offer1.out, offer2.in); - } - - if (auto stream = journal_.debug()) - { - stream << "Available bridge funds:"; - stream << " leg1 in: " << format_amount(leg1_in_funds); - stream << " leg2 out: " << format_amount(leg2_out_funds); - stream << " xrp: " << format_amount(xrp_funds); - } - - auto const leg1_rate = in_rate(owner1, account()); - auto const leg2_rate = out_rate(owner2, account()); - - // Attempt to determine the maximal flow that can be achieved across each - // leg independent of the other. - auto flow1 = - flow_iou_to_xrp(offer1, quality1, xrp_funds, leg1_in_funds, leg1_rate); - - if (!flow1.sanity_check()) - Throw("Computed flow1 fails sanity check."); - - auto flow2 = - flow_xrp_to_iou(offer2, quality2, leg2_out_funds, xrp_funds, leg2_rate); - - if (!flow2.sanity_check()) - Throw("Computed flow2 fails sanity check."); - - // We now have the maximal flows across each leg individually. We need to - // equalize them, so that the amount of XRP that flows out of the first leg - // is the same as the amount of XRP that flows into the second leg. We take - // the side which is the limiting factor (if any) and adjust the other. - if (flow1.order.out < flow2.order.in) - { - // Adjust the second leg of the offer down: - flow2.order.in = flow1.order.out; - flow2.order.out = qual_div(flow2.order.in, quality2, flow2.order.out); - flow2.issuers.out = multiply(flow2.order.out, leg2_rate); - log_flow("Balancing: adjusted second leg down", flow2); - } - else if (flow1.order.out > flow2.order.in) - { - // Adjust the first leg of the offer down: - flow1.order.out = flow2.order.in; - flow1.order.in = qual_mul(flow1.order.out, quality1, flow1.order.in); - flow1.issuers.in = multiply(flow1.order.in, leg1_rate); - log_flow("Balancing: adjusted first leg down", flow2); - } - - if (flow1.order.out != flow2.order.in) - Throw("Bridged flow is out of balance."); - - remaining_.out -= flow2.order.out; - remaining_.in -= flow1.order.in; - - return std::make_pair(flow1, flow2); -} - -//============================================================================== - -Taker::Taker( - CrossType cross_type, - ApplyView& view, - AccountID const& account, - Amounts const& offer, - std::uint32_t flags, - beast::Journal journal) - : BasicTaker( - cross_type, - account, - offer, - Quality(offer), - flags, - calculateRate(view, offer.in.getIssuer(), account), - calculateRate(view, offer.out.getIssuer(), account), - journal) - , view_(view) - , xrp_flow_(0) - , direct_crossings_(0) - , bridge_crossings_(0) -{ - XRPL_ASSERT( - issue_in() == offer.in.issue(), - "ripple::Taker::Taker : issue in is a match"); - XRPL_ASSERT( - issue_out() == offer.out.issue(), - "ripple::Taker::Taker : issue out is a match"); - - if (auto stream = journal_.debug()) - { - stream << "Crossing as: " << to_string(account); - - if (isXRP(issue_in())) - stream << " Offer in: " << format_amount(offer.in); - else - stream << " Offer in: " << format_amount(offer.in) - << " (issuer: " << issue_in().account << ")"; - - if (isXRP(issue_out())) - stream << " Offer out: " << format_amount(offer.out); - else - stream << " Offer out: " << format_amount(offer.out) - << " (issuer: " << issue_out().account << ")"; - - stream << " Balance: " - << format_amount(get_funds(account, offer.in)); - } -} - -void -Taker::consume_offer(Offer& offer, Amounts const& order) -{ - if (order.in < beast::zero) - Throw("flow with negative input."); - - if (order.out < beast::zero) - Throw("flow with negative output."); - - JLOG(journal_.debug()) << "Consuming from offer " << offer; - - if (auto stream = journal_.trace()) - { - auto const& available = offer.amount(); - - stream << " in:" << format_amount(available.in); - stream << " out:" << format_amount(available.out); - } - - offer.consume(view_, order); -} - -STAmount -Taker::get_funds(AccountID const& account, STAmount const& amount) const -{ - return accountFunds(view_, account, amount, fhZERO_IF_FROZEN, journal_); -} - -TER -Taker::transferXRP( - AccountID const& from, - AccountID const& to, - STAmount const& amount) -{ - if (!isXRP(amount)) - Throw("Using transferXRP with IOU"); - - if (from == to) - return tesSUCCESS; - - // Transferring zero is equivalent to not doing a transfer - if (amount == beast::zero) - return tesSUCCESS; - - return ripple::transferXRP(view_, from, to, amount, journal_); -} - -TER -Taker::redeemIOU( - AccountID const& account, - STAmount const& amount, - Issue const& issue) -{ - if (isXRP(amount)) - Throw("Using redeemIOU with XRP"); - - if (account == issue.account) - return tesSUCCESS; - - // Transferring zero is equivalent to not doing a transfer - if (amount == beast::zero) - return tesSUCCESS; - - // If we are trying to redeem some amount, then the account - // must have a credit balance. - if (get_funds(account, amount) <= beast::zero) - Throw("redeemIOU has no funds to redeem"); - - auto ret = ripple::redeemIOU(view_, account, amount, issue, journal_); - - if (get_funds(account, amount) < beast::zero) - Throw("redeemIOU redeemed more funds than available"); - - return ret; -} - -TER -Taker::issueIOU( - AccountID const& account, - STAmount const& amount, - Issue const& issue) -{ - if (isXRP(amount)) - Throw("Using issueIOU with XRP"); - - if (account == issue.account) - return tesSUCCESS; - - // Transferring zero is equivalent to not doing a transfer - if (amount == beast::zero) - return tesSUCCESS; - - return ripple::issueIOU(view_, account, amount, issue, journal_); -} - -// Performs funds transfers to fill the given offer and adjusts offer. -TER -Taker::fill(BasicTaker::Flow const& flow, Offer& offer) -{ - // adjust offer - consume_offer(offer, flow.order); - - TER result = tesSUCCESS; - - if (cross_type() != CrossType::XrpToIou) - { - XRPL_ASSERT( - !isXRP(flow.order.in), "ripple::Taker::fill : order in is not XRP"); - - if (result == tesSUCCESS) - result = - redeemIOU(account(), flow.issuers.in, flow.issuers.in.issue()); - - if (result == tesSUCCESS) - result = - issueIOU(offer.owner(), flow.order.in, flow.order.in.issue()); - } - else - { - XRPL_ASSERT( - isXRP(flow.order.in), "ripple::Taker::fill : order in is XRP"); - - if (result == tesSUCCESS) - result = transferXRP(account(), offer.owner(), flow.order.in); - } - - // Now send funds from the account whose offer we're taking - if (cross_type() != CrossType::IouToXrp) - { - XRPL_ASSERT( - !isXRP(flow.order.out), - "ripple::Taker::fill : order out is not XRP"); - - if (result == tesSUCCESS) - result = redeemIOU( - offer.owner(), flow.issuers.out, flow.issuers.out.issue()); - - if (result == tesSUCCESS) - result = - issueIOU(account(), flow.order.out, flow.order.out.issue()); - } - else - { - XRPL_ASSERT( - isXRP(flow.order.out), "ripple::Taker::fill : order out is XRP"); - - if (result == tesSUCCESS) - result = transferXRP(offer.owner(), account(), flow.order.out); - } - - if (result == tesSUCCESS) - direct_crossings_++; - - return result; -} - -// Performs bridged funds transfers to fill the given offers and adjusts offers. -TER -Taker::fill( - BasicTaker::Flow const& flow1, - Offer& leg1, - BasicTaker::Flow const& flow2, - Offer& leg2) -{ - // Adjust offers accordingly - consume_offer(leg1, flow1.order); - consume_offer(leg2, flow2.order); - - TER result = tesSUCCESS; - - // Taker to leg1: IOU - if (leg1.owner() != account()) - { - if (result == tesSUCCESS) - result = redeemIOU( - account(), flow1.issuers.in, flow1.issuers.in.issue()); - - if (result == tesSUCCESS) - result = - issueIOU(leg1.owner(), flow1.order.in, flow1.order.in.issue()); - } - - // leg1 to leg2: bridging over XRP - if (result == tesSUCCESS) - result = transferXRP(leg1.owner(), leg2.owner(), flow1.order.out); - - // leg2 to Taker: IOU - if (leg2.owner() != account()) - { - if (result == tesSUCCESS) - result = redeemIOU( - leg2.owner(), flow2.issuers.out, flow2.issuers.out.issue()); - - if (result == tesSUCCESS) - result = - issueIOU(account(), flow2.order.out, flow2.order.out.issue()); - } - - if (result == tesSUCCESS) - { - bridge_crossings_++; - xrp_flow_ += flow1.order.out; - } - - return result; -} - -TER -Taker::cross(Offer& offer) -{ - // In direct crossings, at least one leg must not be XRP. - if (isXRP(offer.amount().in) && isXRP(offer.amount().out)) - return tefINTERNAL; - - auto const amount = - do_cross(offer.amount(), offer.quality(), offer.owner()); - - return fill(amount, offer); -} - -TER -Taker::cross(Offer& leg1, Offer& leg2) -{ - // In bridged crossings, XRP must can't be the input to the first leg - // or the output of the second leg. - if (isXRP(leg1.amount().in) || isXRP(leg2.amount().out)) - return tefINTERNAL; - - auto ret = do_cross( - leg1.amount(), - leg1.quality(), - leg1.owner(), - leg2.amount(), - leg2.quality(), - leg2.owner()); - - return fill(ret.first, leg1, ret.second, leg2); -} - -Rate -Taker::calculateRate( - ApplyView const& view, - AccountID const& issuer, - AccountID const& account) -{ - return isXRP(issuer) || (account == issuer) ? parityRate - : transferRate(view, issuer); -} - -} // namespace ripple diff --git a/src/xrpld/app/tx/detail/Taker.h b/src/xrpld/app/tx/detail/Taker.h deleted file mode 100644 index 3702a30deb..0000000000 --- a/src/xrpld/app/tx/detail/Taker.h +++ /dev/null @@ -1,341 +0,0 @@ -//------------------------------------------------------------------------------ -/* - 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. -*/ -//============================================================================== - -#ifndef RIPPLE_APP_BOOK_TAKER_H_INCLUDED -#define RIPPLE_APP_BOOK_TAKER_H_INCLUDED - -#include -#include - -#include -#include -#include -#include -#include - -namespace ripple { - -/** The flavor of an offer crossing */ -enum class CrossType { XrpToIou, IouToXrp, IouToIou }; - -/** State for the active party during order book or payment operations. */ -class BasicTaker -{ -private: - AccountID account_; - Quality quality_; - Quality threshold_; - - bool sell_; - - // The original in and out quantities. - Amounts const original_; - - // The amounts still left over for us to try and take. - Amounts remaining_; - - // The issuers for the input and output - Issue const& issue_in_; - Issue const& issue_out_; - - // The rates that will be paid when the input and output currencies are - // transfered and the currency issuer isn't involved: - Rate const m_rate_in; - Rate const m_rate_out; - - // The type of crossing that we are performing - CrossType cross_type_; - -protected: - beast::Journal const journal_; - - struct Flow - { - explicit Flow() = default; - - Amounts order; - Amounts issuers; - - bool - sanity_check() const - { - using beast::zero; - - if (isXRP(order.in) && isXRP(order.out)) - return false; - - return order.in >= zero && order.out >= zero && - issuers.in >= zero && issuers.out >= zero; - } - }; - -private: - void - log_flow(char const* description, Flow const& flow); - - Flow - flow_xrp_to_iou( - Amounts const& offer, - Quality quality, - STAmount const& owner_funds, - STAmount const& taker_funds, - Rate const& rate_out); - - Flow - flow_iou_to_xrp( - Amounts const& offer, - Quality quality, - STAmount const& owner_funds, - STAmount const& taker_funds, - Rate const& rate_in); - - Flow - flow_iou_to_iou( - Amounts const& offer, - Quality quality, - STAmount const& owner_funds, - STAmount const& taker_funds, - Rate const& rate_in, - Rate const& rate_out); - - // Calculates the transfer rate that we should use when calculating - // flows for a particular issue between two accounts. - static Rate - effective_rate( - Rate const& rate, - Issue const& issue, - AccountID const& from, - AccountID const& to); - - // The transfer rate for the input currency between the given accounts - Rate - in_rate(AccountID const& from, AccountID const& to) const - { - return effective_rate(m_rate_in, original_.in.issue(), from, to); - } - - // The transfer rate for the output currency between the given accounts - Rate - out_rate(AccountID const& from, AccountID const& to) const - { - return effective_rate(m_rate_out, original_.out.issue(), from, to); - } - -public: - BasicTaker() = delete; - BasicTaker(BasicTaker const&) = delete; - - BasicTaker( - CrossType cross_type, - AccountID const& account, - Amounts const& amount, - Quality const& quality, - std::uint32_t flags, - Rate const& rate_in, - Rate const& rate_out, - beast::Journal journal = beast::Journal{beast::Journal::getNullSink()}); - - virtual ~BasicTaker() = default; - - /** 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 (quality_) - */ - Amounts - remaining_offer() const; - - /** Returns the amount that the offer was originally placed at. */ - Amounts const& - original_offer() const; - - /** Returns the account identifier of the taker. */ - AccountID const& - account() const noexcept - { - return account_; - } - - /** Returns `true` if the quality does not meet the taker's requirements. */ - bool - reject(Quality const& quality) const noexcept - { - return quality < threshold_; - } - - /** Returns the type of crossing that is being performed */ - CrossType - cross_type() const - { - return cross_type_; - } - - /** Returns the Issue associated with the input of the offer */ - Issue const& - issue_in() const - { - return issue_in_; - } - - /** Returns the Issue associated with the output of the offer */ - Issue const& - issue_out() const - { - return issue_out_; - } - - /** Returns `true` if the taker has run out of funds. */ - bool - unfunded() const; - - /** 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; - - /** Perform direct crossing through given offer. - @return an `Amounts` describing the flow achieved during cross - */ - BasicTaker::Flow - do_cross(Amounts offer, Quality quality, AccountID const& owner); - - /** Perform bridged crossing through given offers. - @return a pair of `Amounts` describing the flow achieved during cross - */ - std::pair - do_cross( - Amounts offer1, - Quality quality1, - AccountID const& owner1, - Amounts offer2, - Quality quality2, - AccountID const& owner2); - - virtual STAmount - get_funds(AccountID const& account, STAmount const& funds) const = 0; -}; - -//------------------------------------------------------------------------------ - -class Taker : public BasicTaker -{ -public: - Taker() = delete; - Taker(Taker const&) = delete; - - Taker( - CrossType cross_type, - ApplyView& view, - AccountID const& account, - Amounts const& offer, - std::uint32_t flags, - beast::Journal journal); - ~Taker() = default; - - void - consume_offer(Offer& offer, Amounts const& order); - - STAmount - get_funds(AccountID const& account, STAmount const& funds) const override; - - STAmount const& - get_xrp_flow() const - { - return xrp_flow_; - } - - std::uint32_t - get_direct_crossings() const - { - return direct_crossings_; - } - - std::uint32_t - get_bridge_crossings() const - { - return bridge_crossings_; - } - - /** Perform a direct or bridged offer crossing as appropriate. - Funds will be transferred accordingly, and offers will be adjusted. - @return tesSUCCESS if successful, or an error code otherwise. - */ - /** @{ */ - TER - cross(Offer& offer); - - TER - cross(Offer& leg1, Offer& leg2); - /** @} */ - -private: - static Rate - calculateRate( - ApplyView const& view, - AccountID const& issuer, - AccountID const& account); - - TER - fill(BasicTaker::Flow const& flow, Offer& offer); - - TER - fill( - BasicTaker::Flow const& flow1, - Offer& leg1, - BasicTaker::Flow const& flow2, - Offer& leg2); - - TER - transferXRP( - AccountID const& from, - AccountID const& to, - STAmount const& amount); - - TER - redeemIOU( - AccountID const& account, - STAmount const& amount, - Issue const& issue); - - TER - issueIOU( - AccountID const& account, - STAmount const& amount, - Issue const& issue); - -private: - // The underlying ledger entry we are dealing with - ApplyView& view_; - - // The amount of XRP that flowed if we were autobridging - STAmount xrp_flow_; - - // The number direct crossings that we performed - std::uint32_t direct_crossings_; - - // The number autobridged crossings that we performed - std::uint32_t bridge_crossings_; -}; - -} // namespace ripple - -#endif