Skip to content

Commit

Permalink
Fix maxPaymentFlow() for cft, add CI/IC book step, update STAmount() …
Browse files Browse the repository at this point in the history
…for cft, update path() for cft, extend unit-test for iou/cft, cft/cft offer crossing, and xrp/cft cross-currency payment.
  • Loading branch information
gregtatcam committed Oct 15, 2023
1 parent 01296cd commit 64ed05e
Show file tree
Hide file tree
Showing 8 changed files with 206 additions and 26 deletions.
21 changes: 17 additions & 4 deletions src/ripple/app/paths/impl/DirectStepCFT.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -468,13 +468,26 @@ std::pair<CFTAmount, DebtDirection>
DirectStepCFT<TDerived>::maxPaymentFlow(ReadView const& sb) const
{
// TODO
auto const srcOwed = toAmount<CFTAmount>(
accountHolds(sb, src_, currency_, dst_, fhIGNORE_FREEZE, j_, true));
if (src_ != issuer_)
{
auto const srcOwed = toAmount<CFTAmount>(accountHolds(
sb, src_, currency_, issuer_, fhIGNORE_FREEZE, j_, true));

if (srcOwed.signum() > 0)
return {srcOwed, DebtDirection::redeems};
}

if (auto const sle = sb.read(keylet::cftIssuance(issuer_, currency_)))
{
std::int64_t const max =
[&]() {
auto const max = sle->getFieldU64(sfMaximumAmount);
return max > 0 ? max : STAmount::cMaxNativeN; // TODO
}() -
sle->getFieldU64(sfOutstandingAmount);
return {CFTAmount{max}, DebtDirection::issues};
}

return {CFTAmount{0}, DebtDirection::redeems};
return {CFTAmount{0}, DebtDirection::issues};

#if 0
// srcOwed is negative or zero
Expand Down
7 changes: 7 additions & 0 deletions src/ripple/app/paths/impl/PaySteps.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,13 @@ toStep(
return make_BookStepXI(ctx, {outCurrency, outIssuer});
}

// CI or IC
if (curIssue.isCFT && !e2->isCft())
return make_BookStepCI(ctx, curIssue, {outCurrency, outIssuer});
if (!curIssue.isCFT && e2->isCft())
return make_BookStepIC(ctx, curIssue, {outCurrency, outIssuer, true});

// CC or II
if (curIssue.isCFT)
return make_BookStepCC(ctx, curIssue, {outCurrency, outIssuer, true});
return make_BookStepII(ctx, curIssue, {outCurrency, outIssuer});
Expand Down
2 changes: 1 addition & 1 deletion src/ripple/protocol/AmountConversions.h
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ toSTAmount(CFTAmount const& cft)
{
bool const isNeg = cft.signum() < 0;
std::uint64_t const umant = isNeg ? -cft.cft() : cft.cft();
return STAmount(umant, isNeg);
return STAmount(umant, isNeg, true);
}

inline STAmount
Expand Down
10 changes: 7 additions & 3 deletions src/ripple/protocol/STAmount.h
Original file line number Diff line number Diff line change
Expand Up @@ -133,12 +133,13 @@ class STAmount final : public STBase, public CountedObject<STAmount>
bool native,
bool negative);

STAmount(SField const& name, std::int64_t mantissa);
STAmount(SField const& name, std::int64_t mantissa, bool isCFT = false);

STAmount(
SField const& name,
std::uint64_t mantissa = 0,
bool negative = false);
bool negative = false,
bool isCFT = false);

STAmount(
SField const& name,
Expand All @@ -147,7 +148,10 @@ class STAmount final : public STBase, public CountedObject<STAmount>
int exponent = 0,
bool negative = false);

explicit STAmount(std::uint64_t mantissa = 0, bool negative = false);
explicit STAmount(
std::uint64_t mantissa = 0,
bool negative = false,
bool isCFT = false);

explicit STAmount(SField const& name, STAmount const& amt);

Expand Down
25 changes: 16 additions & 9 deletions src/ripple/protocol/impl/STAmount.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -288,19 +288,25 @@ STAmount::STAmount(
canonicalize();
}

STAmount::STAmount(SField const& name, std::int64_t mantissa)
: STBase(name), mOffset(0), mType(Type::xrp)
STAmount::STAmount(SField const& name, std::int64_t mantissa, bool isCFT)
: STBase(name), mOffset(0), mType(isCFT ? Type::cft : Type::xrp)
{
mIssue.isCFT = isCFT;
set(mantissa);
}

STAmount::STAmount(SField const& name, std::uint64_t mantissa, bool negative)
STAmount::STAmount(
SField const& name,
std::uint64_t mantissa,
bool negative,
bool isCFT)
: STBase(name)
, mValue(mantissa)
, mOffset(0)
, mType(Type::xrp)
, mType(isCFT ? Type::cft : Type::xrp)
, mIsNegative(negative)
{
mIssue.isCFT = isCFT;
assert(mValue <= std::numeric_limits<std::int64_t>::max());
}

Expand Down Expand Up @@ -333,12 +339,13 @@ STAmount::STAmount(SField const& name, STAmount const& from)

//------------------------------------------------------------------------------

STAmount::STAmount(std::uint64_t mantissa, bool negative)
STAmount::STAmount(std::uint64_t mantissa, bool negative, bool isCFT)
: mValue(mantissa)
, mOffset(0)
, mType(Type::xrp)
, mType(isCFT ? Type::cft : Type::xrp)
, mIsNegative(mantissa != 0 && negative)
{
mIssue.isCFT = isCFT;
assert(mValue <= std::numeric_limits<std::int64_t>::max());
}

Expand Down Expand Up @@ -1845,8 +1852,8 @@ divRoundImpl(

int offset = numOffset - denOffset - 17;

if (resultNegative != roundUp)
canonicalizeRound(isXRP(issue), amount, offset, roundUp);
if (resultNegative != roundUp) // TODO
canonicalizeRound(isXRP(issue) || issue.isCFT, amount, offset, roundUp);

STAmount result = [&]() {
// If appropriate, tell Number the rounding mode we are using.
Expand All @@ -1860,7 +1867,7 @@ divRoundImpl(

if (roundUp && !resultNegative && !result)
{
if (isXRP(issue))
if (isXRP(issue) || issue.isCFT)
{
// return the smallest value above zero
amount = 1;
Expand Down
144 changes: 143 additions & 1 deletion src/test/app/CFToken_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,19 +22,23 @@
#include <test/jtx.h>

namespace ripple {
namespace test {

class CFToken_test : public beast::unit_test::suite
{
void
testEnabled(FeatureBitset features)
{
testcase("Enabled");
using namespace test::jtx;
using namespace jtx;
Account const gw = Account("gw");
Account const alice = Account("alice");
Account const carol = Account("carol");
Account const bob = Account("bob");
auto const USDCFT = gw("USD");
auto const EURCFT = gw("EUR");
auto const USD = gw["USD"];
auto const EUR = gw["EUR"];

{
Env env(*this);
Expand Down Expand Up @@ -82,6 +86,7 @@ class CFToken_test : public beast::unit_test::suite
// specify an amount for a Payment using CFT amount notation
}

// XRP/CFT offer and offer crossing
{
// If the CFT amendment IS enabled, you should be able to make a
// CFT Payment that doesn't cross
Expand Down Expand Up @@ -117,6 +122,142 @@ class CFToken_test : public beast::unit_test::suite
BEAST_EXPECT(cft.holderAmount(alice) == 99);
BEAST_EXPECT(cft.holderAmount(carol) == 301);
}

// USD/CFT offer and offer crossing
{
// If the CFT amendment IS enabled, you should be able to make a
// CFT Payment that doesn't cross
Env env{*this, features | featureCFTokensV1};
env.fund(XRP(10000), gw, alice, carol);
env.close();

env(trust(alice, EUR(30'000)));
env(pay(gw, alice, EUR(10'000)));
env.close();

env(trust(carol, EUR(30'000)));
env(pay(gw, carol, EUR(10'000)));
env.close();

CFTIssuance cft(env, gw, USD.currency);

cft.cftrust(alice);
env(pay(gw, alice, USDCFT(200)));
env.close();
BEAST_EXPECT(cft.holderAmount(alice) == 200);

cft.cftrust(carol);
env(pay(gw, carol, USDCFT(200)));
env.close();
BEAST_EXPECT(cft.holderAmount(carol) == 200);
BEAST_EXPECT(cft.outstandingAmount() == 400);

env(offer(alice, EUR(100), USDCFT(101)));
env.close();
BEAST_EXPECT(expectOffers(
env, alice, 1, {{Amounts{EUR(100), USDCFT(101)}}}));

// Offer crossing
env(offer(carol, USDCFT(101), EUR(100)));
env.close();

BEAST_EXPECT(env.balance(alice, EUR) == EUR(10100));
BEAST_EXPECT(env.balance(carol, EUR) == EUR(9900));
BEAST_EXPECT(expectOffers(env, alice, 0));
BEAST_EXPECT(expectOffers(env, carol, 0));
BEAST_EXPECT(cft.outstandingAmount() == 400);
BEAST_EXPECT(cft.holderAmount(alice) == 99);
BEAST_EXPECT(cft.holderAmount(carol) == 301);
}

// CFT/CFT offer and offer crossing
{
// If the CFT amendment IS enabled, you should be able to make a
// CFT Payment that doesn't cross
Env env{*this, features | featureCFTokensV1};
env.fund(XRP(10000), gw, alice, carol);
env.close();

CFTIssuance cftUsd(env, gw, USD.currency);
CFTIssuance cftEur(env, gw, EUR.currency);

cftUsd.cftrust(alice);
cftEur.cftrust(alice);
env(pay(gw, alice, USDCFT(200)));
env(pay(gw, alice, EURCFT(200)));
env.close();
BEAST_EXPECT(cftUsd.holderAmount(alice) == 200);
BEAST_EXPECT(cftEur.holderAmount(alice) == 200);

cftUsd.cftrust(carol);
cftEur.cftrust(carol);
env(pay(gw, carol, USDCFT(200)));
env(pay(gw, carol, EURCFT(200)));
env.close();
BEAST_EXPECT(cftUsd.holderAmount(carol) == 200);
BEAST_EXPECT(cftEur.holderAmount(carol) == 200);
BEAST_EXPECT(cftUsd.outstandingAmount() == 400);
BEAST_EXPECT(cftEur.outstandingAmount() == 400);

env(offer(alice, EURCFT(100), USDCFT(101)));
env.close();
BEAST_EXPECT(expectOffers(
env, alice, 1, {{Amounts{EURCFT(100), USDCFT(101)}}}));

// Offer crossing
env(offer(carol, USDCFT(101), EURCFT(100)));
env.close();

BEAST_EXPECT(expectOffers(env, alice, 0));
BEAST_EXPECT(expectOffers(env, carol, 0));
BEAST_EXPECT(cftUsd.outstandingAmount() == 400);
BEAST_EXPECT(cftUsd.holderAmount(alice) == 99);
BEAST_EXPECT(cftUsd.holderAmount(carol) == 301);
BEAST_EXPECT(cftEur.outstandingAmount() == 400);
BEAST_EXPECT(cftEur.holderAmount(alice) == 300);
BEAST_EXPECT(cftEur.holderAmount(carol) == 100);
}

// CFT/XRP cross currency payment
{
// If the CFT amendment IS enabled, you should be able to make a
// CFT Payment that doesn't cross
Env env{*this, features | featureCFTokensV1};
env.fund(XRP(10000), gw, alice, carol, bob);
env.close();

CFTIssuance cftUsd(env, gw, USD.currency);

cftUsd.cftrust(alice);
env(pay(gw, alice, USDCFT(200)));
env.close();
BEAST_EXPECT(cftUsd.holderAmount(alice) == 200);

cftUsd.cftrust(carol);
env(pay(gw, carol, USDCFT(200)));
env.close();
BEAST_EXPECT(cftUsd.holderAmount(carol) == 200);
BEAST_EXPECT(cftUsd.outstandingAmount() == 400);

cftUsd.cftrust(bob);

env(offer(alice, XRP(100), USDCFT(101)));
env.close();
BEAST_EXPECT(expectOffers(
env, alice, 1, {{Amounts{XRP(100), USDCFT(101)}}}));

// Payment
env(pay(carol, bob, USDCFT(101)),
jtx::path(~USDCFT),
sendmax(XRP(100)),
txflags(tfPartialPayment));
env.close();

BEAST_EXPECT(expectOffers(env, alice, 0));
BEAST_EXPECT(cftUsd.outstandingAmount() == 400);
BEAST_EXPECT(cftUsd.holderAmount(alice) == 99);
BEAST_EXPECT(cftUsd.holderAmount(bob) == 101);
}
}

public:
Expand All @@ -132,4 +273,5 @@ class CFToken_test : public beast::unit_test::suite

BEAST_DEFINE_TESTSUITE_PRIO(CFToken, tx, ripple, 2);

} // namespace test
} // namespace ripple
18 changes: 11 additions & 7 deletions src/test/jtx/amount.h
Original file line number Diff line number Diff line change
Expand Up @@ -156,9 +156,13 @@ struct BookSpec
{
AccountID account;
ripple::Currency currency;
bool cft;

BookSpec(AccountID const& account_, ripple::Currency const& currency_)
: account(account_), currency(currency_)
BookSpec(
AccountID const& account_,
ripple::Currency const& currency_,
bool isCFT = false)
: account(account_), currency(currency_), cft(isCFT)
{
}
};
Expand Down Expand Up @@ -409,11 +413,11 @@ class CFT
return {issue()};
}

// friend BookSpec
// operator~(CFT const& iou)
// {
// return BookSpec(iou.account.id(), iou.currency);
// }
friend BookSpec
operator~(CFT const& iou)
{
return BookSpec(iou.account.id(), iou.currency);
}
};

std::ostream&
Expand Down
5 changes: 4 additions & 1 deletion src/test/jtx/impl/paths.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,10 @@ void
path::append_one(BookSpec const& book)
{
auto& jv = create();
jv["currency"] = to_string(book.currency);
if (book.cft)
jv["cft_asset"] = to_string(book.currency);
else
jv["currency"] = to_string(book.currency);
jv["issuer"] = toBase58(book.account);
}

Expand Down

0 comments on commit 64ed05e

Please sign in to comment.