mirror of
https://github.com/Xahau/xahaud.git
synced 2025-11-20 10:35:50 +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,
|
||||
bool defaultPaths,
|
||||
bool partialPayment,
|
||||
bool ownerPaysTransferFee,
|
||||
boost::optional<Quality> const& limitQuality,
|
||||
boost::optional<STAmount> const& sendMax,
|
||||
beast::Journal j)
|
||||
@@ -83,7 +84,7 @@ flow (
|
||||
// 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.
|
||||
auto sr = toStrands (sb, src, dst, dstIssue, sendMaxIssue, paths,
|
||||
defaultPaths, j);
|
||||
defaultPaths, ownerPaysTransferFee, j);
|
||||
|
||||
if (sr.first != tesSUCCESS)
|
||||
{
|
||||
|
||||
@@ -51,6 +51,7 @@ flow (PaymentSandbox& view,
|
||||
STPathSet const& paths,
|
||||
bool defaultPaths,
|
||||
bool partialPayment,
|
||||
bool ownerPaysTransferFee,
|
||||
boost::optional<Quality> const& limitQuality,
|
||||
boost::optional<STAmount> const& sendMax,
|
||||
beast::Journal j);
|
||||
|
||||
@@ -133,9 +133,11 @@ RippleCalc::Output RippleCalc::rippleCalculate (
|
||||
|
||||
try
|
||||
{
|
||||
bool const ownerPaysTransferFee =
|
||||
view.rules ().enabled (featureOwnerPaysFee, config.features);
|
||||
flowV2Out = flow (flowV2SB, saDstAmountReq, uSrcAccountID,
|
||||
uDstAccountID, spsPaths, defaultPaths, partialPayment,
|
||||
limitQuality, sendMax, j);
|
||||
ownerPaysTransferFee, limitQuality, sendMax, j);
|
||||
}
|
||||
catch (std::exception& e)
|
||||
{
|
||||
@@ -144,25 +146,28 @@ RippleCalc::Output RippleCalc::rippleCalculate (
|
||||
Throw();
|
||||
}
|
||||
|
||||
if (callFlowV2 && callFlowV1 &&
|
||||
(flowV2Out.result () != flowV1Out.result () ||
|
||||
(flowV2Out.result () == tesSUCCESS &&
|
||||
(flowV2Out.actualAmountIn != flowV1Out.actualAmountIn ||
|
||||
flowV2Out.actualAmountOut != flowV1Out.actualAmountOut))))
|
||||
if (j.debug())
|
||||
{
|
||||
JLOG (j.trace()) <<
|
||||
"Mismatch: New Flow and RippleCalc" <<
|
||||
" Old actualIn: " << flowV1Out.actualAmountIn <<
|
||||
" New actualIn: " << flowV2Out.actualAmountIn <<
|
||||
" Old actualOut: " << flowV1Out.actualAmountOut <<
|
||||
" New actualOut: " << flowV2Out.actualAmountOut <<
|
||||
" Old result: " << flowV1Out.result () <<
|
||||
" New result: " << flowV2Out.result();
|
||||
}
|
||||
else
|
||||
{
|
||||
JLOG (j.trace()) << "Match: New Flow and RippleCalc";
|
||||
auto logResult = [&] (std::string const& algoName, Output const& result)
|
||||
{
|
||||
j.debug() << "RippleCalc Result> " <<
|
||||
" actualIn: " << result.actualAmountIn <<
|
||||
", actualOut: " << result.actualAmountOut <<
|
||||
", result: " << result.result () <<
|
||||
", dstAmtReq: " << saDstAmountReq <<
|
||||
", sendMax: " << saMaxAmountReq <<
|
||||
", algo: " << algoName;
|
||||
};
|
||||
if (callFlowV1)
|
||||
{
|
||||
logResult ("V1", flowV1Out);
|
||||
}
|
||||
if (callFlowV2)
|
||||
{
|
||||
logResult ("V2", flowV2Out);
|
||||
}
|
||||
}
|
||||
|
||||
JLOG (j.trace()) << "Using old flow: " << useFlowV1Output;
|
||||
}
|
||||
|
||||
|
||||
@@ -52,6 +52,9 @@ private:
|
||||
Book book_;
|
||||
AccountID strandSrc_;
|
||||
AccountID strandDst_;
|
||||
// Charge transfer fees whan the prev step redeems
|
||||
Step const* const prevStep_ = nullptr;
|
||||
bool const ownerPaysTransferFee_;
|
||||
beast::Journal j_;
|
||||
|
||||
struct Cache
|
||||
@@ -59,7 +62,6 @@ private:
|
||||
TIn in;
|
||||
TOut out;
|
||||
|
||||
Cache () = default;
|
||||
Cache (TIn const& in_, TOut const& out_)
|
||||
: in (in_), out (out_)
|
||||
{
|
||||
@@ -73,10 +75,14 @@ public:
|
||||
Issue const& out,
|
||||
AccountID const& strandSrc,
|
||||
AccountID const& strandDst,
|
||||
Step const* prevStep,
|
||||
bool ownerPaysTransferFee,
|
||||
beast::Journal j)
|
||||
: book_ (in, out)
|
||||
, strandSrc_ (strandSrc)
|
||||
, strandDst_ (strandDst)
|
||||
, prevStep_ (prevStep)
|
||||
, ownerPaysTransferFee_ (ownerPaysTransferFee)
|
||||
, j_ (j)
|
||||
{
|
||||
}
|
||||
@@ -102,6 +108,12 @@ public:
|
||||
return EitherAmount (cache_->out);
|
||||
}
|
||||
|
||||
bool
|
||||
redeems (ReadView const& sb, bool fwd) const override
|
||||
{
|
||||
return !ownerPaysTransferFee_;
|
||||
}
|
||||
|
||||
boost::optional<Book>
|
||||
bookStepBook () const override
|
||||
{
|
||||
@@ -147,7 +159,8 @@ private:
|
||||
void consumeOffer (PaymentSandbox& sb,
|
||||
TOffer<TIn, TOut>& offer,
|
||||
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
|
||||
{
|
||||
@@ -178,7 +191,9 @@ static
|
||||
void limitStepIn (Quality const& ofrQ,
|
||||
TAmounts<TIn, TOut>& ofrAmt,
|
||||
TAmounts<TIn, TOut>& stpAmt,
|
||||
TOut& ownerGives,
|
||||
std::uint32_t transferRateIn,
|
||||
std::uint32_t transferRateOut,
|
||||
TIn const& limit)
|
||||
{
|
||||
if (limit < stpAmt.in)
|
||||
@@ -188,6 +203,8 @@ void limitStepIn (Quality const& ofrQ,
|
||||
stpAmt.in, QUALITY_ONE, transferRateIn, /*roundUp*/ false);
|
||||
ofrAmt = ofrQ.ceil_in (ofrAmt, inLmt);
|
||||
stpAmt.out = ofrAmt.out;
|
||||
ownerGives = mulRatio (
|
||||
ofrAmt.out, transferRateOut, QUALITY_ONE, /*roundUp*/ false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -197,13 +214,17 @@ static
|
||||
void limitStepOut (Quality const& ofrQ,
|
||||
TAmounts<TIn, TOut>& ofrAmt,
|
||||
TAmounts<TIn, TOut>& stpAmt,
|
||||
TOut& ownerGives,
|
||||
std::uint32_t transferRateIn,
|
||||
std::uint32_t transferRateOut,
|
||||
TOut const& limit)
|
||||
{
|
||||
if (limit < stpAmt.out)
|
||||
{
|
||||
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 (
|
||||
ofrAmt.in, transferRateIn, QUALITY_ONE, /*roundUp*/ true);
|
||||
}
|
||||
@@ -225,18 +246,28 @@ forEachOffer (
|
||||
Book const& book,
|
||||
AccountID const& src,
|
||||
AccountID const& dst,
|
||||
bool prevStepRedeems,
|
||||
bool ownerPaysTransferFee,
|
||||
Callback& callback,
|
||||
std::uint32_t limit,
|
||||
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
|
||||
{
|
||||
if (isXRP (id) || id == src || id == dst)
|
||||
if (isXRP (id) || id == dst)
|
||||
return QUALITY_ONE;
|
||||
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);
|
||||
FlowOfferStream<TAmtIn, TAmtOut> offers (
|
||||
@@ -251,16 +282,31 @@ forEachOffer (
|
||||
else if (*ofrQ != offer.quality ())
|
||||
break;
|
||||
|
||||
auto const funds = offers.ownerFunds ();
|
||||
auto ofrAmt = offer.amount ();
|
||||
auto stpAmt = make_Amounts (
|
||||
mulRatio (ofrAmt.in, trIn, QUALITY_ONE, /*roundUp*/ true),
|
||||
ofrAmt.out);
|
||||
// owner pays the transfer fee
|
||||
auto ownerGives =
|
||||
mulRatio (ofrAmt.out, trOut, QUALITY_ONE, /*roundUp*/ false);
|
||||
|
||||
if (funds < stpAmt.out)
|
||||
limitStepOut (*ofrQ, ofrAmt, stpAmt, trIn, funds);
|
||||
auto const 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;
|
||||
}
|
||||
|
||||
@@ -272,7 +318,8 @@ void BookStep<TIn, TOut>::consumeOffer (
|
||||
PaymentSandbox& sb,
|
||||
TOffer<TIn, TOut>& offer,
|
||||
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
|
||||
// is a transfer fee that goes to book_.in.account
|
||||
@@ -283,9 +330,11 @@ void BookStep<TIn, TOut>::consumeOffer (
|
||||
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,
|
||||
toSTAmount (stepAmt.out, book_.out), j_);
|
||||
toSTAmount (ownerGives, book_.out), j_);
|
||||
if (cr != tesSUCCESS)
|
||||
Throw<FlowException> (cr);
|
||||
}
|
||||
@@ -330,7 +379,9 @@ BookStep<TIn, TOut>::revImp (
|
||||
[&](TOffer<TIn, TOut>& offer,
|
||||
TAmounts<TIn, TOut> const& ofrAmt,
|
||||
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)
|
||||
return false;
|
||||
@@ -341,7 +392,7 @@ BookStep<TIn, TOut>::revImp (
|
||||
savedOuts.insert(stpAmt.out);
|
||||
result = TAmounts<TIn, TOut>(sum (savedIns), sum(savedOuts));
|
||||
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,
|
||||
// we need to consume the offer
|
||||
return true;
|
||||
@@ -350,22 +401,24 @@ BookStep<TIn, TOut>::revImp (
|
||||
{
|
||||
auto ofrAdjAmt = ofrAmt;
|
||||
auto stpAdjAmt = stpAmt;
|
||||
limitStepOut (
|
||||
offer.quality (), ofrAdjAmt, stpAdjAmt, transferRateIn, remainingOut);
|
||||
auto ownerGivesAdj = ownerGives;
|
||||
limitStepOut (offer.quality (), ofrAdjAmt, stpAdjAmt, ownerGivesAdj,
|
||||
transferRateIn, transferRateOut, remainingOut);
|
||||
remainingOut = beast::zero;
|
||||
savedIns.insert (stpAdjAmt.in);
|
||||
savedOuts.insert (remainingOut);
|
||||
result.in = sum(savedIns);
|
||||
result.out = out;
|
||||
this->consumeOffer (sb, offer, ofrAdjAmt, stpAdjAmt);
|
||||
this->consumeOffer (sb, offer, ofrAdjAmt, stpAdjAmt, ownerGivesAdj);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
{
|
||||
auto const r = forEachOffer<TIn, TOut> (
|
||||
sb, afView, book_,
|
||||
strandSrc_, strandDst_, eachOffer, maxOffersToConsume_, j_);
|
||||
auto const prevStepRedeems = prevStep_ && prevStep_->redeems (sb, false);
|
||||
auto const r = forEachOffer<TIn, TOut> (sb, afView, book_, strandSrc_,
|
||||
strandDst_, prevStepRedeems, ownerPaysTransferFee_, eachOffer,
|
||||
maxOffersToConsume_, j_);
|
||||
boost::container::flat_set<uint256> toRm = std::move(std::get<0>(r));
|
||||
std::uint32_t const offersConsumed = std::get<1>(r);
|
||||
ofrsToRm.insert (boost::container::ordered_unique_range_t{},
|
||||
@@ -427,42 +480,93 @@ BookStep<TIn, TOut>::fwdImp (
|
||||
[&](TOffer<TIn, TOut>& offer,
|
||||
TAmounts<TIn, TOut> const& ofrAmt,
|
||||
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)
|
||||
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)
|
||||
{
|
||||
savedIns.insert(stpAmt.in);
|
||||
savedOuts.insert(stpAmt.out);
|
||||
lastOut = savedOuts.insert(stpAmt.out);
|
||||
result = TAmounts<TIn, TOut>(sum (savedIns), sum(savedOuts));
|
||||
remainingIn = in - result.in;
|
||||
this->consumeOffer (sb, offer, ofrAmt, stpAmt);
|
||||
// return true b/c even if the payment is satisfied,
|
||||
// we need to consume the offer
|
||||
return true;
|
||||
// consume the offer even if stepAmt.in == remainingIn
|
||||
processMore = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
auto ofrAdjAmt = ofrAmt;
|
||||
auto stpAdjAmt = stpAmt;
|
||||
limitStepIn (
|
||||
offer.quality (), ofrAdjAmt, stpAdjAmt, transferRateIn, remainingIn);
|
||||
limitStepIn (offer.quality (), ofrAdjAmt, stpAdjAmt, ownerGivesAdj,
|
||||
transferRateIn, transferRateOut, remainingIn);
|
||||
savedIns.insert (remainingIn);
|
||||
savedOuts.insert (stpAdjAmt.out);
|
||||
remainingIn = beast::zero;
|
||||
lastOut = savedOuts.insert (stpAdjAmt.out);
|
||||
result.out = sum (savedOuts);
|
||||
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> (
|
||||
sb, afView, book_,
|
||||
strandSrc_, strandDst_, eachOffer, maxOffersToConsume_, j_);
|
||||
auto const prevStepRedeems = prevStep_ && prevStep_->redeems (sb, true);
|
||||
auto const r = forEachOffer<TIn, TOut> (sb, afView, book_, strandSrc_,
|
||||
strandDst_, prevStepRedeems, ownerPaysTransferFee_, eachOffer,
|
||||
maxOffersToConsume_, j_);
|
||||
boost::container::flat_set<uint256> toRm = std::move(std::get<0>(r));
|
||||
std::uint32_t const offersConsumed = std::get<1>(r);
|
||||
ofrsToRm.insert (boost::container::ordered_unique_range_t{},
|
||||
@@ -607,7 +711,8 @@ make_BookStepHelper (
|
||||
Issue const& out)
|
||||
{
|
||||
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);
|
||||
if (ter != tesSUCCESS)
|
||||
return {ter, nullptr};
|
||||
|
||||
@@ -39,9 +39,10 @@ class DirectStepI : public StepImp<IOUAmount, IOUAmount, DirectStepI>
|
||||
AccountID src_;
|
||||
AccountID dst_;
|
||||
Currency currency_;
|
||||
// Transfer fees are never charged when sending directly to or from an
|
||||
// issuing account
|
||||
bool noTransferFee_;
|
||||
|
||||
// Charge transfer fees when the prev step redeems
|
||||
Step const* const prevStep_ = nullptr;
|
||||
|
||||
beast::Journal j_;
|
||||
|
||||
struct Cache
|
||||
@@ -49,15 +50,17 @@ class DirectStepI : public StepImp<IOUAmount, IOUAmount, DirectStepI>
|
||||
IOUAmount in;
|
||||
IOUAmount srcToDst;
|
||||
IOUAmount out;
|
||||
bool srcRedeems;
|
||||
|
||||
Cache () = default;
|
||||
Cache (
|
||||
IOUAmount const& in_,
|
||||
IOUAmount const& srcToDst_,
|
||||
IOUAmount const& out_)
|
||||
IOUAmount const& out_,
|
||||
bool srcRedeems_)
|
||||
: in(in_)
|
||||
, srcToDst(srcToDst_)
|
||||
, out(out_)
|
||||
, srcRedeems(srcRedeems_)
|
||||
{}
|
||||
};
|
||||
|
||||
@@ -67,18 +70,19 @@ class DirectStepI : public StepImp<IOUAmount, IOUAmount, DirectStepI>
|
||||
std::pair <std::uint32_t, std::uint32_t>
|
||||
qualities (
|
||||
PaymentSandbox& sb,
|
||||
bool srcRedeems) const;
|
||||
bool srcRedeems,
|
||||
bool fwd) const;
|
||||
public:
|
||||
DirectStepI (
|
||||
AccountID const& src,
|
||||
AccountID const& dst,
|
||||
Currency const& c,
|
||||
bool noTransferFee,
|
||||
Step const* prevStep,
|
||||
beast::Journal j)
|
||||
:src_(src)
|
||||
, dst_(dst)
|
||||
, currency_ (c)
|
||||
, noTransferFee_ (noTransferFee)
|
||||
, prevStep_ (prevStep)
|
||||
, j_ (j) {}
|
||||
|
||||
AccountID const& src () const
|
||||
@@ -115,6 +119,9 @@ class DirectStepI : public StepImp<IOUAmount, IOUAmount, DirectStepI>
|
||||
return src_;
|
||||
}
|
||||
|
||||
bool
|
||||
redeems (ReadView const& sb, bool fwd) const override;
|
||||
|
||||
std::pair<IOUAmount, IOUAmount>
|
||||
revImp (
|
||||
PaymentSandbox& sb,
|
||||
@@ -142,14 +149,14 @@ class DirectStepI : public StepImp<IOUAmount, IOUAmount, DirectStepI>
|
||||
void setCacheLimiting (
|
||||
IOUAmount const& fwdIn,
|
||||
IOUAmount const& fwdSrcToDst,
|
||||
IOUAmount const& fwdOut);
|
||||
IOUAmount const& fwdOut,
|
||||
bool srcRedeems);
|
||||
|
||||
friend bool operator==(DirectStepI const& lhs, DirectStepI const& rhs)
|
||||
{
|
||||
return lhs.src_ == rhs.src_ &&
|
||||
lhs.dst_ == rhs.dst_ &&
|
||||
lhs.currency_ == rhs.currency_ &&
|
||||
lhs.noTransferFee_ == rhs.noTransferFee_;
|
||||
lhs.currency_ == rhs.currency_;
|
||||
}
|
||||
|
||||
friend bool operator!=(DirectStepI const& lhs, DirectStepI const& rhs)
|
||||
@@ -199,6 +206,25 @@ maxFlow (
|
||||
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>
|
||||
DirectStepI::revImp (
|
||||
PaymentSandbox& sb,
|
||||
@@ -214,7 +240,7 @@ DirectStepI::revImp (
|
||||
maxFlow (sb, src_, dst_, currency_);
|
||||
|
||||
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_);
|
||||
|
||||
@@ -232,7 +258,8 @@ DirectStepI::revImp (
|
||||
cache_.emplace (
|
||||
IOUAmount (beast::zero),
|
||||
IOUAmount (beast::zero),
|
||||
IOUAmount (beast::zero));
|
||||
IOUAmount (beast::zero),
|
||||
srcRedeems);
|
||||
return {beast::zero, beast::zero};
|
||||
}
|
||||
|
||||
@@ -244,7 +271,7 @@ DirectStepI::revImp (
|
||||
|
||||
IOUAmount const in = mulRatio (
|
||||
srcToDst, srcQOut, QUALITY_ONE, /*roundUp*/ true);
|
||||
cache_.emplace (in, srcToDst, out);
|
||||
cache_.emplace (in, srcToDst, out, srcRedeems);
|
||||
rippleCredit (sb,
|
||||
src_, dst_, toSTAmount (srcToDst, srcToDstIss),
|
||||
/*checkIssuer*/ true, j_);
|
||||
@@ -262,7 +289,7 @@ DirectStepI::revImp (
|
||||
maxSrcToDst, srcQOut, QUALITY_ONE, /*roundUp*/ true);
|
||||
IOUAmount const actualOut = mulRatio (
|
||||
maxSrcToDst, dstQIn, QUALITY_ONE, /*roundUp*/ false);
|
||||
cache_.emplace (in, maxSrcToDst, actualOut);
|
||||
cache_.emplace (in, maxSrcToDst, actualOut, srcRedeems);
|
||||
rippleCredit (sb,
|
||||
src_, dst_, toSTAmount (maxSrcToDst, srcToDstIss),
|
||||
/*checkIssuer*/ true, j_);
|
||||
@@ -276,14 +303,15 @@ DirectStepI::revImp (
|
||||
}
|
||||
|
||||
// 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
|
||||
// to prevent this.
|
||||
void
|
||||
DirectStepI::setCacheLimiting (
|
||||
IOUAmount const& fwdIn,
|
||||
IOUAmount const& fwdSrcToDst,
|
||||
IOUAmount const& fwdOut)
|
||||
IOUAmount const& fwdOut,
|
||||
bool srcRedeems)
|
||||
{
|
||||
if (cache_->in < fwdIn)
|
||||
{
|
||||
@@ -305,7 +333,7 @@ DirectStepI::setCacheLimiting (
|
||||
<< " cacheSrcToDst: " << to_string (cache_->srcToDst)
|
||||
<< " fwdOut: " << to_string (fwdOut)
|
||||
<< " cacheOut: " << to_string (cache_->out);
|
||||
cache_.emplace (fwdIn, fwdSrcToDst, fwdOut);
|
||||
cache_.emplace (fwdIn, fwdSrcToDst, fwdOut, srcRedeems);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -315,6 +343,7 @@ DirectStepI::setCacheLimiting (
|
||||
cache_->srcToDst = fwdSrcToDst;
|
||||
if (fwdOut < cache_->out)
|
||||
cache_->out = fwdOut;
|
||||
cache_->srcRedeems = srcRedeems;
|
||||
};
|
||||
|
||||
std::pair<IOUAmount, IOUAmount>
|
||||
@@ -332,7 +361,7 @@ DirectStepI::fwdImp (
|
||||
maxFlow (sb, src_, dst_, currency_);
|
||||
|
||||
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_);
|
||||
|
||||
@@ -350,7 +379,8 @@ DirectStepI::fwdImp (
|
||||
cache_.emplace (
|
||||
IOUAmount (beast::zero),
|
||||
IOUAmount (beast::zero),
|
||||
IOUAmount (beast::zero));
|
||||
IOUAmount (beast::zero),
|
||||
srcRedeems);
|
||||
return {beast::zero, beast::zero};
|
||||
}
|
||||
|
||||
@@ -361,7 +391,7 @@ DirectStepI::fwdImp (
|
||||
{
|
||||
IOUAmount const out = mulRatio (
|
||||
srcToDst, dstQIn, QUALITY_ONE, /*roundUp*/ false);
|
||||
setCacheLimiting (in, srcToDst, out);
|
||||
setCacheLimiting (in, srcToDst, out, srcRedeems);
|
||||
rippleCredit (sb,
|
||||
src_, dst_, toSTAmount (cache_->srcToDst, srcToDstIss),
|
||||
/*checkIssuer*/ true, j_);
|
||||
@@ -379,7 +409,7 @@ DirectStepI::fwdImp (
|
||||
maxSrcToDst, srcQOut, QUALITY_ONE, /*roundUp*/ true);
|
||||
IOUAmount const out = mulRatio (
|
||||
maxSrcToDst, dstQIn, QUALITY_ONE, /*roundUp*/ false);
|
||||
setCacheLimiting (actualIn, maxSrcToDst, out);
|
||||
setCacheLimiting (actualIn, maxSrcToDst, out, srcRedeems);
|
||||
rippleCredit (sb,
|
||||
src_, dst_, toSTAmount (cache_->srcToDst, srcToDstIss),
|
||||
/*checkIssuer*/ true, j_);
|
||||
@@ -499,7 +529,8 @@ quality (
|
||||
std::pair<std::uint32_t, std::uint32_t>
|
||||
DirectStepI::qualities (
|
||||
PaymentSandbox& sb,
|
||||
bool srcRedeems) const
|
||||
bool srcRedeems,
|
||||
bool fwd) const
|
||||
{
|
||||
if (srcRedeems)
|
||||
{
|
||||
@@ -511,9 +542,10 @@ DirectStepI::qualities (
|
||||
}
|
||||
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 =
|
||||
noTransferFee_ ? QUALITY_ONE : rippleTransferRate (sb, src_);
|
||||
prevStepRedeems ? rippleTransferRate (sb, src_) : QUALITY_ONE;
|
||||
return std::make_pair(
|
||||
srcQOut,
|
||||
quality ( // dst quality in
|
||||
@@ -655,8 +687,9 @@ make_DirectStepI (
|
||||
AccountID const& dst,
|
||||
Currency const& c)
|
||||
{
|
||||
// Only charge a transfer fee if the previous step redeems
|
||||
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);
|
||||
if (ter != tesSUCCESS)
|
||||
return {ter, nullptr};
|
||||
|
||||
@@ -139,6 +139,7 @@ toStrand (
|
||||
Issue const& deliver,
|
||||
boost::optional<Issue> const& sendMaxIssue,
|
||||
STPath const& path,
|
||||
bool ownerPaysTransferFee,
|
||||
beast::Journal j)
|
||||
{
|
||||
if (isXRP (src))
|
||||
@@ -233,7 +234,7 @@ toStrand (
|
||||
auto ctx = [&](bool isLast = false)
|
||||
{
|
||||
return StrandContext{view, result, strandSrc, strandDst, isLast,
|
||||
seenDirectIssues, seenBookOuts, j};
|
||||
ownerPaysTransferFee, seenDirectIssues, seenBookOuts, j};
|
||||
};
|
||||
|
||||
for (int i = 0; i < pes.size () - 1; ++i)
|
||||
@@ -359,7 +360,7 @@ toStrand (
|
||||
result.emplace_back (std::move (s.second));
|
||||
else
|
||||
{
|
||||
JLOG (j.warn()) << "toStep failed";
|
||||
JLOG (j.debug()) << "toStep failed: " << s.first;
|
||||
return {s.first, Strand{}};
|
||||
}
|
||||
}
|
||||
@@ -376,6 +377,7 @@ toStrands (
|
||||
boost::optional<Issue> const& sendMax,
|
||||
STPathSet const& paths,
|
||||
bool addDefaultPath,
|
||||
bool ownerPaysTransferFee,
|
||||
beast::Journal j)
|
||||
{
|
||||
std::vector<Strand> result;
|
||||
@@ -391,7 +393,8 @@ toStrands (
|
||||
|
||||
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& strand = sp.second;
|
||||
|
||||
@@ -407,7 +410,10 @@ toStrands (
|
||||
JLOG (j.trace()) << "toStrand failed";
|
||||
Throw<FlowException> (tefEXCEPTION, "toStrand returned tes & empty strand");
|
||||
}
|
||||
insert(std::move(strand));
|
||||
else
|
||||
{
|
||||
insert(std::move(strand));
|
||||
}
|
||||
}
|
||||
else if (paths.empty ())
|
||||
{
|
||||
@@ -419,7 +425,8 @@ toStrands (
|
||||
TER lastFailTer = tesSUCCESS;
|
||||
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& strand = sp.second;
|
||||
|
||||
@@ -436,9 +443,10 @@ toStrands (
|
||||
JLOG (j.trace()) << "toStrand failed";
|
||||
Throw<FlowException> (tefEXCEPTION, "toStrand returned tes & empty strand");
|
||||
}
|
||||
|
||||
if (ter == tesSUCCESS)
|
||||
else
|
||||
{
|
||||
insert(std::move(strand));
|
||||
}
|
||||
}
|
||||
|
||||
if (result.empty ())
|
||||
@@ -455,6 +463,7 @@ StrandContext::StrandContext (
|
||||
AccountID strandSrc_,
|
||||
AccountID strandDst_,
|
||||
bool isLast_,
|
||||
bool ownerPaysTransferFee_,
|
||||
std::array<boost::container::flat_set<Issue>, 2>& seenDirectIssues_,
|
||||
boost::container::flat_set<Issue>& seenBookOuts_,
|
||||
beast::Journal j_)
|
||||
@@ -463,6 +472,7 @@ StrandContext::StrandContext (
|
||||
, strandDst (strandDst_)
|
||||
, isFirst (strand_.empty ())
|
||||
, isLast (isLast_)
|
||||
, ownerPaysTransferFee (ownerPaysTransferFee_)
|
||||
, strandSize (strand_.size ())
|
||||
, prevStep (!strand_.empty () ? strand_.back ().get ()
|
||||
: nullptr)
|
||||
|
||||
@@ -120,6 +120,18 @@ public:
|
||||
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.
|
||||
*/
|
||||
@@ -226,6 +238,7 @@ toStrand (
|
||||
Issue const& deliver,
|
||||
boost::optional<Issue> const& sendMaxIssue,
|
||||
STPath const& path,
|
||||
bool ownerPaysTransferFee,
|
||||
beast::Journal j);
|
||||
|
||||
/**
|
||||
@@ -253,6 +266,7 @@ toStrands (ReadView const& sb,
|
||||
boost::optional<Issue> const& sendMax,
|
||||
STPathSet const& paths,
|
||||
bool addDefaultPath,
|
||||
bool ownerPaysTransferFee,
|
||||
beast::Journal j);
|
||||
|
||||
template <class TIn, class TOut, class TDerived>
|
||||
@@ -338,6 +352,7 @@ struct StrandContext
|
||||
AccountID const strandDst;
|
||||
bool const isFirst;
|
||||
bool const isLast = false;
|
||||
bool ownerPaysTransferFee;
|
||||
size_t const strandSize;
|
||||
// The previous step in the strand. Needed to check the no ripple constraint
|
||||
Step const* const prevStep = nullptr;
|
||||
@@ -357,6 +372,7 @@ struct StrandContext
|
||||
AccountID strandSrc_,
|
||||
AccountID strandDst_,
|
||||
bool isLast_,
|
||||
bool ownerPaysTransferFee_,
|
||||
std::array<boost::container::flat_set<Issue>, 2>& seenDirectIssues_,
|
||||
boost::container::flat_set<Issue>& seenBookOuts_,
|
||||
beast::Journal j);
|
||||
|
||||
@@ -138,7 +138,7 @@ flow (
|
||||
limitStepOut = r.second;
|
||||
|
||||
if (strand[i]->dry (r.second) ||
|
||||
get<TInAmt> (r.first) != get<TInAmt> (*maxIn))
|
||||
get<TInAmt> (r.first) != *maxIn)
|
||||
{
|
||||
// Something is very wrong
|
||||
// throwing out the sandbox can only increase liquidity
|
||||
|
||||
@@ -182,7 +182,7 @@ struct Flow_test : public beast::unit_test::suite
|
||||
TER expTer, auto&&... expSteps)
|
||||
{
|
||||
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);
|
||||
if (sizeof...(expSteps))
|
||||
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);
|
||||
|
||||
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
|
||||
auto r = toStrand (*env.current (), alice,
|
||||
xrpAccount (), XRP, USD.issue (), STPath (), flowJournal);
|
||||
xrpAccount (), XRP, USD.issue (), STPath (), true, flowJournal);
|
||||
expect (r.first == temBAD_PATH);
|
||||
}
|
||||
{
|
||||
// The root account can't be the src
|
||||
auto r =
|
||||
toStrand (*env.current (), xrpAccount (),
|
||||
alice, XRP, boost::none, STPath (), flowJournal);
|
||||
alice, XRP, boost::none, STPath (), true, flowJournal);
|
||||
expect (r.first == temBAD_PATH);
|
||||
}
|
||||
{
|
||||
// The root account can't be the src
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -279,7 +279,7 @@ struct Flow_test : public beast::unit_test::suite
|
||||
// cannot have more than one offer with the same output issue
|
||||
|
||||
using namespace jtx;
|
||||
Env env (*this, features (featureFlowV2));
|
||||
Env env (*this, features (featureFlowV2), features(featureOwnerPaysFee));
|
||||
|
||||
env.fund (XRP (10000), alice, bob, carol, gw);
|
||||
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.trust (USD (1000), alice, bob);
|
||||
env (pay (gw, alice, USD (100)));
|
||||
@@ -308,7 +308,7 @@ struct Flow_test : public beast::unit_test::suite
|
||||
|
||||
{
|
||||
// check global freeze
|
||||
Env env (*this, features(featureFlowV2));
|
||||
Env env (*this, features(featureFlowV2), features(featureOwnerPaysFee));
|
||||
env.fund (XRP (10000), alice, bob, gw);
|
||||
env.trust (USD (1000), alice, bob);
|
||||
env (pay (gw, alice, USD (100)));
|
||||
@@ -333,7 +333,7 @@ struct Flow_test : public beast::unit_test::suite
|
||||
}
|
||||
{
|
||||
// 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.trust (USD (1000), alice, bob);
|
||||
env (pay (gw, alice, USD (100)));
|
||||
@@ -346,7 +346,7 @@ struct Flow_test : public beast::unit_test::suite
|
||||
// check no auth
|
||||
// An account may require authorization to receive IOUs from an
|
||||
// issuer
|
||||
Env env (*this, features(featureFlowV2));
|
||||
Env env (*this, features(featureFlowV2), features(featureOwnerPaysFee));
|
||||
env.fund (XRP (10000), alice, bob, gw);
|
||||
env (fset (gw, asfRequireAuth));
|
||||
env.trust (USD (1000), alice, bob);
|
||||
@@ -359,7 +359,7 @@ struct Flow_test : public beast::unit_test::suite
|
||||
|
||||
// Check pure issue redeem still works
|
||||
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 (equal (r.second, D{alice, gw, usdC}));
|
||||
}
|
||||
@@ -382,7 +382,7 @@ struct Flow_test : public beast::unit_test::suite
|
||||
auto const USD = gw["USD"];
|
||||
{
|
||||
// Pay USD, trivial path
|
||||
Env env (*this, features(featureFlowV2));
|
||||
Env env (*this, features(featureFlowV2), features(featureOwnerPaysFee));
|
||||
|
||||
env.fund (XRP (10000), alice, bob, gw);
|
||||
env.trust (USD (1000), alice, bob);
|
||||
@@ -392,7 +392,7 @@ struct Flow_test : public beast::unit_test::suite
|
||||
}
|
||||
{
|
||||
// XRP transfer
|
||||
Env env (*this, features(featureFlowV2));
|
||||
Env env (*this, features(featureFlowV2), features(featureOwnerPaysFee));
|
||||
|
||||
env.fund (XRP (10000), alice, bob);
|
||||
env (pay (alice, bob, XRP (100)));
|
||||
@@ -401,7 +401,7 @@ struct Flow_test : public beast::unit_test::suite
|
||||
}
|
||||
{
|
||||
// Partial payments
|
||||
Env env (*this, features(featureFlowV2));
|
||||
Env env (*this, features(featureFlowV2), features(featureOwnerPaysFee));
|
||||
|
||||
env.fund (XRP (10000), alice, bob, gw);
|
||||
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
|
||||
Env env (*this, features(featureFlowV2));
|
||||
Env env (*this, features(featureFlowV2), features(featureOwnerPaysFee));
|
||||
|
||||
env.fund (XRP (10000), alice, bob, carol, dan);
|
||||
env.trust (USDA (10), bob);
|
||||
@@ -430,32 +430,52 @@ struct Flow_test : public beast::unit_test::suite
|
||||
{
|
||||
// Pay by rippling through accounts, specify path
|
||||
// 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.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 (rate (bob, 1.1));
|
||||
|
||||
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 (bob, USDA (5.5)));
|
||||
env.require (balance (bob, USDA (5)));
|
||||
}
|
||||
{
|
||||
// test best quality path is taken
|
||||
// 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.trust (USDA (10), bob, carol);
|
||||
env.trust (USDB (10), dan);
|
||||
env.trust (USDC (10), dan);
|
||||
env.trust (USDC (10), alice, dan);
|
||||
env.trust (USDD (20), erin);
|
||||
env (rate (bob, 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),
|
||||
path (bob, dan), txflags (tfNoRippleDirect));
|
||||
|
||||
@@ -465,7 +485,7 @@ struct Flow_test : public beast::unit_test::suite
|
||||
}
|
||||
{
|
||||
// Limit quality
|
||||
Env env (*this, features(featureFlowV2));
|
||||
Env env (*this, features(featureFlowV2), features(featureOwnerPaysFee));
|
||||
|
||||
env.fund (XRP (10000), alice, bob, carol);
|
||||
env.trust (USDA (10), bob);
|
||||
@@ -497,7 +517,7 @@ struct Flow_test : public beast::unit_test::suite
|
||||
|
||||
{
|
||||
// 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.trust (USD (1000), alice, bob, carol);
|
||||
@@ -518,7 +538,7 @@ struct Flow_test : public beast::unit_test::suite
|
||||
}
|
||||
{
|
||||
// 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.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
|
||||
Env env (*this, features(featureFlowV2));
|
||||
Env env (*this, features(featureFlowV2), features(featureOwnerPaysFee));
|
||||
|
||||
env.fund (XRP (10000), alice, bob, carol, gw);
|
||||
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
|
||||
Env env (*this, features(featureFlowV2));
|
||||
Env env (*this, features(featureFlowV2), features(featureOwnerPaysFee));
|
||||
|
||||
env.fund (XRP (10000), alice, bob, carol, gw);
|
||||
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
|
||||
Env env (*this, features(featureFlowV2));
|
||||
Env env (*this, features(featureFlowV2), features(featureOwnerPaysFee));
|
||||
|
||||
env.fund (XRP (10000), alice, bob, carol, gw);
|
||||
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. This test is intentionally similar to the one that removes
|
||||
// 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.trust (USD (1000), alice, bob, carol);
|
||||
@@ -666,7 +686,7 @@ struct Flow_test : public beast::unit_test::suite
|
||||
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);
|
||||
}();
|
||||
|
||||
@@ -689,6 +709,38 @@ struct Flow_test : public beast::unit_test::suite
|
||||
// found unfunded
|
||||
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 ()
|
||||
@@ -709,7 +761,7 @@ struct Flow_test : public beast::unit_test::suite
|
||||
{
|
||||
// Simple payment through a gateway with a
|
||||
// transfer rate
|
||||
Env env (*this, features(featureFlowV2));
|
||||
Env env (*this, features(featureFlowV2), features(featureOwnerPaysFee));
|
||||
|
||||
env.fund (XRP (10000), alice, bob, carol, gw);
|
||||
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
|
||||
Env env (*this, features(featureFlowV2));
|
||||
Env env (*this, features(featureFlowV2), features(featureOwnerPaysFee));
|
||||
|
||||
env.fund (XRP (10000), alice, bob, carol, gw);
|
||||
env(rate(gw, 1.25));
|
||||
@@ -733,25 +785,25 @@ struct Flow_test : public beast::unit_test::suite
|
||||
}
|
||||
{
|
||||
// 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(rate(gw, 1.25));
|
||||
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 (pay (alice, carol, USD (40)), path (~USD), sendmax (XRP (50)));
|
||||
env (pay (alice, carol, USD (50)), path (~USD), sendmax (XRP (50)));
|
||||
env.require (
|
||||
balance (alice, xrpMinusFee (env, 10000 - 50)),
|
||||
balance (bob, USD (0)),
|
||||
balance (carol, USD (40)));
|
||||
balance (bob, USD (2.5)), // owner pays transfer fee
|
||||
balance (carol, USD (50)));
|
||||
}
|
||||
|
||||
{
|
||||
// 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(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, 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 (
|
||||
balance (alice, xrpMinusFee (env, 10000 - 50)),
|
||||
balance (alice, xrpMinusFee (env, 10000 - 40)),
|
||||
balance (bob, USD (40)),
|
||||
balance (bob, EUR (50 - 40)),
|
||||
balance (carol, EUR (32)));
|
||||
balance (bob, EUR (0)),
|
||||
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 ()
|
||||
{
|
||||
using namespace std::chrono_literals;
|
||||
// Mon March 28, 2016 10:00:00am PST
|
||||
static NetClock::time_point const soTime{512503200s};
|
||||
// Wed May 25, 2016 10:00:00am PDT
|
||||
static NetClock::time_point const soTime{517510800s};
|
||||
return soTime;
|
||||
}
|
||||
|
||||
|
||||
@@ -40,6 +40,7 @@ extern uint256 const featureSusPay;
|
||||
extern uint256 const featureTrustSetAuth;
|
||||
extern uint256 const featureFeeEscalation;
|
||||
extern uint256 const featureFlowV2;
|
||||
extern uint256 const featureOwnerPaysFee;
|
||||
|
||||
} // ripple
|
||||
|
||||
|
||||
@@ -51,5 +51,6 @@ uint256 const featureSusPay = feature("SusPay");
|
||||
uint256 const featureTrustSetAuth = feature("TrustSetAuth");
|
||||
uint256 const featureFeeEscalation = feature("FeeEscalation");
|
||||
uint256 const featureFlowV2 = feature("FlowV2");
|
||||
uint256 const featureOwnerPaysFee = feature("OwnerPaysFee");
|
||||
|
||||
} // ripple
|
||||
|
||||
Reference in New Issue
Block a user