mirror of
https://github.com/Xahau/xahaud.git
synced 2025-12-02 08:25:55 +00:00
Transfer fee changes:
An offer owner pays the transfer fee and only change a transfer fee when transfering an IOU (as the old code does).
This commit is contained in:
@@ -62,6 +62,7 @@ flow (
|
|||||||
STPathSet const& paths,
|
STPathSet const& paths,
|
||||||
bool defaultPaths,
|
bool defaultPaths,
|
||||||
bool partialPayment,
|
bool partialPayment,
|
||||||
|
bool ownerPaysTransferFee,
|
||||||
boost::optional<Quality> const& limitQuality,
|
boost::optional<Quality> const& limitQuality,
|
||||||
boost::optional<STAmount> const& sendMax,
|
boost::optional<STAmount> const& sendMax,
|
||||||
beast::Journal j)
|
beast::Journal j)
|
||||||
@@ -83,7 +84,7 @@ flow (
|
|||||||
// convert the paths to a collection of strands. Each strand is the collection
|
// convert the paths to a collection of strands. Each strand is the collection
|
||||||
// of account->account steps and book steps that may be used in this payment.
|
// of account->account steps and book steps that may be used in this payment.
|
||||||
auto sr = toStrands (sb, src, dst, dstIssue, sendMaxIssue, paths,
|
auto sr = toStrands (sb, src, dst, dstIssue, sendMaxIssue, paths,
|
||||||
defaultPaths, j);
|
defaultPaths, ownerPaysTransferFee, j);
|
||||||
|
|
||||||
if (sr.first != tesSUCCESS)
|
if (sr.first != tesSUCCESS)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -51,6 +51,7 @@ flow (PaymentSandbox& view,
|
|||||||
STPathSet const& paths,
|
STPathSet const& paths,
|
||||||
bool defaultPaths,
|
bool defaultPaths,
|
||||||
bool partialPayment,
|
bool partialPayment,
|
||||||
|
bool ownerPaysTransferFee,
|
||||||
boost::optional<Quality> const& limitQuality,
|
boost::optional<Quality> const& limitQuality,
|
||||||
boost::optional<STAmount> const& sendMax,
|
boost::optional<STAmount> const& sendMax,
|
||||||
beast::Journal j);
|
beast::Journal j);
|
||||||
|
|||||||
@@ -133,9 +133,11 @@ RippleCalc::Output RippleCalc::rippleCalculate (
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
bool const ownerPaysTransferFee =
|
||||||
|
view.rules ().enabled (featureOwnerPaysFee, config.features);
|
||||||
flowV2Out = flow (flowV2SB, saDstAmountReq, uSrcAccountID,
|
flowV2Out = flow (flowV2SB, saDstAmountReq, uSrcAccountID,
|
||||||
uDstAccountID, spsPaths, defaultPaths, partialPayment,
|
uDstAccountID, spsPaths, defaultPaths, partialPayment,
|
||||||
limitQuality, sendMax, j);
|
ownerPaysTransferFee, limitQuality, sendMax, j);
|
||||||
}
|
}
|
||||||
catch (std::exception& e)
|
catch (std::exception& e)
|
||||||
{
|
{
|
||||||
@@ -144,25 +146,28 @@ RippleCalc::Output RippleCalc::rippleCalculate (
|
|||||||
Throw();
|
Throw();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (callFlowV2 && callFlowV1 &&
|
if (j.debug())
|
||||||
(flowV2Out.result () != flowV1Out.result () ||
|
|
||||||
(flowV2Out.result () == tesSUCCESS &&
|
|
||||||
(flowV2Out.actualAmountIn != flowV1Out.actualAmountIn ||
|
|
||||||
flowV2Out.actualAmountOut != flowV1Out.actualAmountOut))))
|
|
||||||
{
|
{
|
||||||
JLOG (j.trace()) <<
|
auto logResult = [&] (std::string const& algoName, Output const& result)
|
||||||
"Mismatch: New Flow and RippleCalc" <<
|
{
|
||||||
" Old actualIn: " << flowV1Out.actualAmountIn <<
|
j.debug() << "RippleCalc Result> " <<
|
||||||
" New actualIn: " << flowV2Out.actualAmountIn <<
|
" actualIn: " << result.actualAmountIn <<
|
||||||
" Old actualOut: " << flowV1Out.actualAmountOut <<
|
", actualOut: " << result.actualAmountOut <<
|
||||||
" New actualOut: " << flowV2Out.actualAmountOut <<
|
", result: " << result.result () <<
|
||||||
" Old result: " << flowV1Out.result () <<
|
", dstAmtReq: " << saDstAmountReq <<
|
||||||
" New result: " << flowV2Out.result();
|
", sendMax: " << saMaxAmountReq <<
|
||||||
}
|
", algo: " << algoName;
|
||||||
else
|
};
|
||||||
{
|
if (callFlowV1)
|
||||||
JLOG (j.trace()) << "Match: New Flow and RippleCalc";
|
{
|
||||||
|
logResult ("V1", flowV1Out);
|
||||||
|
}
|
||||||
|
if (callFlowV2)
|
||||||
|
{
|
||||||
|
logResult ("V2", flowV2Out);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
JLOG (j.trace()) << "Using old flow: " << useFlowV1Output;
|
JLOG (j.trace()) << "Using old flow: " << useFlowV1Output;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -52,6 +52,9 @@ private:
|
|||||||
Book book_;
|
Book book_;
|
||||||
AccountID strandSrc_;
|
AccountID strandSrc_;
|
||||||
AccountID strandDst_;
|
AccountID strandDst_;
|
||||||
|
// Charge transfer fees whan the prev step redeems
|
||||||
|
Step const* const prevStep_ = nullptr;
|
||||||
|
bool const ownerPaysTransferFee_;
|
||||||
beast::Journal j_;
|
beast::Journal j_;
|
||||||
|
|
||||||
struct Cache
|
struct Cache
|
||||||
@@ -59,7 +62,6 @@ private:
|
|||||||
TIn in;
|
TIn in;
|
||||||
TOut out;
|
TOut out;
|
||||||
|
|
||||||
Cache () = default;
|
|
||||||
Cache (TIn const& in_, TOut const& out_)
|
Cache (TIn const& in_, TOut const& out_)
|
||||||
: in (in_), out (out_)
|
: in (in_), out (out_)
|
||||||
{
|
{
|
||||||
@@ -73,10 +75,14 @@ public:
|
|||||||
Issue const& out,
|
Issue const& out,
|
||||||
AccountID const& strandSrc,
|
AccountID const& strandSrc,
|
||||||
AccountID const& strandDst,
|
AccountID const& strandDst,
|
||||||
|
Step const* prevStep,
|
||||||
|
bool ownerPaysTransferFee,
|
||||||
beast::Journal j)
|
beast::Journal j)
|
||||||
: book_ (in, out)
|
: book_ (in, out)
|
||||||
, strandSrc_ (strandSrc)
|
, strandSrc_ (strandSrc)
|
||||||
, strandDst_ (strandDst)
|
, strandDst_ (strandDst)
|
||||||
|
, prevStep_ (prevStep)
|
||||||
|
, ownerPaysTransferFee_ (ownerPaysTransferFee)
|
||||||
, j_ (j)
|
, j_ (j)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
@@ -102,6 +108,12 @@ public:
|
|||||||
return EitherAmount (cache_->out);
|
return EitherAmount (cache_->out);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
redeems (ReadView const& sb, bool fwd) const override
|
||||||
|
{
|
||||||
|
return !ownerPaysTransferFee_;
|
||||||
|
}
|
||||||
|
|
||||||
boost::optional<Book>
|
boost::optional<Book>
|
||||||
bookStepBook () const override
|
bookStepBook () const override
|
||||||
{
|
{
|
||||||
@@ -147,7 +159,8 @@ private:
|
|||||||
void consumeOffer (PaymentSandbox& sb,
|
void consumeOffer (PaymentSandbox& sb,
|
||||||
TOffer<TIn, TOut>& offer,
|
TOffer<TIn, TOut>& offer,
|
||||||
TAmounts<TIn, TOut> const& ofrAmt,
|
TAmounts<TIn, TOut> const& ofrAmt,
|
||||||
TAmounts<TIn, TOut> const& stepAmt) const;
|
TAmounts<TIn, TOut> const& stepAmt,
|
||||||
|
TOut const& ownerGives) const;
|
||||||
|
|
||||||
std::string logString () const override
|
std::string logString () const override
|
||||||
{
|
{
|
||||||
@@ -178,7 +191,9 @@ static
|
|||||||
void limitStepIn (Quality const& ofrQ,
|
void limitStepIn (Quality const& ofrQ,
|
||||||
TAmounts<TIn, TOut>& ofrAmt,
|
TAmounts<TIn, TOut>& ofrAmt,
|
||||||
TAmounts<TIn, TOut>& stpAmt,
|
TAmounts<TIn, TOut>& stpAmt,
|
||||||
|
TOut& ownerGives,
|
||||||
std::uint32_t transferRateIn,
|
std::uint32_t transferRateIn,
|
||||||
|
std::uint32_t transferRateOut,
|
||||||
TIn const& limit)
|
TIn const& limit)
|
||||||
{
|
{
|
||||||
if (limit < stpAmt.in)
|
if (limit < stpAmt.in)
|
||||||
@@ -188,6 +203,8 @@ void limitStepIn (Quality const& ofrQ,
|
|||||||
stpAmt.in, QUALITY_ONE, transferRateIn, /*roundUp*/ false);
|
stpAmt.in, QUALITY_ONE, transferRateIn, /*roundUp*/ false);
|
||||||
ofrAmt = ofrQ.ceil_in (ofrAmt, inLmt);
|
ofrAmt = ofrQ.ceil_in (ofrAmt, inLmt);
|
||||||
stpAmt.out = ofrAmt.out;
|
stpAmt.out = ofrAmt.out;
|
||||||
|
ownerGives = mulRatio (
|
||||||
|
ofrAmt.out, transferRateOut, QUALITY_ONE, /*roundUp*/ false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -197,13 +214,17 @@ static
|
|||||||
void limitStepOut (Quality const& ofrQ,
|
void limitStepOut (Quality const& ofrQ,
|
||||||
TAmounts<TIn, TOut>& ofrAmt,
|
TAmounts<TIn, TOut>& ofrAmt,
|
||||||
TAmounts<TIn, TOut>& stpAmt,
|
TAmounts<TIn, TOut>& stpAmt,
|
||||||
|
TOut& ownerGives,
|
||||||
std::uint32_t transferRateIn,
|
std::uint32_t transferRateIn,
|
||||||
|
std::uint32_t transferRateOut,
|
||||||
TOut const& limit)
|
TOut const& limit)
|
||||||
{
|
{
|
||||||
if (limit < stpAmt.out)
|
if (limit < stpAmt.out)
|
||||||
{
|
{
|
||||||
stpAmt.out = limit;
|
stpAmt.out = limit;
|
||||||
ofrAmt = ofrQ.ceil_out (ofrAmt, limit);
|
ownerGives = mulRatio (
|
||||||
|
stpAmt.out, transferRateOut, QUALITY_ONE, /*roundUp*/ false);
|
||||||
|
ofrAmt = ofrQ.ceil_out (ofrAmt, stpAmt.out);
|
||||||
stpAmt.in = mulRatio (
|
stpAmt.in = mulRatio (
|
||||||
ofrAmt.in, transferRateIn, QUALITY_ONE, /*roundUp*/ true);
|
ofrAmt.in, transferRateIn, QUALITY_ONE, /*roundUp*/ true);
|
||||||
}
|
}
|
||||||
@@ -225,18 +246,28 @@ forEachOffer (
|
|||||||
Book const& book,
|
Book const& book,
|
||||||
AccountID const& src,
|
AccountID const& src,
|
||||||
AccountID const& dst,
|
AccountID const& dst,
|
||||||
|
bool prevStepRedeems,
|
||||||
|
bool ownerPaysTransferFee,
|
||||||
Callback& callback,
|
Callback& callback,
|
||||||
std::uint32_t limit,
|
std::uint32_t limit,
|
||||||
beast::Journal j)
|
beast::Journal j)
|
||||||
{
|
{
|
||||||
|
// Charge the offer owner, not the sender
|
||||||
|
// Charge a fee even if the owner is the same as the issuer
|
||||||
|
// (the old code does not charge a fee)
|
||||||
|
// Calculate amount that goes to the taker and the amount charged the offer owner
|
||||||
auto transferRate = [&](AccountID const& id)->std::uint32_t
|
auto transferRate = [&](AccountID const& id)->std::uint32_t
|
||||||
{
|
{
|
||||||
if (isXRP (id) || id == src || id == dst)
|
if (isXRP (id) || id == dst)
|
||||||
return QUALITY_ONE;
|
return QUALITY_ONE;
|
||||||
return rippleTransferRate (sb, id);
|
return rippleTransferRate (sb, id);
|
||||||
};
|
};
|
||||||
|
|
||||||
std::uint32_t const trIn = transferRate (book.in.account);
|
std::uint32_t const trIn =
|
||||||
|
prevStepRedeems ? transferRate (book.in.account) : QUALITY_ONE;
|
||||||
|
// Always charge the transfer fee, even if the owner is the issuer
|
||||||
|
std::uint32_t const trOut =
|
||||||
|
ownerPaysTransferFee ? transferRate (book.out.account) : QUALITY_ONE;
|
||||||
|
|
||||||
typename FlowOfferStream<TAmtIn, TAmtOut>::StepCounter counter (limit, j);
|
typename FlowOfferStream<TAmtIn, TAmtOut>::StepCounter counter (limit, j);
|
||||||
FlowOfferStream<TAmtIn, TAmtOut> offers (
|
FlowOfferStream<TAmtIn, TAmtOut> offers (
|
||||||
@@ -251,16 +282,31 @@ forEachOffer (
|
|||||||
else if (*ofrQ != offer.quality ())
|
else if (*ofrQ != offer.quality ())
|
||||||
break;
|
break;
|
||||||
|
|
||||||
auto const funds = offers.ownerFunds ();
|
|
||||||
auto ofrAmt = offer.amount ();
|
auto ofrAmt = offer.amount ();
|
||||||
auto stpAmt = make_Amounts (
|
auto stpAmt = make_Amounts (
|
||||||
mulRatio (ofrAmt.in, trIn, QUALITY_ONE, /*roundUp*/ true),
|
mulRatio (ofrAmt.in, trIn, QUALITY_ONE, /*roundUp*/ true),
|
||||||
ofrAmt.out);
|
ofrAmt.out);
|
||||||
|
// owner pays the transfer fee
|
||||||
|
auto ownerGives =
|
||||||
|
mulRatio (ofrAmt.out, trOut, QUALITY_ONE, /*roundUp*/ false);
|
||||||
|
|
||||||
if (funds < stpAmt.out)
|
auto const funds =
|
||||||
limitStepOut (*ofrQ, ofrAmt, stpAmt, trIn, funds);
|
(offer.owner () == offer.issueOut ().account)
|
||||||
|
? ownerGives // Offer owner is issuer; they have unlimited funds
|
||||||
|
: offers.ownerFunds ();
|
||||||
|
|
||||||
if (!callback (offer, ofrAmt, stpAmt, trIn))
|
if (funds < ownerGives)
|
||||||
|
{
|
||||||
|
// We already know offer.owner()!=offer.issueOut().account
|
||||||
|
ownerGives = funds;
|
||||||
|
stpAmt.out = mulRatio (
|
||||||
|
ownerGives, QUALITY_ONE, trOut, /*roundUp*/ false);
|
||||||
|
ofrAmt = ofrQ->ceil_out (ofrAmt, stpAmt.out);
|
||||||
|
stpAmt.in = mulRatio (
|
||||||
|
ofrAmt.in, trIn, QUALITY_ONE, /*roundUp*/ true);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!callback (offer, ofrAmt, stpAmt, ownerGives, trIn, trOut))
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -272,7 +318,8 @@ void BookStep<TIn, TOut>::consumeOffer (
|
|||||||
PaymentSandbox& sb,
|
PaymentSandbox& sb,
|
||||||
TOffer<TIn, TOut>& offer,
|
TOffer<TIn, TOut>& offer,
|
||||||
TAmounts<TIn, TOut> const& ofrAmt,
|
TAmounts<TIn, TOut> const& ofrAmt,
|
||||||
TAmounts<TIn, TOut> const& stepAmt) const
|
TAmounts<TIn, TOut> const& stepAmt,
|
||||||
|
TOut const& ownerGives) const
|
||||||
{
|
{
|
||||||
// The offer owner gets the ofrAmt. The difference between ofrAmt and stepAmt
|
// The offer owner gets the ofrAmt. The difference between ofrAmt and stepAmt
|
||||||
// is a transfer fee that goes to book_.in.account
|
// is a transfer fee that goes to book_.in.account
|
||||||
@@ -283,9 +330,11 @@ void BookStep<TIn, TOut>::consumeOffer (
|
|||||||
Throw<FlowException> (dr);
|
Throw<FlowException> (dr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The offer owner pays `ownerGives`. The difference between ownerGives and
|
||||||
|
// stepAmt is a transfer fee that goes to book_.out.account
|
||||||
{
|
{
|
||||||
auto const cr = accountSend (sb, offer.owner (), book_.out.account,
|
auto const cr = accountSend (sb, offer.owner (), book_.out.account,
|
||||||
toSTAmount (stepAmt.out, book_.out), j_);
|
toSTAmount (ownerGives, book_.out), j_);
|
||||||
if (cr != tesSUCCESS)
|
if (cr != tesSUCCESS)
|
||||||
Throw<FlowException> (cr);
|
Throw<FlowException> (cr);
|
||||||
}
|
}
|
||||||
@@ -330,7 +379,9 @@ BookStep<TIn, TOut>::revImp (
|
|||||||
[&](TOffer<TIn, TOut>& offer,
|
[&](TOffer<TIn, TOut>& offer,
|
||||||
TAmounts<TIn, TOut> const& ofrAmt,
|
TAmounts<TIn, TOut> const& ofrAmt,
|
||||||
TAmounts<TIn, TOut> const& stpAmt,
|
TAmounts<TIn, TOut> const& stpAmt,
|
||||||
std::uint32_t transferRateIn) mutable -> bool
|
TOut const& ownerGives,
|
||||||
|
std::uint32_t transferRateIn,
|
||||||
|
std::uint32_t transferRateOut) mutable -> bool
|
||||||
{
|
{
|
||||||
if (remainingOut <= beast::zero)
|
if (remainingOut <= beast::zero)
|
||||||
return false;
|
return false;
|
||||||
@@ -341,7 +392,7 @@ BookStep<TIn, TOut>::revImp (
|
|||||||
savedOuts.insert(stpAmt.out);
|
savedOuts.insert(stpAmt.out);
|
||||||
result = TAmounts<TIn, TOut>(sum (savedIns), sum(savedOuts));
|
result = TAmounts<TIn, TOut>(sum (savedIns), sum(savedOuts));
|
||||||
remainingOut = out - result.out;
|
remainingOut = out - result.out;
|
||||||
this->consumeOffer (sb, offer, ofrAmt, stpAmt);
|
this->consumeOffer (sb, offer, ofrAmt, stpAmt, ownerGives);
|
||||||
// return true b/c even if the payment is satisfied,
|
// return true b/c even if the payment is satisfied,
|
||||||
// we need to consume the offer
|
// we need to consume the offer
|
||||||
return true;
|
return true;
|
||||||
@@ -350,22 +401,24 @@ BookStep<TIn, TOut>::revImp (
|
|||||||
{
|
{
|
||||||
auto ofrAdjAmt = ofrAmt;
|
auto ofrAdjAmt = ofrAmt;
|
||||||
auto stpAdjAmt = stpAmt;
|
auto stpAdjAmt = stpAmt;
|
||||||
limitStepOut (
|
auto ownerGivesAdj = ownerGives;
|
||||||
offer.quality (), ofrAdjAmt, stpAdjAmt, transferRateIn, remainingOut);
|
limitStepOut (offer.quality (), ofrAdjAmt, stpAdjAmt, ownerGivesAdj,
|
||||||
|
transferRateIn, transferRateOut, remainingOut);
|
||||||
remainingOut = beast::zero;
|
remainingOut = beast::zero;
|
||||||
savedIns.insert (stpAdjAmt.in);
|
savedIns.insert (stpAdjAmt.in);
|
||||||
savedOuts.insert (remainingOut);
|
savedOuts.insert (remainingOut);
|
||||||
result.in = sum(savedIns);
|
result.in = sum(savedIns);
|
||||||
result.out = out;
|
result.out = out;
|
||||||
this->consumeOffer (sb, offer, ofrAdjAmt, stpAdjAmt);
|
this->consumeOffer (sb, offer, ofrAdjAmt, stpAdjAmt, ownerGivesAdj);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
{
|
{
|
||||||
auto const r = forEachOffer<TIn, TOut> (
|
auto const prevStepRedeems = prevStep_ && prevStep_->redeems (sb, false);
|
||||||
sb, afView, book_,
|
auto const r = forEachOffer<TIn, TOut> (sb, afView, book_, strandSrc_,
|
||||||
strandSrc_, strandDst_, eachOffer, maxOffersToConsume_, j_);
|
strandDst_, prevStepRedeems, ownerPaysTransferFee_, eachOffer,
|
||||||
|
maxOffersToConsume_, j_);
|
||||||
boost::container::flat_set<uint256> toRm = std::move(std::get<0>(r));
|
boost::container::flat_set<uint256> toRm = std::move(std::get<0>(r));
|
||||||
std::uint32_t const offersConsumed = std::get<1>(r);
|
std::uint32_t const offersConsumed = std::get<1>(r);
|
||||||
ofrsToRm.insert (boost::container::ordered_unique_range_t{},
|
ofrsToRm.insert (boost::container::ordered_unique_range_t{},
|
||||||
@@ -427,42 +480,93 @@ BookStep<TIn, TOut>::fwdImp (
|
|||||||
[&](TOffer<TIn, TOut>& offer,
|
[&](TOffer<TIn, TOut>& offer,
|
||||||
TAmounts<TIn, TOut> const& ofrAmt,
|
TAmounts<TIn, TOut> const& ofrAmt,
|
||||||
TAmounts<TIn, TOut> const& stpAmt,
|
TAmounts<TIn, TOut> const& stpAmt,
|
||||||
std::uint32_t transferRateIn) mutable -> bool
|
TOut const& ownerGives,
|
||||||
|
std::uint32_t transferRateIn,
|
||||||
|
std::uint32_t transferRateOut) mutable -> bool
|
||||||
{
|
{
|
||||||
|
assert(cache_);
|
||||||
|
|
||||||
if (remainingIn <= beast::zero)
|
if (remainingIn <= beast::zero)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
bool processMore = true;
|
||||||
|
auto ofrAdjAmt = ofrAmt;
|
||||||
|
auto stpAdjAmt = stpAmt;
|
||||||
|
auto ownerGivesAdj = ownerGives;
|
||||||
|
|
||||||
|
typename boost::container::flat_multiset<TOut>::const_iterator lastOut;
|
||||||
if (stpAmt.in <= remainingIn)
|
if (stpAmt.in <= remainingIn)
|
||||||
{
|
{
|
||||||
savedIns.insert(stpAmt.in);
|
savedIns.insert(stpAmt.in);
|
||||||
savedOuts.insert(stpAmt.out);
|
lastOut = savedOuts.insert(stpAmt.out);
|
||||||
result = TAmounts<TIn, TOut>(sum (savedIns), sum(savedOuts));
|
result = TAmounts<TIn, TOut>(sum (savedIns), sum(savedOuts));
|
||||||
remainingIn = in - result.in;
|
// consume the offer even if stepAmt.in == remainingIn
|
||||||
this->consumeOffer (sb, offer, ofrAmt, stpAmt);
|
processMore = true;
|
||||||
// return true b/c even if the payment is satisfied,
|
|
||||||
// we need to consume the offer
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
auto ofrAdjAmt = ofrAmt;
|
limitStepIn (offer.quality (), ofrAdjAmt, stpAdjAmt, ownerGivesAdj,
|
||||||
auto stpAdjAmt = stpAmt;
|
transferRateIn, transferRateOut, remainingIn);
|
||||||
limitStepIn (
|
|
||||||
offer.quality (), ofrAdjAmt, stpAdjAmt, transferRateIn, remainingIn);
|
|
||||||
savedIns.insert (remainingIn);
|
savedIns.insert (remainingIn);
|
||||||
savedOuts.insert (stpAdjAmt.out);
|
lastOut = savedOuts.insert (stpAdjAmt.out);
|
||||||
remainingIn = beast::zero;
|
|
||||||
result.out = sum (savedOuts);
|
result.out = sum (savedOuts);
|
||||||
result.in = in;
|
result.in = in;
|
||||||
this->consumeOffer (sb, offer, ofrAdjAmt, stpAdjAmt);
|
|
||||||
return false;
|
processMore = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (result.out > cache_->out && result.in <= cache_->in)
|
||||||
|
{
|
||||||
|
// The step produced more output in the forward pass than the
|
||||||
|
// reverse pass while consuming the same input (or less). If we
|
||||||
|
// compute the input required to produce the cached output
|
||||||
|
// (produced in the reverse step) and the input is equal to
|
||||||
|
// the input consumed in the forward step, then consume the
|
||||||
|
// input provided in the forward step and produce the output
|
||||||
|
// requested from the reverse step.
|
||||||
|
auto const lastOutAmt = *lastOut;
|
||||||
|
savedOuts.erase(lastOut);
|
||||||
|
auto const remainingOut = cache_->out - sum (savedOuts);
|
||||||
|
auto ofrAdjAmtRev = ofrAmt;
|
||||||
|
auto stpAdjAmtRev = stpAmt;
|
||||||
|
auto ownerGivesAdjRev = ownerGives;
|
||||||
|
limitStepOut (offer.quality (), ofrAdjAmtRev, stpAdjAmtRev,
|
||||||
|
ownerGivesAdjRev, transferRateIn, transferRateOut,
|
||||||
|
remainingOut);
|
||||||
|
|
||||||
|
if (stpAdjAmtRev.in == remainingIn)
|
||||||
|
{
|
||||||
|
result.in = in;
|
||||||
|
result.out = cache_->out;
|
||||||
|
|
||||||
|
savedIns.clear();
|
||||||
|
savedIns.insert(result.in);
|
||||||
|
savedOuts.clear();
|
||||||
|
savedOuts.insert(result.out);
|
||||||
|
|
||||||
|
ofrAdjAmt = ofrAdjAmtRev;
|
||||||
|
stpAdjAmt.in = remainingIn;
|
||||||
|
stpAdjAmt.out = remainingOut;
|
||||||
|
ownerGivesAdj = ownerGivesAdjRev;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// This is (likely) a problem case, and wil be caught
|
||||||
|
// with later checks
|
||||||
|
savedOuts.insert (lastOutAmt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
remainingIn = in - result.in;
|
||||||
|
this->consumeOffer (sb, offer, ofrAdjAmt, stpAdjAmt, ownerGivesAdj);
|
||||||
|
return processMore;
|
||||||
};
|
};
|
||||||
|
|
||||||
{
|
{
|
||||||
auto const r = forEachOffer<TIn, TOut> (
|
auto const prevStepRedeems = prevStep_ && prevStep_->redeems (sb, true);
|
||||||
sb, afView, book_,
|
auto const r = forEachOffer<TIn, TOut> (sb, afView, book_, strandSrc_,
|
||||||
strandSrc_, strandDst_, eachOffer, maxOffersToConsume_, j_);
|
strandDst_, prevStepRedeems, ownerPaysTransferFee_, eachOffer,
|
||||||
|
maxOffersToConsume_, j_);
|
||||||
boost::container::flat_set<uint256> toRm = std::move(std::get<0>(r));
|
boost::container::flat_set<uint256> toRm = std::move(std::get<0>(r));
|
||||||
std::uint32_t const offersConsumed = std::get<1>(r);
|
std::uint32_t const offersConsumed = std::get<1>(r);
|
||||||
ofrsToRm.insert (boost::container::ordered_unique_range_t{},
|
ofrsToRm.insert (boost::container::ordered_unique_range_t{},
|
||||||
@@ -607,7 +711,8 @@ make_BookStepHelper (
|
|||||||
Issue const& out)
|
Issue const& out)
|
||||||
{
|
{
|
||||||
auto r = std::make_unique<BookStep<TIn, TOut>> (
|
auto r = std::make_unique<BookStep<TIn, TOut>> (
|
||||||
in, out, ctx.strandSrc, ctx.strandDst, ctx.j);
|
in, out, ctx.strandSrc, ctx.strandDst, ctx.prevStep,
|
||||||
|
ctx.ownerPaysTransferFee, ctx.j);
|
||||||
auto ter = r->check (ctx);
|
auto ter = r->check (ctx);
|
||||||
if (ter != tesSUCCESS)
|
if (ter != tesSUCCESS)
|
||||||
return {ter, nullptr};
|
return {ter, nullptr};
|
||||||
|
|||||||
@@ -39,9 +39,10 @@ class DirectStepI : public StepImp<IOUAmount, IOUAmount, DirectStepI>
|
|||||||
AccountID src_;
|
AccountID src_;
|
||||||
AccountID dst_;
|
AccountID dst_;
|
||||||
Currency currency_;
|
Currency currency_;
|
||||||
// Transfer fees are never charged when sending directly to or from an
|
|
||||||
// issuing account
|
// Charge transfer fees when the prev step redeems
|
||||||
bool noTransferFee_;
|
Step const* const prevStep_ = nullptr;
|
||||||
|
|
||||||
beast::Journal j_;
|
beast::Journal j_;
|
||||||
|
|
||||||
struct Cache
|
struct Cache
|
||||||
@@ -49,15 +50,17 @@ class DirectStepI : public StepImp<IOUAmount, IOUAmount, DirectStepI>
|
|||||||
IOUAmount in;
|
IOUAmount in;
|
||||||
IOUAmount srcToDst;
|
IOUAmount srcToDst;
|
||||||
IOUAmount out;
|
IOUAmount out;
|
||||||
|
bool srcRedeems;
|
||||||
|
|
||||||
Cache () = default;
|
|
||||||
Cache (
|
Cache (
|
||||||
IOUAmount const& in_,
|
IOUAmount const& in_,
|
||||||
IOUAmount const& srcToDst_,
|
IOUAmount const& srcToDst_,
|
||||||
IOUAmount const& out_)
|
IOUAmount const& out_,
|
||||||
|
bool srcRedeems_)
|
||||||
: in(in_)
|
: in(in_)
|
||||||
, srcToDst(srcToDst_)
|
, srcToDst(srcToDst_)
|
||||||
, out(out_)
|
, out(out_)
|
||||||
|
, srcRedeems(srcRedeems_)
|
||||||
{}
|
{}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -67,18 +70,19 @@ class DirectStepI : public StepImp<IOUAmount, IOUAmount, DirectStepI>
|
|||||||
std::pair <std::uint32_t, std::uint32_t>
|
std::pair <std::uint32_t, std::uint32_t>
|
||||||
qualities (
|
qualities (
|
||||||
PaymentSandbox& sb,
|
PaymentSandbox& sb,
|
||||||
bool srcRedeems) const;
|
bool srcRedeems,
|
||||||
|
bool fwd) const;
|
||||||
public:
|
public:
|
||||||
DirectStepI (
|
DirectStepI (
|
||||||
AccountID const& src,
|
AccountID const& src,
|
||||||
AccountID const& dst,
|
AccountID const& dst,
|
||||||
Currency const& c,
|
Currency const& c,
|
||||||
bool noTransferFee,
|
Step const* prevStep,
|
||||||
beast::Journal j)
|
beast::Journal j)
|
||||||
:src_(src)
|
:src_(src)
|
||||||
, dst_(dst)
|
, dst_(dst)
|
||||||
, currency_ (c)
|
, currency_ (c)
|
||||||
, noTransferFee_ (noTransferFee)
|
, prevStep_ (prevStep)
|
||||||
, j_ (j) {}
|
, j_ (j) {}
|
||||||
|
|
||||||
AccountID const& src () const
|
AccountID const& src () const
|
||||||
@@ -115,6 +119,9 @@ class DirectStepI : public StepImp<IOUAmount, IOUAmount, DirectStepI>
|
|||||||
return src_;
|
return src_;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
redeems (ReadView const& sb, bool fwd) const override;
|
||||||
|
|
||||||
std::pair<IOUAmount, IOUAmount>
|
std::pair<IOUAmount, IOUAmount>
|
||||||
revImp (
|
revImp (
|
||||||
PaymentSandbox& sb,
|
PaymentSandbox& sb,
|
||||||
@@ -142,14 +149,14 @@ class DirectStepI : public StepImp<IOUAmount, IOUAmount, DirectStepI>
|
|||||||
void setCacheLimiting (
|
void setCacheLimiting (
|
||||||
IOUAmount const& fwdIn,
|
IOUAmount const& fwdIn,
|
||||||
IOUAmount const& fwdSrcToDst,
|
IOUAmount const& fwdSrcToDst,
|
||||||
IOUAmount const& fwdOut);
|
IOUAmount const& fwdOut,
|
||||||
|
bool srcRedeems);
|
||||||
|
|
||||||
friend bool operator==(DirectStepI const& lhs, DirectStepI const& rhs)
|
friend bool operator==(DirectStepI const& lhs, DirectStepI const& rhs)
|
||||||
{
|
{
|
||||||
return lhs.src_ == rhs.src_ &&
|
return lhs.src_ == rhs.src_ &&
|
||||||
lhs.dst_ == rhs.dst_ &&
|
lhs.dst_ == rhs.dst_ &&
|
||||||
lhs.currency_ == rhs.currency_ &&
|
lhs.currency_ == rhs.currency_;
|
||||||
lhs.noTransferFee_ == rhs.noTransferFee_;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
friend bool operator!=(DirectStepI const& lhs, DirectStepI const& rhs)
|
friend bool operator!=(DirectStepI const& lhs, DirectStepI const& rhs)
|
||||||
@@ -199,6 +206,25 @@ maxFlow (
|
|||||||
return {creditLimit2 (sb, dst, src, cur) + srcOwed, false};
|
return {creditLimit2 (sb, dst, src, cur) + srcOwed, false};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
DirectStepI::redeems (ReadView const& sb, bool fwd) const
|
||||||
|
{
|
||||||
|
if (!fwd)
|
||||||
|
{
|
||||||
|
auto const srcOwed = creditBalance2 (sb, dst_, src_, currency_);
|
||||||
|
return srcOwed.signum () > 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (!cache_)
|
||||||
|
{
|
||||||
|
assert (0);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return cache_->srcRedeems;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
std::pair<IOUAmount, IOUAmount>
|
std::pair<IOUAmount, IOUAmount>
|
||||||
DirectStepI::revImp (
|
DirectStepI::revImp (
|
||||||
PaymentSandbox& sb,
|
PaymentSandbox& sb,
|
||||||
@@ -214,7 +240,7 @@ DirectStepI::revImp (
|
|||||||
maxFlow (sb, src_, dst_, currency_);
|
maxFlow (sb, src_, dst_, currency_);
|
||||||
|
|
||||||
std::uint32_t srcQOut, dstQIn;
|
std::uint32_t srcQOut, dstQIn;
|
||||||
std::tie (srcQOut, dstQIn) = qualities (sb, srcRedeems);
|
std::tie (srcQOut, dstQIn) = qualities (sb, srcRedeems, false);
|
||||||
|
|
||||||
Issue const srcToDstIss (currency_, srcRedeems ? dst_ : src_);
|
Issue const srcToDstIss (currency_, srcRedeems ? dst_ : src_);
|
||||||
|
|
||||||
@@ -232,7 +258,8 @@ DirectStepI::revImp (
|
|||||||
cache_.emplace (
|
cache_.emplace (
|
||||||
IOUAmount (beast::zero),
|
IOUAmount (beast::zero),
|
||||||
IOUAmount (beast::zero),
|
IOUAmount (beast::zero),
|
||||||
IOUAmount (beast::zero));
|
IOUAmount (beast::zero),
|
||||||
|
srcRedeems);
|
||||||
return {beast::zero, beast::zero};
|
return {beast::zero, beast::zero};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -244,7 +271,7 @@ DirectStepI::revImp (
|
|||||||
|
|
||||||
IOUAmount const in = mulRatio (
|
IOUAmount const in = mulRatio (
|
||||||
srcToDst, srcQOut, QUALITY_ONE, /*roundUp*/ true);
|
srcToDst, srcQOut, QUALITY_ONE, /*roundUp*/ true);
|
||||||
cache_.emplace (in, srcToDst, out);
|
cache_.emplace (in, srcToDst, out, srcRedeems);
|
||||||
rippleCredit (sb,
|
rippleCredit (sb,
|
||||||
src_, dst_, toSTAmount (srcToDst, srcToDstIss),
|
src_, dst_, toSTAmount (srcToDst, srcToDstIss),
|
||||||
/*checkIssuer*/ true, j_);
|
/*checkIssuer*/ true, j_);
|
||||||
@@ -262,7 +289,7 @@ DirectStepI::revImp (
|
|||||||
maxSrcToDst, srcQOut, QUALITY_ONE, /*roundUp*/ true);
|
maxSrcToDst, srcQOut, QUALITY_ONE, /*roundUp*/ true);
|
||||||
IOUAmount const actualOut = mulRatio (
|
IOUAmount const actualOut = mulRatio (
|
||||||
maxSrcToDst, dstQIn, QUALITY_ONE, /*roundUp*/ false);
|
maxSrcToDst, dstQIn, QUALITY_ONE, /*roundUp*/ false);
|
||||||
cache_.emplace (in, maxSrcToDst, actualOut);
|
cache_.emplace (in, maxSrcToDst, actualOut, srcRedeems);
|
||||||
rippleCredit (sb,
|
rippleCredit (sb,
|
||||||
src_, dst_, toSTAmount (maxSrcToDst, srcToDstIss),
|
src_, dst_, toSTAmount (maxSrcToDst, srcToDstIss),
|
||||||
/*checkIssuer*/ true, j_);
|
/*checkIssuer*/ true, j_);
|
||||||
@@ -276,14 +303,15 @@ DirectStepI::revImp (
|
|||||||
}
|
}
|
||||||
|
|
||||||
// The forward pass should never have more liquidity than the reverse
|
// The forward pass should never have more liquidity than the reverse
|
||||||
// pass. But sometime rounding differences cause the forward pass to
|
// pass. But sometimes rounding differences cause the forward pass to
|
||||||
// deliver more liquidity. Use the cached values from the reverse pass
|
// deliver more liquidity. Use the cached values from the reverse pass
|
||||||
// to prevent this.
|
// to prevent this.
|
||||||
void
|
void
|
||||||
DirectStepI::setCacheLimiting (
|
DirectStepI::setCacheLimiting (
|
||||||
IOUAmount const& fwdIn,
|
IOUAmount const& fwdIn,
|
||||||
IOUAmount const& fwdSrcToDst,
|
IOUAmount const& fwdSrcToDst,
|
||||||
IOUAmount const& fwdOut)
|
IOUAmount const& fwdOut,
|
||||||
|
bool srcRedeems)
|
||||||
{
|
{
|
||||||
if (cache_->in < fwdIn)
|
if (cache_->in < fwdIn)
|
||||||
{
|
{
|
||||||
@@ -305,7 +333,7 @@ DirectStepI::setCacheLimiting (
|
|||||||
<< " cacheSrcToDst: " << to_string (cache_->srcToDst)
|
<< " cacheSrcToDst: " << to_string (cache_->srcToDst)
|
||||||
<< " fwdOut: " << to_string (fwdOut)
|
<< " fwdOut: " << to_string (fwdOut)
|
||||||
<< " cacheOut: " << to_string (cache_->out);
|
<< " cacheOut: " << to_string (cache_->out);
|
||||||
cache_.emplace (fwdIn, fwdSrcToDst, fwdOut);
|
cache_.emplace (fwdIn, fwdSrcToDst, fwdOut, srcRedeems);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -315,6 +343,7 @@ DirectStepI::setCacheLimiting (
|
|||||||
cache_->srcToDst = fwdSrcToDst;
|
cache_->srcToDst = fwdSrcToDst;
|
||||||
if (fwdOut < cache_->out)
|
if (fwdOut < cache_->out)
|
||||||
cache_->out = fwdOut;
|
cache_->out = fwdOut;
|
||||||
|
cache_->srcRedeems = srcRedeems;
|
||||||
};
|
};
|
||||||
|
|
||||||
std::pair<IOUAmount, IOUAmount>
|
std::pair<IOUAmount, IOUAmount>
|
||||||
@@ -332,7 +361,7 @@ DirectStepI::fwdImp (
|
|||||||
maxFlow (sb, src_, dst_, currency_);
|
maxFlow (sb, src_, dst_, currency_);
|
||||||
|
|
||||||
std::uint32_t srcQOut, dstQIn;
|
std::uint32_t srcQOut, dstQIn;
|
||||||
std::tie (srcQOut, dstQIn) = qualities (sb, srcRedeems);
|
std::tie (srcQOut, dstQIn) = qualities (sb, srcRedeems, true);
|
||||||
|
|
||||||
Issue const srcToDstIss (currency_, srcRedeems ? dst_ : src_);
|
Issue const srcToDstIss (currency_, srcRedeems ? dst_ : src_);
|
||||||
|
|
||||||
@@ -350,7 +379,8 @@ DirectStepI::fwdImp (
|
|||||||
cache_.emplace (
|
cache_.emplace (
|
||||||
IOUAmount (beast::zero),
|
IOUAmount (beast::zero),
|
||||||
IOUAmount (beast::zero),
|
IOUAmount (beast::zero),
|
||||||
IOUAmount (beast::zero));
|
IOUAmount (beast::zero),
|
||||||
|
srcRedeems);
|
||||||
return {beast::zero, beast::zero};
|
return {beast::zero, beast::zero};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -361,7 +391,7 @@ DirectStepI::fwdImp (
|
|||||||
{
|
{
|
||||||
IOUAmount const out = mulRatio (
|
IOUAmount const out = mulRatio (
|
||||||
srcToDst, dstQIn, QUALITY_ONE, /*roundUp*/ false);
|
srcToDst, dstQIn, QUALITY_ONE, /*roundUp*/ false);
|
||||||
setCacheLimiting (in, srcToDst, out);
|
setCacheLimiting (in, srcToDst, out, srcRedeems);
|
||||||
rippleCredit (sb,
|
rippleCredit (sb,
|
||||||
src_, dst_, toSTAmount (cache_->srcToDst, srcToDstIss),
|
src_, dst_, toSTAmount (cache_->srcToDst, srcToDstIss),
|
||||||
/*checkIssuer*/ true, j_);
|
/*checkIssuer*/ true, j_);
|
||||||
@@ -379,7 +409,7 @@ DirectStepI::fwdImp (
|
|||||||
maxSrcToDst, srcQOut, QUALITY_ONE, /*roundUp*/ true);
|
maxSrcToDst, srcQOut, QUALITY_ONE, /*roundUp*/ true);
|
||||||
IOUAmount const out = mulRatio (
|
IOUAmount const out = mulRatio (
|
||||||
maxSrcToDst, dstQIn, QUALITY_ONE, /*roundUp*/ false);
|
maxSrcToDst, dstQIn, QUALITY_ONE, /*roundUp*/ false);
|
||||||
setCacheLimiting (actualIn, maxSrcToDst, out);
|
setCacheLimiting (actualIn, maxSrcToDst, out, srcRedeems);
|
||||||
rippleCredit (sb,
|
rippleCredit (sb,
|
||||||
src_, dst_, toSTAmount (cache_->srcToDst, srcToDstIss),
|
src_, dst_, toSTAmount (cache_->srcToDst, srcToDstIss),
|
||||||
/*checkIssuer*/ true, j_);
|
/*checkIssuer*/ true, j_);
|
||||||
@@ -499,7 +529,8 @@ quality (
|
|||||||
std::pair<std::uint32_t, std::uint32_t>
|
std::pair<std::uint32_t, std::uint32_t>
|
||||||
DirectStepI::qualities (
|
DirectStepI::qualities (
|
||||||
PaymentSandbox& sb,
|
PaymentSandbox& sb,
|
||||||
bool srcRedeems) const
|
bool srcRedeems,
|
||||||
|
bool fwd) const
|
||||||
{
|
{
|
||||||
if (srcRedeems)
|
if (srcRedeems)
|
||||||
{
|
{
|
||||||
@@ -511,9 +542,10 @@ DirectStepI::qualities (
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Charge a transfer rate when issuing, unless this is the first step.
|
// Charge a transfer rate when issuing and previous step redeems
|
||||||
|
auto const prevStepRedeems = prevStep_ && prevStep_->redeems (sb, fwd);
|
||||||
std::uint32_t const srcQOut =
|
std::uint32_t const srcQOut =
|
||||||
noTransferFee_ ? QUALITY_ONE : rippleTransferRate (sb, src_);
|
prevStepRedeems ? rippleTransferRate (sb, src_) : QUALITY_ONE;
|
||||||
return std::make_pair(
|
return std::make_pair(
|
||||||
srcQOut,
|
srcQOut,
|
||||||
quality ( // dst quality in
|
quality ( // dst quality in
|
||||||
@@ -655,8 +687,9 @@ make_DirectStepI (
|
|||||||
AccountID const& dst,
|
AccountID const& dst,
|
||||||
Currency const& c)
|
Currency const& c)
|
||||||
{
|
{
|
||||||
|
// Only charge a transfer fee if the previous step redeems
|
||||||
auto r = std::make_unique<DirectStepI> (
|
auto r = std::make_unique<DirectStepI> (
|
||||||
src, dst, c, /* noTransferFee */ ctx.isFirst, ctx.j);
|
src, dst, c, ctx.prevStep, ctx.j);
|
||||||
auto ter = r->check (ctx);
|
auto ter = r->check (ctx);
|
||||||
if (ter != tesSUCCESS)
|
if (ter != tesSUCCESS)
|
||||||
return {ter, nullptr};
|
return {ter, nullptr};
|
||||||
|
|||||||
@@ -139,6 +139,7 @@ toStrand (
|
|||||||
Issue const& deliver,
|
Issue const& deliver,
|
||||||
boost::optional<Issue> const& sendMaxIssue,
|
boost::optional<Issue> const& sendMaxIssue,
|
||||||
STPath const& path,
|
STPath const& path,
|
||||||
|
bool ownerPaysTransferFee,
|
||||||
beast::Journal j)
|
beast::Journal j)
|
||||||
{
|
{
|
||||||
if (isXRP (src))
|
if (isXRP (src))
|
||||||
@@ -233,7 +234,7 @@ toStrand (
|
|||||||
auto ctx = [&](bool isLast = false)
|
auto ctx = [&](bool isLast = false)
|
||||||
{
|
{
|
||||||
return StrandContext{view, result, strandSrc, strandDst, isLast,
|
return StrandContext{view, result, strandSrc, strandDst, isLast,
|
||||||
seenDirectIssues, seenBookOuts, j};
|
ownerPaysTransferFee, seenDirectIssues, seenBookOuts, j};
|
||||||
};
|
};
|
||||||
|
|
||||||
for (int i = 0; i < pes.size () - 1; ++i)
|
for (int i = 0; i < pes.size () - 1; ++i)
|
||||||
@@ -359,7 +360,7 @@ toStrand (
|
|||||||
result.emplace_back (std::move (s.second));
|
result.emplace_back (std::move (s.second));
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
JLOG (j.warn()) << "toStep failed";
|
JLOG (j.debug()) << "toStep failed: " << s.first;
|
||||||
return {s.first, Strand{}};
|
return {s.first, Strand{}};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -376,6 +377,7 @@ toStrands (
|
|||||||
boost::optional<Issue> const& sendMax,
|
boost::optional<Issue> const& sendMax,
|
||||||
STPathSet const& paths,
|
STPathSet const& paths,
|
||||||
bool addDefaultPath,
|
bool addDefaultPath,
|
||||||
|
bool ownerPaysTransferFee,
|
||||||
beast::Journal j)
|
beast::Journal j)
|
||||||
{
|
{
|
||||||
std::vector<Strand> result;
|
std::vector<Strand> result;
|
||||||
@@ -391,7 +393,8 @@ toStrands (
|
|||||||
|
|
||||||
if (addDefaultPath)
|
if (addDefaultPath)
|
||||||
{
|
{
|
||||||
auto sp = toStrand (view, src, dst, deliver, sendMax, STPath(), j);
|
auto sp = toStrand (
|
||||||
|
view, src, dst, deliver, sendMax, STPath (), ownerPaysTransferFee, j);
|
||||||
auto const ter = sp.first;
|
auto const ter = sp.first;
|
||||||
auto& strand = sp.second;
|
auto& strand = sp.second;
|
||||||
|
|
||||||
@@ -407,7 +410,10 @@ toStrands (
|
|||||||
JLOG (j.trace()) << "toStrand failed";
|
JLOG (j.trace()) << "toStrand failed";
|
||||||
Throw<FlowException> (tefEXCEPTION, "toStrand returned tes & empty strand");
|
Throw<FlowException> (tefEXCEPTION, "toStrand returned tes & empty strand");
|
||||||
}
|
}
|
||||||
insert(std::move(strand));
|
else
|
||||||
|
{
|
||||||
|
insert(std::move(strand));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else if (paths.empty ())
|
else if (paths.empty ())
|
||||||
{
|
{
|
||||||
@@ -419,7 +425,8 @@ toStrands (
|
|||||||
TER lastFailTer = tesSUCCESS;
|
TER lastFailTer = tesSUCCESS;
|
||||||
for (auto const& p : paths)
|
for (auto const& p : paths)
|
||||||
{
|
{
|
||||||
auto sp = toStrand (view, src, dst, deliver, sendMax, p, j);
|
auto sp = toStrand (
|
||||||
|
view, src, dst, deliver, sendMax, p, ownerPaysTransferFee, j);
|
||||||
auto ter = sp.first;
|
auto ter = sp.first;
|
||||||
auto& strand = sp.second;
|
auto& strand = sp.second;
|
||||||
|
|
||||||
@@ -436,9 +443,10 @@ toStrands (
|
|||||||
JLOG (j.trace()) << "toStrand failed";
|
JLOG (j.trace()) << "toStrand failed";
|
||||||
Throw<FlowException> (tefEXCEPTION, "toStrand returned tes & empty strand");
|
Throw<FlowException> (tefEXCEPTION, "toStrand returned tes & empty strand");
|
||||||
}
|
}
|
||||||
|
else
|
||||||
if (ter == tesSUCCESS)
|
{
|
||||||
insert(std::move(strand));
|
insert(std::move(strand));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (result.empty ())
|
if (result.empty ())
|
||||||
@@ -455,6 +463,7 @@ StrandContext::StrandContext (
|
|||||||
AccountID strandSrc_,
|
AccountID strandSrc_,
|
||||||
AccountID strandDst_,
|
AccountID strandDst_,
|
||||||
bool isLast_,
|
bool isLast_,
|
||||||
|
bool ownerPaysTransferFee_,
|
||||||
std::array<boost::container::flat_set<Issue>, 2>& seenDirectIssues_,
|
std::array<boost::container::flat_set<Issue>, 2>& seenDirectIssues_,
|
||||||
boost::container::flat_set<Issue>& seenBookOuts_,
|
boost::container::flat_set<Issue>& seenBookOuts_,
|
||||||
beast::Journal j_)
|
beast::Journal j_)
|
||||||
@@ -463,6 +472,7 @@ StrandContext::StrandContext (
|
|||||||
, strandDst (strandDst_)
|
, strandDst (strandDst_)
|
||||||
, isFirst (strand_.empty ())
|
, isFirst (strand_.empty ())
|
||||||
, isLast (isLast_)
|
, isLast (isLast_)
|
||||||
|
, ownerPaysTransferFee (ownerPaysTransferFee_)
|
||||||
, strandSize (strand_.size ())
|
, strandSize (strand_.size ())
|
||||||
, prevStep (!strand_.empty () ? strand_.back ().get ()
|
, prevStep (!strand_.empty () ? strand_.back ().get ()
|
||||||
: nullptr)
|
: nullptr)
|
||||||
|
|||||||
@@ -120,6 +120,18 @@ public:
|
|||||||
return boost::none;
|
return boost::none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
If this step is a DirectStepI and the src redeems to the dst, return true,
|
||||||
|
otherwise return false.
|
||||||
|
If this step is a BookStep, return false if the owner pays the transfer fee,
|
||||||
|
otherwise return true.
|
||||||
|
*/
|
||||||
|
virtual bool
|
||||||
|
redeems (ReadView const& sb, bool fwd) const
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
If this step is a BookStep, return the book.
|
If this step is a BookStep, return the book.
|
||||||
*/
|
*/
|
||||||
@@ -226,6 +238,7 @@ toStrand (
|
|||||||
Issue const& deliver,
|
Issue const& deliver,
|
||||||
boost::optional<Issue> const& sendMaxIssue,
|
boost::optional<Issue> const& sendMaxIssue,
|
||||||
STPath const& path,
|
STPath const& path,
|
||||||
|
bool ownerPaysTransferFee,
|
||||||
beast::Journal j);
|
beast::Journal j);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -253,6 +266,7 @@ toStrands (ReadView const& sb,
|
|||||||
boost::optional<Issue> const& sendMax,
|
boost::optional<Issue> const& sendMax,
|
||||||
STPathSet const& paths,
|
STPathSet const& paths,
|
||||||
bool addDefaultPath,
|
bool addDefaultPath,
|
||||||
|
bool ownerPaysTransferFee,
|
||||||
beast::Journal j);
|
beast::Journal j);
|
||||||
|
|
||||||
template <class TIn, class TOut, class TDerived>
|
template <class TIn, class TOut, class TDerived>
|
||||||
@@ -338,6 +352,7 @@ struct StrandContext
|
|||||||
AccountID const strandDst;
|
AccountID const strandDst;
|
||||||
bool const isFirst;
|
bool const isFirst;
|
||||||
bool const isLast = false;
|
bool const isLast = false;
|
||||||
|
bool ownerPaysTransferFee;
|
||||||
size_t const strandSize;
|
size_t const strandSize;
|
||||||
// The previous step in the strand. Needed to check the no ripple constraint
|
// The previous step in the strand. Needed to check the no ripple constraint
|
||||||
Step const* const prevStep = nullptr;
|
Step const* const prevStep = nullptr;
|
||||||
@@ -357,6 +372,7 @@ struct StrandContext
|
|||||||
AccountID strandSrc_,
|
AccountID strandSrc_,
|
||||||
AccountID strandDst_,
|
AccountID strandDst_,
|
||||||
bool isLast_,
|
bool isLast_,
|
||||||
|
bool ownerPaysTransferFee_,
|
||||||
std::array<boost::container::flat_set<Issue>, 2>& seenDirectIssues_,
|
std::array<boost::container::flat_set<Issue>, 2>& seenDirectIssues_,
|
||||||
boost::container::flat_set<Issue>& seenBookOuts_,
|
boost::container::flat_set<Issue>& seenBookOuts_,
|
||||||
beast::Journal j);
|
beast::Journal j);
|
||||||
|
|||||||
@@ -138,7 +138,7 @@ flow (
|
|||||||
limitStepOut = r.second;
|
limitStepOut = r.second;
|
||||||
|
|
||||||
if (strand[i]->dry (r.second) ||
|
if (strand[i]->dry (r.second) ||
|
||||||
get<TInAmt> (r.first) != get<TInAmt> (*maxIn))
|
get<TInAmt> (r.first) != *maxIn)
|
||||||
{
|
{
|
||||||
// Something is very wrong
|
// Something is very wrong
|
||||||
// throwing out the sandbox can only increase liquidity
|
// throwing out the sandbox can only increase liquidity
|
||||||
|
|||||||
@@ -182,7 +182,7 @@ struct Flow_test : public beast::unit_test::suite
|
|||||||
TER expTer, auto&&... expSteps)
|
TER expTer, auto&&... expSteps)
|
||||||
{
|
{
|
||||||
auto r = toStrand (*env.current (), alice, bob,
|
auto r = toStrand (*env.current (), alice, bob,
|
||||||
deliver, sendMaxIssue, path, env.app ().logs ().journal ("Flow"));
|
deliver, sendMaxIssue, path, true, env.app ().logs ().journal ("Flow"));
|
||||||
expect (r.first == expTer);
|
expect (r.first == expTer);
|
||||||
if (sizeof...(expSteps))
|
if (sizeof...(expSteps))
|
||||||
expect (equal (
|
expect (equal (
|
||||||
@@ -190,7 +190,7 @@ struct Flow_test : public beast::unit_test::suite
|
|||||||
};
|
};
|
||||||
|
|
||||||
{
|
{
|
||||||
Env env (*this, features(featureFlowV2));
|
Env env (*this, features(featureFlowV2), features(featureOwnerPaysFee));
|
||||||
env.fund (XRP (10000), alice, bob, carol, gw);
|
env.fund (XRP (10000), alice, bob, carol, gw);
|
||||||
|
|
||||||
test (env, USD, boost::none, STPath(), terNO_LINE);
|
test (env, USD, boost::none, STPath(), terNO_LINE);
|
||||||
@@ -236,20 +236,20 @@ struct Flow_test : public beast::unit_test::suite
|
|||||||
{
|
{
|
||||||
// The root account can't be the dst
|
// The root account can't be the dst
|
||||||
auto r = toStrand (*env.current (), alice,
|
auto r = toStrand (*env.current (), alice,
|
||||||
xrpAccount (), XRP, USD.issue (), STPath (), flowJournal);
|
xrpAccount (), XRP, USD.issue (), STPath (), true, flowJournal);
|
||||||
expect (r.first == temBAD_PATH);
|
expect (r.first == temBAD_PATH);
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
// The root account can't be the src
|
// The root account can't be the src
|
||||||
auto r =
|
auto r =
|
||||||
toStrand (*env.current (), xrpAccount (),
|
toStrand (*env.current (), xrpAccount (),
|
||||||
alice, XRP, boost::none, STPath (), flowJournal);
|
alice, XRP, boost::none, STPath (), true, flowJournal);
|
||||||
expect (r.first == temBAD_PATH);
|
expect (r.first == temBAD_PATH);
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
// The root account can't be the src
|
// The root account can't be the src
|
||||||
auto r = toStrand (*env.current (),
|
auto r = toStrand (*env.current (),
|
||||||
noAccount (), bob, USD, boost::none, STPath (), flowJournal);
|
noAccount (), bob, USD, boost::none, STPath (), true, flowJournal);
|
||||||
expect (r.first == terNO_ACCOUNT);
|
expect (r.first == terNO_ACCOUNT);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -279,7 +279,7 @@ struct Flow_test : public beast::unit_test::suite
|
|||||||
// cannot have more than one offer with the same output issue
|
// cannot have more than one offer with the same output issue
|
||||||
|
|
||||||
using namespace jtx;
|
using namespace jtx;
|
||||||
Env env (*this, features (featureFlowV2));
|
Env env (*this, features (featureFlowV2), features(featureOwnerPaysFee));
|
||||||
|
|
||||||
env.fund (XRP (10000), alice, bob, carol, gw);
|
env.fund (XRP (10000), alice, bob, carol, gw);
|
||||||
env.trust (USD (10000), alice, bob, carol);
|
env.trust (USD (10000), alice, bob, carol);
|
||||||
@@ -299,7 +299,7 @@ struct Flow_test : public beast::unit_test::suite
|
|||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
Env env (*this, features(featureFlowV2));
|
Env env (*this, features(featureFlowV2), features(featureOwnerPaysFee));
|
||||||
env.fund (XRP (10000), alice, bob, noripple (gw));
|
env.fund (XRP (10000), alice, bob, noripple (gw));
|
||||||
env.trust (USD (1000), alice, bob);
|
env.trust (USD (1000), alice, bob);
|
||||||
env (pay (gw, alice, USD (100)));
|
env (pay (gw, alice, USD (100)));
|
||||||
@@ -308,7 +308,7 @@ struct Flow_test : public beast::unit_test::suite
|
|||||||
|
|
||||||
{
|
{
|
||||||
// check global freeze
|
// check global freeze
|
||||||
Env env (*this, features(featureFlowV2));
|
Env env (*this, features(featureFlowV2), features(featureOwnerPaysFee));
|
||||||
env.fund (XRP (10000), alice, bob, gw);
|
env.fund (XRP (10000), alice, bob, gw);
|
||||||
env.trust (USD (1000), alice, bob);
|
env.trust (USD (1000), alice, bob);
|
||||||
env (pay (gw, alice, USD (100)));
|
env (pay (gw, alice, USD (100)));
|
||||||
@@ -333,7 +333,7 @@ struct Flow_test : public beast::unit_test::suite
|
|||||||
}
|
}
|
||||||
{
|
{
|
||||||
// Freeze between gw and alice
|
// Freeze between gw and alice
|
||||||
Env env (*this, features(featureFlowV2));
|
Env env (*this, features(featureFlowV2), features(featureOwnerPaysFee));
|
||||||
env.fund (XRP (10000), alice, bob, gw);
|
env.fund (XRP (10000), alice, bob, gw);
|
||||||
env.trust (USD (1000), alice, bob);
|
env.trust (USD (1000), alice, bob);
|
||||||
env (pay (gw, alice, USD (100)));
|
env (pay (gw, alice, USD (100)));
|
||||||
@@ -346,7 +346,7 @@ struct Flow_test : public beast::unit_test::suite
|
|||||||
// check no auth
|
// check no auth
|
||||||
// An account may require authorization to receive IOUs from an
|
// An account may require authorization to receive IOUs from an
|
||||||
// issuer
|
// issuer
|
||||||
Env env (*this, features(featureFlowV2));
|
Env env (*this, features(featureFlowV2), features(featureOwnerPaysFee));
|
||||||
env.fund (XRP (10000), alice, bob, gw);
|
env.fund (XRP (10000), alice, bob, gw);
|
||||||
env (fset (gw, asfRequireAuth));
|
env (fset (gw, asfRequireAuth));
|
||||||
env.trust (USD (1000), alice, bob);
|
env.trust (USD (1000), alice, bob);
|
||||||
@@ -359,7 +359,7 @@ struct Flow_test : public beast::unit_test::suite
|
|||||||
|
|
||||||
// Check pure issue redeem still works
|
// Check pure issue redeem still works
|
||||||
auto r = toStrand (*env.current (), alice, gw, USD,
|
auto r = toStrand (*env.current (), alice, gw, USD,
|
||||||
boost::none, STPath (), env.app ().logs ().journal ("Flow"));
|
boost::none, STPath (), true, env.app ().logs ().journal ("Flow"));
|
||||||
expect (r.first == tesSUCCESS);
|
expect (r.first == tesSUCCESS);
|
||||||
expect (equal (r.second, D{alice, gw, usdC}));
|
expect (equal (r.second, D{alice, gw, usdC}));
|
||||||
}
|
}
|
||||||
@@ -382,7 +382,7 @@ struct Flow_test : public beast::unit_test::suite
|
|||||||
auto const USD = gw["USD"];
|
auto const USD = gw["USD"];
|
||||||
{
|
{
|
||||||
// Pay USD, trivial path
|
// Pay USD, trivial path
|
||||||
Env env (*this, features(featureFlowV2));
|
Env env (*this, features(featureFlowV2), features(featureOwnerPaysFee));
|
||||||
|
|
||||||
env.fund (XRP (10000), alice, bob, gw);
|
env.fund (XRP (10000), alice, bob, gw);
|
||||||
env.trust (USD (1000), alice, bob);
|
env.trust (USD (1000), alice, bob);
|
||||||
@@ -392,7 +392,7 @@ struct Flow_test : public beast::unit_test::suite
|
|||||||
}
|
}
|
||||||
{
|
{
|
||||||
// XRP transfer
|
// XRP transfer
|
||||||
Env env (*this, features(featureFlowV2));
|
Env env (*this, features(featureFlowV2), features(featureOwnerPaysFee));
|
||||||
|
|
||||||
env.fund (XRP (10000), alice, bob);
|
env.fund (XRP (10000), alice, bob);
|
||||||
env (pay (alice, bob, XRP (100)));
|
env (pay (alice, bob, XRP (100)));
|
||||||
@@ -401,7 +401,7 @@ struct Flow_test : public beast::unit_test::suite
|
|||||||
}
|
}
|
||||||
{
|
{
|
||||||
// Partial payments
|
// Partial payments
|
||||||
Env env (*this, features(featureFlowV2));
|
Env env (*this, features(featureFlowV2), features(featureOwnerPaysFee));
|
||||||
|
|
||||||
env.fund (XRP (10000), alice, bob, gw);
|
env.fund (XRP (10000), alice, bob, gw);
|
||||||
env.trust (USD (1000), alice, bob);
|
env.trust (USD (1000), alice, bob);
|
||||||
@@ -415,7 +415,7 @@ struct Flow_test : public beast::unit_test::suite
|
|||||||
}
|
}
|
||||||
{
|
{
|
||||||
// Pay by rippling through accounts, use path finder
|
// Pay by rippling through accounts, use path finder
|
||||||
Env env (*this, features(featureFlowV2));
|
Env env (*this, features(featureFlowV2), features(featureOwnerPaysFee));
|
||||||
|
|
||||||
env.fund (XRP (10000), alice, bob, carol, dan);
|
env.fund (XRP (10000), alice, bob, carol, dan);
|
||||||
env.trust (USDA (10), bob);
|
env.trust (USDA (10), bob);
|
||||||
@@ -430,32 +430,52 @@ struct Flow_test : public beast::unit_test::suite
|
|||||||
{
|
{
|
||||||
// Pay by rippling through accounts, specify path
|
// Pay by rippling through accounts, specify path
|
||||||
// and charge a transfer fee
|
// and charge a transfer fee
|
||||||
Env env (*this, features(featureFlowV2));
|
Env env (*this, features(featureFlowV2), features(featureOwnerPaysFee));
|
||||||
|
|
||||||
env.fund (XRP (10000), alice, bob, carol, dan);
|
env.fund (XRP (10000), alice, bob, carol, dan);
|
||||||
env.trust (USDA (10), bob);
|
env.trust (USDA (10), bob);
|
||||||
env.trust (USDB (10), carol);
|
env.trust (USDB (10), alice, carol);
|
||||||
|
env.trust (USDC (10), dan);
|
||||||
|
env (rate (bob, 1.1));
|
||||||
|
|
||||||
|
// alice will redeem to bob; a transfer fee will be charged
|
||||||
|
env (pay (bob, alice, USDB(6)));
|
||||||
|
env (pay (alice, dan, USDC (5)), path (bob, carol),
|
||||||
|
sendmax (USDA (6)), txflags (tfNoRippleDirect));
|
||||||
|
env.require (balance (dan, USDC (5)));
|
||||||
|
env.require (balance (alice, USDB (0.5)));
|
||||||
|
}
|
||||||
|
{
|
||||||
|
// Pay by rippling through accounts, specify path and transfer fee
|
||||||
|
// Test that the transfer fee is not charged when alice issues
|
||||||
|
Env env (*this, features(featureFlowV2), features(featureOwnerPaysFee));
|
||||||
|
|
||||||
|
env.fund (XRP (10000), alice, bob, carol, dan);
|
||||||
|
env.trust (USDA (10), bob);
|
||||||
|
env.trust (USDB (10), alice, carol);
|
||||||
env.trust (USDC (10), dan);
|
env.trust (USDC (10), dan);
|
||||||
env (rate (bob, 1.1));
|
env (rate (bob, 1.1));
|
||||||
|
|
||||||
env (pay (alice, dan, USDC (5)), path (bob, carol),
|
env (pay (alice, dan, USDC (5)), path (bob, carol),
|
||||||
sendmax (USDA (6)), txflags (tfNoRippleDirect));
|
sendmax (USDA (6)), txflags (tfNoRippleDirect));
|
||||||
env.require (balance (dan, USDC (5)));
|
env.require (balance (dan, USDC (5)));
|
||||||
env.require (balance (bob, USDA (5.5)));
|
env.require (balance (bob, USDA (5)));
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
// test best quality path is taken
|
// test best quality path is taken
|
||||||
// Paths: A->B->D->E ; A->C->D->E
|
// Paths: A->B->D->E ; A->C->D->E
|
||||||
Env env (*this, features(featureFlowV2));
|
Env env (*this, features(featureFlowV2), features(featureOwnerPaysFee));
|
||||||
|
|
||||||
env.fund (XRP (10000), alice, bob, carol, dan, erin);
|
env.fund (XRP (10000), alice, bob, carol, dan, erin);
|
||||||
env.trust (USDA (10), bob, carol);
|
env.trust (USDA (10), bob, carol);
|
||||||
env.trust (USDB (10), dan);
|
env.trust (USDB (10), dan);
|
||||||
env.trust (USDC (10), dan);
|
env.trust (USDC (10), alice, dan);
|
||||||
env.trust (USDD (20), erin);
|
env.trust (USDD (20), erin);
|
||||||
env (rate (bob, 1));
|
env (rate (bob, 1));
|
||||||
env (rate (carol, 1.1));
|
env (rate (carol, 1.1));
|
||||||
|
|
||||||
|
// Pay alice so she redeems to carol and a transfer fee is charged
|
||||||
|
env (pay (carol, alice, USDC(10)));
|
||||||
env (pay (alice, erin, USDD (5)), path (carol, dan),
|
env (pay (alice, erin, USDD (5)), path (carol, dan),
|
||||||
path (bob, dan), txflags (tfNoRippleDirect));
|
path (bob, dan), txflags (tfNoRippleDirect));
|
||||||
|
|
||||||
@@ -465,7 +485,7 @@ struct Flow_test : public beast::unit_test::suite
|
|||||||
}
|
}
|
||||||
{
|
{
|
||||||
// Limit quality
|
// Limit quality
|
||||||
Env env (*this, features(featureFlowV2));
|
Env env (*this, features(featureFlowV2), features(featureOwnerPaysFee));
|
||||||
|
|
||||||
env.fund (XRP (10000), alice, bob, carol);
|
env.fund (XRP (10000), alice, bob, carol);
|
||||||
env.trust (USDA (10), bob);
|
env.trust (USDA (10), bob);
|
||||||
@@ -497,7 +517,7 @@ struct Flow_test : public beast::unit_test::suite
|
|||||||
|
|
||||||
{
|
{
|
||||||
// simple IOU/IOU offer
|
// simple IOU/IOU offer
|
||||||
Env env (*this, features(featureFlowV2));
|
Env env (*this, features(featureFlowV2), features(featureOwnerPaysFee));
|
||||||
|
|
||||||
env.fund (XRP (10000), alice, bob, carol, gw);
|
env.fund (XRP (10000), alice, bob, carol, gw);
|
||||||
env.trust (USD (1000), alice, bob, carol);
|
env.trust (USD (1000), alice, bob, carol);
|
||||||
@@ -518,7 +538,7 @@ struct Flow_test : public beast::unit_test::suite
|
|||||||
}
|
}
|
||||||
{
|
{
|
||||||
// simple IOU/XRP XRP/IOU offer
|
// simple IOU/XRP XRP/IOU offer
|
||||||
Env env (*this, features(featureFlowV2));
|
Env env (*this, features(featureFlowV2), features(featureOwnerPaysFee));
|
||||||
|
|
||||||
env.fund (XRP (10000), alice, bob, carol, gw);
|
env.fund (XRP (10000), alice, bob, carol, gw);
|
||||||
env.trust (USD (1000), alice, bob, carol);
|
env.trust (USD (1000), alice, bob, carol);
|
||||||
@@ -542,7 +562,7 @@ struct Flow_test : public beast::unit_test::suite
|
|||||||
}
|
}
|
||||||
{
|
{
|
||||||
// simple XRP -> USD through offer and sendmax
|
// simple XRP -> USD through offer and sendmax
|
||||||
Env env (*this, features(featureFlowV2));
|
Env env (*this, features(featureFlowV2), features(featureOwnerPaysFee));
|
||||||
|
|
||||||
env.fund (XRP (10000), alice, bob, carol, gw);
|
env.fund (XRP (10000), alice, bob, carol, gw);
|
||||||
env.trust (USD (1000), alice, bob, carol);
|
env.trust (USD (1000), alice, bob, carol);
|
||||||
@@ -563,7 +583,7 @@ struct Flow_test : public beast::unit_test::suite
|
|||||||
}
|
}
|
||||||
{
|
{
|
||||||
// simple USD -> XRP through offer and sendmax
|
// simple USD -> XRP through offer and sendmax
|
||||||
Env env (*this, features(featureFlowV2));
|
Env env (*this, features(featureFlowV2), features(featureOwnerPaysFee));
|
||||||
|
|
||||||
env.fund (XRP (10000), alice, bob, carol, gw);
|
env.fund (XRP (10000), alice, bob, carol, gw);
|
||||||
env.trust (USD (1000), alice, bob, carol);
|
env.trust (USD (1000), alice, bob, carol);
|
||||||
@@ -584,7 +604,7 @@ struct Flow_test : public beast::unit_test::suite
|
|||||||
}
|
}
|
||||||
{
|
{
|
||||||
// test unfunded offers are removed when payment succeeds
|
// test unfunded offers are removed when payment succeeds
|
||||||
Env env (*this, features(featureFlowV2));
|
Env env (*this, features(featureFlowV2), features(featureOwnerPaysFee));
|
||||||
|
|
||||||
env.fund (XRP (10000), alice, bob, carol, gw);
|
env.fund (XRP (10000), alice, bob, carol, gw);
|
||||||
env.trust (USD (1000), alice, bob, carol);
|
env.trust (USD (1000), alice, bob, carol);
|
||||||
@@ -630,7 +650,7 @@ struct Flow_test : public beast::unit_test::suite
|
|||||||
// offer. When the payment fails `flow` should return the unfunded
|
// offer. When the payment fails `flow` should return the unfunded
|
||||||
// offer. This test is intentionally similar to the one that removes
|
// offer. This test is intentionally similar to the one that removes
|
||||||
// unfunded offers when the payment succeeds.
|
// unfunded offers when the payment succeeds.
|
||||||
Env env (*this, features(featureFlowV2));
|
Env env (*this, features(featureFlowV2), features(featureOwnerPaysFee));
|
||||||
|
|
||||||
env.fund (XRP (10000), alice, bob, carol, gw);
|
env.fund (XRP (10000), alice, bob, carol, gw);
|
||||||
env.trust (USD (1000), alice, bob, carol);
|
env.trust (USD (1000), alice, bob, carol);
|
||||||
@@ -666,7 +686,7 @@ struct Flow_test : public beast::unit_test::suite
|
|||||||
paths.push_back (p2);
|
paths.push_back (p2);
|
||||||
}
|
}
|
||||||
|
|
||||||
return flow (sb, deliver, alice, carol, paths, false, false,
|
return flow (sb, deliver, alice, carol, paths, false, false, true,
|
||||||
boost::none, smax, flowJournal);
|
boost::none, smax, flowJournal);
|
||||||
}();
|
}();
|
||||||
|
|
||||||
@@ -689,6 +709,38 @@ struct Flow_test : public beast::unit_test::suite
|
|||||||
// found unfunded
|
// found unfunded
|
||||||
expect (!isOffer (env, bob, BTC (60), EUR (50)));
|
expect (!isOffer (env, bob, BTC (60), EUR (50)));
|
||||||
}
|
}
|
||||||
|
{
|
||||||
|
// Do not produce more in the forward pass than the reverse pass
|
||||||
|
// This test uses a path that whose reverse pass will compute a
|
||||||
|
// 0.5 USD input required for a 1 EUR output. It sets a sendmax of
|
||||||
|
// 0.4 USD, so the payment engine will need to do a forward pass.
|
||||||
|
// Without limits, the 0.4 USD would produce 1000 EUR in the forward
|
||||||
|
// pass. This test checks that the payment produces 1 EUR, as expected.
|
||||||
|
|
||||||
|
Env env (*this, features (featureFlowV2),
|
||||||
|
features (featureOwnerPaysFee));
|
||||||
|
|
||||||
|
auto const closeTime = STAmountSO::soTime2 +
|
||||||
|
100 * env.closed ()->info ().closeTimeResolution;
|
||||||
|
env.close (closeTime);
|
||||||
|
|
||||||
|
env.fund (XRP (10000), alice, bob, carol, gw);
|
||||||
|
env.trust (USD (1000), alice, bob, carol);
|
||||||
|
env.trust (EUR (1000), alice, bob, carol);
|
||||||
|
|
||||||
|
env (pay (gw, alice, USD (1000)));
|
||||||
|
env (pay (gw, bob, EUR (1000)));
|
||||||
|
|
||||||
|
env (offer (bob, USD (1), drops (2)), txflags (tfPassive));
|
||||||
|
env (offer (bob, drops (1), EUR (1000)), txflags (tfPassive));
|
||||||
|
|
||||||
|
env (pay (alice, carol, EUR (1)), path (~XRP, ~EUR),
|
||||||
|
sendmax (USD (0.4)), txflags (tfNoRippleDirect|tfPartialPayment));
|
||||||
|
|
||||||
|
env.require (balance (carol, EUR (1)));
|
||||||
|
env.require (balance (bob, USD (0.4)));
|
||||||
|
env.require (balance (bob, EUR (999)));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void testTransferRate ()
|
void testTransferRate ()
|
||||||
@@ -709,7 +761,7 @@ struct Flow_test : public beast::unit_test::suite
|
|||||||
{
|
{
|
||||||
// Simple payment through a gateway with a
|
// Simple payment through a gateway with a
|
||||||
// transfer rate
|
// transfer rate
|
||||||
Env env (*this, features(featureFlowV2));
|
Env env (*this, features(featureFlowV2), features(featureOwnerPaysFee));
|
||||||
|
|
||||||
env.fund (XRP (10000), alice, bob, carol, gw);
|
env.fund (XRP (10000), alice, bob, carol, gw);
|
||||||
env(rate(gw, 1.25));
|
env(rate(gw, 1.25));
|
||||||
@@ -721,7 +773,7 @@ struct Flow_test : public beast::unit_test::suite
|
|||||||
}
|
}
|
||||||
{
|
{
|
||||||
// transfer rate is not charged when issuer is src or dst
|
// transfer rate is not charged when issuer is src or dst
|
||||||
Env env (*this, features(featureFlowV2));
|
Env env (*this, features(featureFlowV2), features(featureOwnerPaysFee));
|
||||||
|
|
||||||
env.fund (XRP (10000), alice, bob, carol, gw);
|
env.fund (XRP (10000), alice, bob, carol, gw);
|
||||||
env(rate(gw, 1.25));
|
env(rate(gw, 1.25));
|
||||||
@@ -733,25 +785,25 @@ struct Flow_test : public beast::unit_test::suite
|
|||||||
}
|
}
|
||||||
{
|
{
|
||||||
// transfer fee on an offer
|
// transfer fee on an offer
|
||||||
Env env (*this, features(featureFlowV2));
|
Env env (*this, features(featureFlowV2), features(featureOwnerPaysFee));
|
||||||
|
|
||||||
env.fund (XRP (10000), alice, bob, carol, gw);
|
env.fund (XRP (10000), alice, bob, carol, gw);
|
||||||
env(rate(gw, 1.25));
|
env(rate(gw, 1.25));
|
||||||
env.trust (USD (1000), alice, bob, carol);
|
env.trust (USD (1000), alice, bob, carol);
|
||||||
env (pay (gw, bob, USD (50)));
|
env (pay (gw, bob, USD (65)));
|
||||||
|
|
||||||
env (offer (bob, XRP (50), USD (50)));
|
env (offer (bob, XRP (50), USD (50)));
|
||||||
|
|
||||||
env (pay (alice, carol, USD (40)), path (~USD), sendmax (XRP (50)));
|
env (pay (alice, carol, USD (50)), path (~USD), sendmax (XRP (50)));
|
||||||
env.require (
|
env.require (
|
||||||
balance (alice, xrpMinusFee (env, 10000 - 50)),
|
balance (alice, xrpMinusFee (env, 10000 - 50)),
|
||||||
balance (bob, USD (0)),
|
balance (bob, USD (2.5)), // owner pays transfer fee
|
||||||
balance (carol, USD (40)));
|
balance (carol, USD (50)));
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
// Transfer fee two consecutive offers
|
// Transfer fee two consecutive offers
|
||||||
Env env (*this, features(featureFlowV2));
|
Env env (*this, features(featureFlowV2), features(featureOwnerPaysFee));
|
||||||
|
|
||||||
env.fund (XRP (10000), alice, bob, carol, gw);
|
env.fund (XRP (10000), alice, bob, carol, gw);
|
||||||
env(rate(gw, 1.25));
|
env(rate(gw, 1.25));
|
||||||
@@ -763,12 +815,89 @@ struct Flow_test : public beast::unit_test::suite
|
|||||||
env (offer (bob, XRP (50), USD (50)));
|
env (offer (bob, XRP (50), USD (50)));
|
||||||
env (offer (bob, USD (50), EUR (50)));
|
env (offer (bob, USD (50), EUR (50)));
|
||||||
|
|
||||||
env (pay (alice, carol, EUR (32)), path (~USD, ~EUR), sendmax (XRP (50)));
|
env (pay (alice, carol, EUR (40)), path (~USD, ~EUR), sendmax (XRP (40)));
|
||||||
env.require (
|
env.require (
|
||||||
balance (alice, xrpMinusFee (env, 10000 - 50)),
|
balance (alice, xrpMinusFee (env, 10000 - 40)),
|
||||||
balance (bob, USD (40)),
|
balance (bob, USD (40)),
|
||||||
balance (bob, EUR (50 - 40)),
|
balance (bob, EUR (0)),
|
||||||
balance (carol, EUR (32)));
|
balance (carol, EUR (40)));
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
// First pass through a strand redeems, second pass issues, no offers
|
||||||
|
// limiting step is not an endpoint
|
||||||
|
Env env (*this, features(featureFlowV2), features(featureOwnerPaysFee));
|
||||||
|
auto const USDA = alice["USD"];
|
||||||
|
auto const USDB = bob["USD"];
|
||||||
|
|
||||||
|
env.fund (XRP (10000), alice, bob, carol, gw);
|
||||||
|
env(rate(gw, 1.25));
|
||||||
|
env.trust (USD (1000), alice, bob, carol);
|
||||||
|
env.trust (USDA (1000), bob);
|
||||||
|
env.trust (USDB (1000), gw);
|
||||||
|
env (pay (gw, bob, USD (50)));
|
||||||
|
// alice -> bob -> gw -> carol. $50 should have transfer fee; $10, no fee
|
||||||
|
env (pay (alice, carol, USD(50)), path (bob), sendmax (USDA(60)));
|
||||||
|
env.require (
|
||||||
|
balance (bob, USD (-10)),
|
||||||
|
balance (bob, USDA (60)),
|
||||||
|
balance (carol, USD (50)));
|
||||||
|
}
|
||||||
|
{
|
||||||
|
// First pass through a strand redeems, second pass issues, through an offer
|
||||||
|
// limiting step is not an endpoint
|
||||||
|
Env env (*this, features(featureFlowV2), features(featureOwnerPaysFee));
|
||||||
|
auto const USDA = alice["USD"];
|
||||||
|
auto const USDB = bob["USD"];
|
||||||
|
Account const dan ("dan");
|
||||||
|
|
||||||
|
env.fund (XRP (10000), alice, bob, carol, dan, gw);
|
||||||
|
env(rate(gw, 1.25));
|
||||||
|
env.trust (USD (1000), alice, bob, carol, dan);
|
||||||
|
env.trust (EUR (1000), carol, dan);
|
||||||
|
env.trust (USDA (1000), bob);
|
||||||
|
env.trust (USDB (1000), gw);
|
||||||
|
env (pay (gw, bob, USD (50)));
|
||||||
|
env (pay (gw, dan, EUR (100)));
|
||||||
|
env (offer (dan, USD (100), EUR (100)));
|
||||||
|
// alice -> bob -> gw -> carol. $50 should have transfer fee; $10, no fee
|
||||||
|
env (pay (alice, carol, EUR (50)), path (bob, gw, ~EUR),
|
||||||
|
sendmax (USDA (60)), txflags (tfNoRippleDirect));
|
||||||
|
env.require (
|
||||||
|
balance (bob, USD (-10)),
|
||||||
|
balance (bob, USDA (60)),
|
||||||
|
balance (dan, USD (50)),
|
||||||
|
balance (dan, EUR (37.5)),
|
||||||
|
balance (carol, EUR (50)));
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
// Offer where the owner is also the issuer, owner pays fee
|
||||||
|
Env env (*this, features(featureFlowV2), features(featureOwnerPaysFee));
|
||||||
|
|
||||||
|
env.fund (XRP (10000), alice, bob, gw);
|
||||||
|
env(rate(gw, 1.25));
|
||||||
|
env.trust (USD (1000), alice, bob);
|
||||||
|
env (offer (gw, XRP (100), USD (100)));
|
||||||
|
env (pay (alice, bob, USD (100)),
|
||||||
|
sendmax (XRP (100)));
|
||||||
|
env.require (
|
||||||
|
balance (alice, xrpMinusFee(env, 10000-100)),
|
||||||
|
balance (bob, USD (100)));
|
||||||
|
}
|
||||||
|
{
|
||||||
|
// Offer where the owner is also the issuer, sender pays fee
|
||||||
|
Env env (*this, features(featureFlowV2));
|
||||||
|
|
||||||
|
env.fund (XRP (10000), alice, bob, gw);
|
||||||
|
env(rate(gw, 1.25));
|
||||||
|
env.trust (USD (1000), alice, bob);
|
||||||
|
env (offer (gw, XRP (125), USD (125)));
|
||||||
|
env (pay (alice, bob, USD (100)),
|
||||||
|
sendmax (XRP (200)));
|
||||||
|
env.require (
|
||||||
|
balance (alice, xrpMinusFee(env, 10000-125)),
|
||||||
|
balance (bob, USD (100)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -34,8 +34,8 @@ namespace ripple {
|
|||||||
NetClock::time_point const& flowV2SoTime ()
|
NetClock::time_point const& flowV2SoTime ()
|
||||||
{
|
{
|
||||||
using namespace std::chrono_literals;
|
using namespace std::chrono_literals;
|
||||||
// Mon March 28, 2016 10:00:00am PST
|
// Wed May 25, 2016 10:00:00am PDT
|
||||||
static NetClock::time_point const soTime{512503200s};
|
static NetClock::time_point const soTime{517510800s};
|
||||||
return soTime;
|
return soTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ extern uint256 const featureSusPay;
|
|||||||
extern uint256 const featureTrustSetAuth;
|
extern uint256 const featureTrustSetAuth;
|
||||||
extern uint256 const featureFeeEscalation;
|
extern uint256 const featureFeeEscalation;
|
||||||
extern uint256 const featureFlowV2;
|
extern uint256 const featureFlowV2;
|
||||||
|
extern uint256 const featureOwnerPaysFee;
|
||||||
|
|
||||||
} // ripple
|
} // ripple
|
||||||
|
|
||||||
|
|||||||
@@ -51,5 +51,6 @@ uint256 const featureSusPay = feature("SusPay");
|
|||||||
uint256 const featureTrustSetAuth = feature("TrustSetAuth");
|
uint256 const featureTrustSetAuth = feature("TrustSetAuth");
|
||||||
uint256 const featureFeeEscalation = feature("FeeEscalation");
|
uint256 const featureFeeEscalation = feature("FeeEscalation");
|
||||||
uint256 const featureFlowV2 = feature("FlowV2");
|
uint256 const featureFlowV2 = feature("FlowV2");
|
||||||
|
uint256 const featureOwnerPaysFee = feature("OwnerPaysFee");
|
||||||
|
|
||||||
} // ripple
|
} // ripple
|
||||||
|
|||||||
Reference in New Issue
Block a user