Skip to content

Commit

Permalink
[fold] add precision loss
Browse files Browse the repository at this point in the history
  • Loading branch information
dangell7 committed Dec 2, 2024
1 parent d6e305d commit 79ef781
Show file tree
Hide file tree
Showing 3 changed files with 193 additions and 17 deletions.
42 changes: 42 additions & 0 deletions include/xrpl/protocol/STAmount.h
Original file line number Diff line number Diff line change
Expand Up @@ -684,6 +684,48 @@ isXRP(STAmount const& amount)
return amount.native();
}

/** returns true if adding or subtracting results in less than or equal to
* 0.01% precision loss **/
inline bool
isAddable(STAmount const& amt1, STAmount const& amt2)
{
// special case: adding anything to zero is always fine
if (amt1 == beast::zero || amt2 == beast::zero)
return true;

// special case: adding two xrp amounts together.
// this is just an overflow check
if (isXRP(amt1) && isXRP(amt2))
{
XRPAmount A = (amt1.signum() == -1 ? -(amt1.xrp()) : amt1.xrp());
XRPAmount B = (amt2.signum() == -1 ? -(amt2.xrp()) : amt2.xrp());

XRPAmount finalAmt = A + B;
return (finalAmt >= A && finalAmt >= B);
}

static const STAmount one{IOUAmount{1, 0}, noIssue()};
static const STAmount maxLoss{IOUAmount{1, -4}, noIssue()};

STAmount A = amt1;
STAmount B = amt2;

if (isXRP(A))
A = STAmount{IOUAmount{A.xrp().drops(), -6}, noIssue()};

if (isXRP(B))
B = STAmount{IOUAmount{B.xrp().drops(), -6}, noIssue()};

A.setIssue(noIssue());
B.setIssue(noIssue());

STAmount lhs = divide((A - B) + B, A, noIssue()) - one;
STAmount rhs = divide((B - A) + A, B, noIssue()) - one;

return ((rhs.negative() ? -rhs : rhs) + (lhs.negative() ? -lhs : lhs)) <=
maxLoss;
}

// Since `canonicalize` does not have access to a ledger, this is needed to put
// the low-level routine stAmountCanonicalize on an amendment switch. Only
// transactions need to use this switchover. Outside of a transaction it's safe
Expand Down
164 changes: 148 additions & 16 deletions src/test/app/EscrowToken_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1654,6 +1654,13 @@ struct EscrowToken_test : public beast::unit_test::suite
std::find(aod.begin(), aod.end(), aa) != aod.end());
}

{
ripple::Dir iod(*env.current(), keylet::ownerDir(gw.id()));
BEAST_EXPECT(std::distance(iod.begin(), iod.end()) == 4);
BEAST_EXPECT(
std::find(iod.begin(), iod.end(), aa) != iod.end());
}

env(escrow(bob, bob, USD(1000)),
finish_time(env.now() + 1s),
cancel_time(env.now() + 2s));
Expand All @@ -1671,6 +1678,13 @@ struct EscrowToken_test : public beast::unit_test::suite
std::find(bod.begin(), bod.end(), bb) != bod.end());
}

{
ripple::Dir iod(*env.current(), keylet::ownerDir(gw.id()));
BEAST_EXPECT(std::distance(iod.begin(), iod.end()) == 5);
BEAST_EXPECT(
std::find(iod.begin(), iod.end(), bb) != iod.end());
}

env.close(5s);
env(finish(alice, alice, aseq));
{
Expand All @@ -1688,6 +1702,11 @@ struct EscrowToken_test : public beast::unit_test::suite
BEAST_EXPECT(std::distance(bod.begin(), bod.end()) == 2);
BEAST_EXPECT(
std::find(bod.begin(), bod.end(), bb) != bod.end());

ripple::Dir iod(*env.current(), keylet::ownerDir(gw.id()));
BEAST_EXPECT(std::distance(iod.begin(), iod.end()) == 4);
BEAST_EXPECT(
std::find(iod.begin(), iod.end(), bb) != iod.end());
}

env.close(5s);
Expand All @@ -1702,6 +1721,11 @@ struct EscrowToken_test : public beast::unit_test::suite
BEAST_EXPECT(std::distance(bod.begin(), bod.end()) == 1);
BEAST_EXPECT(
std::find(bod.begin(), bod.end(), bb) == bod.end());

ripple::Dir iod(*env.current(), keylet::ownerDir(gw.id()));
BEAST_EXPECT(std::distance(iod.begin(), iod.end()) == 3);
BEAST_EXPECT(
std::find(iod.begin(), iod.end(), bb) == iod.end());
}
}
{
Expand Down Expand Up @@ -1756,6 +1780,13 @@ struct EscrowToken_test : public beast::unit_test::suite
BEAST_EXPECT(std::distance(cod.begin(), cod.end()) == 2);
BEAST_EXPECT(
std::find(cod.begin(), cod.end(), bc) != cod.end());

ripple::Dir iod(*env.current(), keylet::ownerDir(gw.id()));
BEAST_EXPECT(std::distance(iod.begin(), iod.end()) == 5);
BEAST_EXPECT(
std::find(iod.begin(), iod.end(), ab) != iod.end());
BEAST_EXPECT(
std::find(iod.begin(), iod.end(), bc) != iod.end());
}

env.close(5s);
Expand All @@ -1778,6 +1809,13 @@ struct EscrowToken_test : public beast::unit_test::suite

ripple::Dir cod(*env.current(), keylet::ownerDir(carol.id()));
BEAST_EXPECT(std::distance(cod.begin(), cod.end()) == 2);

ripple::Dir iod(*env.current(), keylet::ownerDir(gw.id()));
BEAST_EXPECT(std::distance(iod.begin(), iod.end()) == 4);
BEAST_EXPECT(
std::find(iod.begin(), iod.end(), ab) == iod.end());
BEAST_EXPECT(
std::find(iod.begin(), iod.end(), bc) != iod.end());
}

env.close(5s);
Expand All @@ -1800,6 +1838,112 @@ struct EscrowToken_test : public beast::unit_test::suite

ripple::Dir cod(*env.current(), keylet::ownerDir(carol.id()));
BEAST_EXPECT(std::distance(cod.begin(), cod.end()) == 1);

ripple::Dir iod(*env.current(), keylet::ownerDir(gw.id()));
BEAST_EXPECT(std::distance(iod.begin(), iod.end()) == 3);
BEAST_EXPECT(
std::find(iod.begin(), iod.end(), ab) == iod.end());
BEAST_EXPECT(
std::find(iod.begin(), iod.end(), bc) == iod.end());
}
}

{
testcase("Token Metadata to issuer");

Env env{*this, features};
env.fund(XRP(5000), alice, carol, gw);
env(fset(gw, asfAllowTokenLocking));
env.close();
env.trust(USD(10000), alice, carol);
env.close();
env(pay(gw, alice, USD(5000)));
env(pay(gw, carol, USD(5000)));
env.close();
auto const aseq = env.seq(alice);
auto const gseq = env.seq(gw);

env(escrow(alice, gw, USD(1000)), finish_time(env.now() + 1s));

BEAST_EXPECT(
(*env.meta())[sfTransactionResult] ==
static_cast<std::uint8_t>(tesSUCCESS));
env.close(5s);
env(escrow(gw, carol, USD(1000)),
finish_time(env.now() + 1s),
cancel_time(env.now() + 2s));
BEAST_EXPECT(
(*env.meta())[sfTransactionResult] ==
static_cast<std::uint8_t>(tesSUCCESS));
env.close(5s);

auto const ag = env.le(keylet::escrow(alice.id(), aseq));
BEAST_EXPECT(ag);

auto const gc = env.le(keylet::escrow(gw.id(), gseq));
BEAST_EXPECT(gc);

{
ripple::Dir aod(*env.current(), keylet::ownerDir(alice.id()));
BEAST_EXPECT(std::distance(aod.begin(), aod.end()) == 2);
BEAST_EXPECT(
std::find(aod.begin(), aod.end(), ag) != aod.end());

ripple::Dir cod(*env.current(), keylet::ownerDir(carol.id()));
BEAST_EXPECT(std::distance(cod.begin(), cod.end()) == 2);
BEAST_EXPECT(
std::find(cod.begin(), cod.end(), gc) != cod.end());

ripple::Dir iod(*env.current(), keylet::ownerDir(gw.id()));
BEAST_EXPECT(std::distance(iod.begin(), iod.end()) == 4);
BEAST_EXPECT(
std::find(iod.begin(), iod.end(), ag) != iod.end());
BEAST_EXPECT(
std::find(iod.begin(), iod.end(), gc) != iod.end());
}

env.close(5s);
env(finish(alice, alice, aseq));
{
BEAST_EXPECT(!env.le(keylet::escrow(alice.id(), aseq)));
BEAST_EXPECT(env.le(keylet::escrow(gw.id(), gseq)));

ripple::Dir aod(*env.current(), keylet::ownerDir(alice.id()));
BEAST_EXPECT(std::distance(aod.begin(), aod.end()) == 1);
BEAST_EXPECT(
std::find(aod.begin(), aod.end(), ag) == aod.end());

ripple::Dir cod(*env.current(), keylet::ownerDir(carol.id()));
BEAST_EXPECT(std::distance(cod.begin(), cod.end()) == 2);

ripple::Dir iod(*env.current(), keylet::ownerDir(gw.id()));
BEAST_EXPECT(std::distance(iod.begin(), iod.end()) == 3);
BEAST_EXPECT(
std::find(iod.begin(), iod.end(), ag) == iod.end());
BEAST_EXPECT(
std::find(iod.begin(), iod.end(), gc) != iod.end());
}

env.close(5s);
env(cancel(gw, gw, gseq));
{
BEAST_EXPECT(!env.le(keylet::escrow(alice.id(), aseq)));
BEAST_EXPECT(!env.le(keylet::escrow(gw.id(), gseq)));

ripple::Dir aod(*env.current(), keylet::ownerDir(alice.id()));
BEAST_EXPECT(std::distance(aod.begin(), aod.end()) == 1);
BEAST_EXPECT(
std::find(aod.begin(), aod.end(), ag) == aod.end());

ripple::Dir cod(*env.current(), keylet::ownerDir(carol.id()));
BEAST_EXPECT(std::distance(cod.begin(), cod.end()) == 1);

ripple::Dir iod(*env.current(), keylet::ownerDir(gw.id()));
BEAST_EXPECT(std::distance(iod.begin(), iod.end()) == 2);
BEAST_EXPECT(
std::find(iod.begin(), iod.end(), ag) == iod.end());
BEAST_EXPECT(
std::find(iod.begin(), iod.end(), gc) == iod.end());
}
}
}
Expand Down Expand Up @@ -2758,16 +2902,9 @@ struct EscrowToken_test : public beast::unit_test::suite
condition(cb1),
finish_time(env.now() + 1s),
fee(1500),
ter(tesSUCCESS));
ter(tecPRECISION_LOSS));
env.close();

Json::Value params;
params[jss::ledger_index] = env.current()->seq() - 1;
params[jss::transactions] = true;
params[jss::expand] = true;
auto const jrr = env.rpc("json", "ledger", to_string(params));
std::cout << "jrr: " << jrr << "\n";

auto const seq1 = env.seq(alice);
// alice can create escrow for 1000 iou
env(escrow(alice, bob, USD(1000)),
Expand All @@ -2786,7 +2923,7 @@ struct EscrowToken_test : public beast::unit_test::suite
}

void
testTokenWithFeats(FeatureBitset features)
testWithFeats(FeatureBitset features)
{
testTokenEnablement(features);
testTokenTiming(features);
Expand All @@ -2806,10 +2943,6 @@ struct EscrowToken_test : public beast::unit_test::suite
testTokenTLFreeze(features);
testTokenTLINSF(features);
testTokenPrecisionLoss(features);
// testDeleteIssuer(features);

// test deleting an issuer when they have locked escrow
// TODO: Validate Metadata for IssuerNode
}

public:
Expand All @@ -2818,9 +2951,8 @@ struct EscrowToken_test : public beast::unit_test::suite
{
using namespace test::jtx;
FeatureBitset const all{supported_amendments()};
// testWithFeats(all - featureTokenEscrow);
// testWithFeats(all);
testTokenWithFeats(all);
testWithFeats(all - featureTokenEscrow);
testWithFeats(all);
}
};

Expand Down
4 changes: 3 additions & 1 deletion src/xrpld/app/tx/detail/Escrow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -329,7 +329,9 @@ EscrowCreate::doApply()
if (spendableAmount < amount)
return tecINSUFFICIENT_FUNDS;

// TODO: isAddable (Precision Loss)
// check if there is significant precision loss
if (!isAddable(spendableAmount, amount))
return tecPRECISION_LOSS;
}
}

Expand Down

0 comments on commit 79ef781

Please sign in to comment.