Combine AMM Amendments (#521)

* fixAMMv1_2
* fixAMMv1_1
* fixAMMOverflowOffer
* fixLPTokenTransfer
* suppress AMM test logs
* exclude `ltAMM` from `fixPreviousTxnID` Amendment
    - make `sfPreviousTxnID` and `sfPreviousTxnLgrSeq` required for ltAMM
This commit is contained in:
tequ
2025-07-01 13:51:32 +09:00
committed by GitHub
parent 37669452f6
commit e9f671043d
16 changed files with 796 additions and 1921 deletions

View File

@@ -80,7 +80,7 @@ namespace detail {
// Feature.cpp. Because it's only used to reserve storage, and determine how // Feature.cpp. Because it's only used to reserve storage, and determine how
// large to make the FeatureBitset, it MAY be larger. It MUST NOT be less than // large to make the FeatureBitset, it MAY be larger. It MUST NOT be less than
// the actual number of amendments. A LogicError on startup will verify this. // the actual number of amendments. A LogicError on startup will verify this.
static constexpr std::size_t numFeatures = 107; static constexpr std::size_t numFeatures = 103;
/** Amendments that this server supports and the default voting behavior. /** Amendments that this server supports and the default voting behavior.
Whether they are enabled depends on the Rules defined in the validated Whether they are enabled depends on the Rules defined in the validated

View File

@@ -29,13 +29,11 @@
// If you add an amendment here, then do not forget to increment `numFeatures` // If you add an amendment here, then do not forget to increment `numFeatures`
// in include/xrpl/protocol/Feature.h. // in include/xrpl/protocol/Feature.h.
XRPL_FIX (FrozenLPTokenTransfer, Supported::yes, VoteBehavior::DefaultNo)
XRPL_FEATURE(DeepFreeze, Supported::yes, VoteBehavior::DefaultNo) XRPL_FEATURE(DeepFreeze, Supported::yes, VoteBehavior::DefaultNo)
XRPL_FEATURE(PermissionedDomains, Supported::yes, VoteBehavior::DefaultNo) XRPL_FEATURE(PermissionedDomains, Supported::yes, VoteBehavior::DefaultNo)
XRPL_FEATURE(DynamicNFT, Supported::yes, VoteBehavior::DefaultNo) XRPL_FEATURE(DynamicNFT, Supported::yes, VoteBehavior::DefaultNo)
XRPL_FEATURE(Credentials, Supported::yes, VoteBehavior::DefaultNo) XRPL_FEATURE(Credentials, Supported::yes, VoteBehavior::DefaultNo)
XRPL_FEATURE(AMMClawback, Supported::yes, VoteBehavior::DefaultNo) XRPL_FEATURE(AMMClawback, Supported::yes, VoteBehavior::DefaultNo)
XRPL_FIX (AMMv1_2, Supported::yes, VoteBehavior::DefaultNo)
XRPL_FEATURE(MPTokensV1, Supported::yes, VoteBehavior::DefaultNo) XRPL_FEATURE(MPTokensV1, Supported::yes, VoteBehavior::DefaultNo)
// InvariantsV1_1 will be changes to Supported::yes when all the // InvariantsV1_1 will be changes to Supported::yes when all the
// invariants expected to be included under it are complete. // invariants expected to be included under it are complete.
@@ -44,10 +42,8 @@ XRPL_FIX (NFTokenPageLinks, Supported::yes, VoteBehavior::DefaultNo
XRPL_FIX (EnforceNFTokenTrustline, Supported::yes, VoteBehavior::DefaultNo) XRPL_FIX (EnforceNFTokenTrustline, Supported::yes, VoteBehavior::DefaultNo)
XRPL_FIX (ReducedOffersV2, Supported::yes, VoteBehavior::DefaultNo) XRPL_FIX (ReducedOffersV2, Supported::yes, VoteBehavior::DefaultNo)
XRPL_FEATURE(NFTokenMintOffer, Supported::yes, VoteBehavior::DefaultNo) XRPL_FEATURE(NFTokenMintOffer, Supported::yes, VoteBehavior::DefaultNo)
XRPL_FIX (AMMv1_1, Supported::yes, VoteBehavior::DefaultNo)
XRPL_FIX (PreviousTxnID, Supported::yes, VoteBehavior::DefaultNo) XRPL_FIX (PreviousTxnID, Supported::yes, VoteBehavior::DefaultNo)
XRPL_FEATURE(PriceOracle, Supported::yes, VoteBehavior::DefaultNo) XRPL_FEATURE(PriceOracle, Supported::yes, VoteBehavior::DefaultNo)
XRPL_FIX (AMMOverflowOffer, Supported::yes, VoteBehavior::DefaultYes)
XRPL_FIX (InnerObjTemplate, Supported::yes, VoteBehavior::DefaultNo) XRPL_FIX (InnerObjTemplate, Supported::yes, VoteBehavior::DefaultNo)
XRPL_FIX (NFTokenReserve, Supported::yes, VoteBehavior::DefaultNo) XRPL_FIX (NFTokenReserve, Supported::yes, VoteBehavior::DefaultNo)
XRPL_FIX (FillOrKill, Supported::yes, VoteBehavior::DefaultNo) XRPL_FIX (FillOrKill, Supported::yes, VoteBehavior::DefaultNo)

View File

@@ -482,8 +482,8 @@ LEDGER_ENTRY(ltAMM, 0x0079, AMM, amm, ({
{sfAsset, soeREQUIRED}, {sfAsset, soeREQUIRED},
{sfAsset2, soeREQUIRED}, {sfAsset2, soeREQUIRED},
{sfOwnerNode, soeREQUIRED}, {sfOwnerNode, soeREQUIRED},
{sfPreviousTxnID, soeOPTIONAL}, {sfPreviousTxnID, soeREQUIRED},
{sfPreviousTxnLgrSeq, soeOPTIONAL}, {sfPreviousTxnLgrSeq, soeREQUIRED},
})) }))
/** A ledger object which tracks Oracle /** A ledger object which tracks Oracle

View File

@@ -135,12 +135,11 @@ STLedgerEntry::getJson(JsonOptions options) const
bool bool
STLedgerEntry::isThreadedType(Rules const& rules) const STLedgerEntry::isThreadedType(Rules const& rules) const
{ {
static constexpr std::array<LedgerEntryType, 8> newPreviousTxnIDTypes = { static constexpr std::array<LedgerEntryType, 7> newPreviousTxnIDTypes = {
ltDIR_NODE, ltDIR_NODE,
ltAMENDMENTS, ltAMENDMENTS,
ltFEE_SETTINGS, ltFEE_SETTINGS,
ltNEGATIVE_UNL, ltNEGATIVE_UNL,
ltAMM,
ltHOOK_STATE, ltHOOK_STATE,
ltHOOK_DEFINITION, ltHOOK_DEFINITION,
ltIMPORT_VLSEQ, ltIMPORT_VLSEQ,

View File

@@ -94,20 +94,10 @@ private:
sendmax(BTC(1'000)), sendmax(BTC(1'000)),
txflags(tfPartialPayment)); txflags(tfPartialPayment));
if (!features[fixAMMv1_1]) BEAST_EXPECT(ammCarol.expectBalances(
{ STAmount{BTC, UINT64_C(1'001'000000374815), -12},
BEAST_EXPECT(ammCarol.expectBalances( USD(100'000),
STAmount{BTC, UINT64_C(1'001'000000374812), -12}, ammCarol.tokens()));
USD(100'000),
ammCarol.tokens()));
}
else
{
BEAST_EXPECT(ammCarol.expectBalances(
STAmount{BTC, UINT64_C(1'001'000000374815), -12},
USD(100'000),
ammCarol.tokens()));
}
env.require(balance(bob, USD(200'100))); env.require(balance(bob, USD(200'100)));
BEAST_EXPECT(isOffer(env, carol, BTC(49), XRP(49))); BEAST_EXPECT(isOffer(env, carol, BTC(49), XRP(49)));
@@ -720,24 +710,12 @@ private:
auto const jrr = env.rpc("json", "submit", to_string(payment)); auto const jrr = env.rpc("json", "submit", to_string(payment));
BEAST_EXPECT(jrr[jss::result][jss::status] == "success"); BEAST_EXPECT(jrr[jss::result][jss::status] == "success");
BEAST_EXPECT(jrr[jss::result][jss::engine_result] == "tesSUCCESS"); BEAST_EXPECT(jrr[jss::result][jss::engine_result] == "tesSUCCESS");
if (!features[fixAMMv1_1]) BEAST_EXPECT(ammAlice.expectBalances(
{ STAmount(XTS, UINT64_C(101'0101010101011), -13),
BEAST_EXPECT(ammAlice.expectBalances( XXX(99),
STAmount(XTS, UINT64_C(101'010101010101), -12), ammAlice.tokens()));
XXX(99), BEAST_EXPECT(expectLine(
ammAlice.tokens())); env, bob, STAmount{XTS, UINT64_C(98'9898989898989), -13}));
BEAST_EXPECT(expectLine(
env, bob, STAmount{XTS, UINT64_C(98'989898989899), -12}));
}
else
{
BEAST_EXPECT(ammAlice.expectBalances(
STAmount(XTS, UINT64_C(101'0101010101011), -13),
XXX(99),
ammAlice.tokens()));
BEAST_EXPECT(expectLine(
env, bob, STAmount{XTS, UINT64_C(98'9898989898989), -13}));
}
BEAST_EXPECT(expectLine(env, bob, XXX(101))); BEAST_EXPECT(expectLine(env, bob, XXX(101)));
} }
@@ -1291,34 +1269,17 @@ private:
env(offer(cam, B_BUX(30), A_BUX(30))); env(offer(cam, B_BUX(30), A_BUX(30)));
// AMM is consumed up to the first cam Offer quality // AMM is consumed up to the first cam Offer quality
if (!features[fixAMMv1_1]) BEAST_EXPECT(ammCarol.expectBalances(
{ STAmount{A_BUX, UINT64_C(309'3541659651604), -13},
BEAST_EXPECT(ammCarol.expectBalances( STAmount{B_BUX, UINT64_C(320'0215509984419), -13},
STAmount{A_BUX, UINT64_C(309'3541659651605), -13}, ammCarol.tokens()));
STAmount{B_BUX, UINT64_C(320'0215509984417), -13}, BEAST_EXPECT(expectOffers(
ammCarol.tokens())); env,
BEAST_EXPECT(expectOffers( cam,
env, 1,
cam, {{Amounts{
1, STAmount{B_BUX, UINT64_C(20'0215509984419), -13},
{{Amounts{ STAmount{A_BUX, UINT64_C(20'0215509984419), -13}}}}));
STAmount{B_BUX, UINT64_C(20'0215509984417), -13},
STAmount{A_BUX, UINT64_C(20'0215509984417), -13}}}}));
}
else
{
BEAST_EXPECT(ammCarol.expectBalances(
STAmount{A_BUX, UINT64_C(309'3541659651604), -13},
STAmount{B_BUX, UINT64_C(320'0215509984419), -13},
ammCarol.tokens()));
BEAST_EXPECT(expectOffers(
env,
cam,
1,
{{Amounts{
STAmount{B_BUX, UINT64_C(20'0215509984419), -13},
STAmount{A_BUX, UINT64_C(20'0215509984419), -13}}}}));
}
} }
void void
@@ -1444,7 +1405,6 @@ private:
using namespace jtx; using namespace jtx;
FeatureBitset const all{supported_amendments()}; FeatureBitset const all{supported_amendments()};
testRmFundedOffer(all); testRmFundedOffer(all);
testRmFundedOffer(all - fixAMMv1_1);
testEnforceNoRipple(all); testEnforceNoRipple(all);
testFillModes(all); testFillModes(all);
testOfferCrossWithXRP(all); testOfferCrossWithXRP(all);
@@ -1458,7 +1418,6 @@ private:
testOfferCreateThenCross(all); testOfferCreateThenCross(all);
testSellFlagExceedLimit(all); testSellFlagExceedLimit(all);
testGatewayCrossCurrency(all); testGatewayCrossCurrency(all);
testGatewayCrossCurrency(all - fixAMMv1_1);
testBridgedCross(all); testBridgedCross(all);
testSellWithFillOrKill(all); testSellWithFillOrKill(all);
testTransferRateOffer(all); testTransferRateOffer(all);
@@ -1466,7 +1425,6 @@ private:
testBadPathAssert(all); testBadPathAssert(all);
testSellFlagBasic(all); testSellFlagBasic(all);
testDirectToDirectPath(all); testDirectToDirectPath(all);
testDirectToDirectPath(all - fixAMMv1_1);
testRequireAuth(all); testRequireAuth(all);
testMissingAuth(all); testMissingAuth(all);
} }
@@ -2326,36 +2284,16 @@ private:
txflags(tfNoRippleDirect | tfPartialPayment | tfLimitQuality)); txflags(tfNoRippleDirect | tfPartialPayment | tfLimitQuality));
env.close(); env.close();
if (!features[fixAMMv1_1]) // alice buys 77.2727USD with 75.5555GBP and pays 25% tr fee
{ // on 75.5555GBP
// alice buys 77.2727USD with 75.5555GBP and pays 25% tr fee // 1,200 - 75.55555*1.25 = 1200 - 94.4444 = 1105.55555GBP
// on 75.5555GBP BEAST_EXPECT(expectLine(
// 1,200 - 75.55555*1.25 = 1200 - 94.4444 = 1105.55555GBP env, alice, STAmount{GBP, UINT64_C(1'105'555555555554), -12}));
BEAST_EXPECT(expectLine( // 75.5555GBP is swapped in for 77.7272USD
env, BEAST_EXPECT(amm.expectBalances(
alice, STAmount{GBP, UINT64_C(1'075'555555555557), -12},
STAmount{GBP, UINT64_C(1'105'555555555555), -12})); STAmount{USD, UINT64_C(1'022'727272727272), -12},
// 75.5555GBP is swapped in for 77.7272USD amm.tokens()));
BEAST_EXPECT(amm.expectBalances(
STAmount{GBP, UINT64_C(1'075'555555555556), -12},
STAmount{USD, UINT64_C(1'022'727272727272), -12},
amm.tokens()));
}
else
{
// alice buys 77.2727USD with 75.5555GBP and pays 25% tr fee
// on 75.5555GBP
// 1,200 - 75.55555*1.25 = 1200 - 94.4444 = 1105.55555GBP
BEAST_EXPECT(expectLine(
env,
alice,
STAmount{GBP, UINT64_C(1'105'555555555554), -12}));
// 75.5555GBP is swapped in for 77.7272USD
BEAST_EXPECT(amm.expectBalances(
STAmount{GBP, UINT64_C(1'075'555555555557), -12},
STAmount{USD, UINT64_C(1'022'727272727272), -12},
amm.tokens()));
}
BEAST_EXPECT(expectLine( BEAST_EXPECT(expectLine(
env, carol, STAmount{USD, UINT64_C(1'277'272727272728), -12})); env, carol, STAmount{USD, UINT64_C(1'277'272727272728), -12}));
} }
@@ -2373,36 +2311,18 @@ private:
env(offer(alice, EUR(100), USD(100))); env(offer(alice, EUR(100), USD(100)));
env.close(); env.close();
if (!features[fixAMMv1_1]) // 95.2380USD is swapped in for 100EUR
{ BEAST_EXPECT(amm.expectBalances(
// 95.2380USD is swapped in for 100EUR STAmount{USD, UINT64_C(1'095'238095238096), -12},
BEAST_EXPECT(amm.expectBalances( EUR(1'050),
STAmount{USD, UINT64_C(1'095'238095238095), -12}, amm.tokens()));
EUR(1'050), // alice pays 25% tr fee on 95.2380USD
amm.tokens())); // 1200-95.2380*1.25 = 1200 - 119.0477 = 1080.9523USD
// alice pays 25% tr fee on 95.2380USD BEAST_EXPECT(expectLine(
// 1200-95.2380*1.25 = 1200 - 119.0477 = 1080.9523USD env,
BEAST_EXPECT(expectLine( alice,
env, STAmount{USD, UINT64_C(1'080'95238095238), -11},
alice, EUR(1'300)));
STAmount{USD, UINT64_C(1'080'952380952381), -12},
EUR(1'300)));
}
else
{
// 95.2380USD is swapped in for 100EUR
BEAST_EXPECT(amm.expectBalances(
STAmount{USD, UINT64_C(1'095'238095238096), -12},
EUR(1'050),
amm.tokens()));
// alice pays 25% tr fee on 95.2380USD
// 1200-95.2380*1.25 = 1200 - 119.0477 = 1080.9523USD
BEAST_EXPECT(expectLine(
env,
alice,
STAmount{USD, UINT64_C(1'080'95238095238), -11},
EUR(1'300)));
}
BEAST_EXPECT(expectOffers(env, alice, 0)); BEAST_EXPECT(expectOffers(env, alice, 0));
} }
@@ -2426,42 +2346,23 @@ private:
env(pay(gw, dan, USD(1'000))); env(pay(gw, dan, USD(1'000)));
AMM ammDan(env, dan, USD(1'000), EUR(1'050)); AMM ammDan(env, dan, USD(1'000), EUR(1'050));
if (!features[fixAMMv1_1]) // alice -> bob -> gw -> carol. $50 should have transfer fee;
{ // $10, no fee
// alice -> bob -> gw -> carol. $50 should have transfer fee; env(pay(alice, carol, EUR(50)),
// $10, no fee path(bob, gw, ~EUR),
env(pay(alice, carol, EUR(50)), sendmax(USDA(60.1)),
path(bob, gw, ~EUR), txflags(tfNoRippleDirect));
sendmax(USDA(60)), BEAST_EXPECT(ammDan.expectBalances(
txflags(tfNoRippleDirect)); STAmount{USD, UINT64_C(1'050'000000000001), -12},
BEAST_EXPECT(ammDan.expectBalances( EUR(1'000),
USD(1'050), EUR(1'000), ammDan.tokens())); ammDan.tokens()));
BEAST_EXPECT(expectLine(env, dan, USD(0))); BEAST_EXPECT(expectLine(env, dan, USD(0)));
BEAST_EXPECT(expectLine(env, dan, EUR(0))); BEAST_EXPECT(expectLine(env, dan, EUR(0)));
BEAST_EXPECT(expectLine(env, bob, USD(-10))); BEAST_EXPECT(expectLine(
BEAST_EXPECT(expectLine(env, bob, USDA(60))); env, bob, STAmount{USD, INT64_C(-10'000000000001), -12}));
BEAST_EXPECT(expectLine(env, carol, EUR(50))); BEAST_EXPECT(expectLine(
} env, bob, STAmount{USDA, UINT64_C(60'000000000001), -12}));
else BEAST_EXPECT(expectLine(env, carol, EUR(50)));
{
// 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.1)),
txflags(tfNoRippleDirect));
BEAST_EXPECT(ammDan.expectBalances(
STAmount{USD, UINT64_C(1'050'000000000001), -12},
EUR(1'000),
ammDan.tokens()));
BEAST_EXPECT(expectLine(env, dan, USD(0)));
BEAST_EXPECT(expectLine(env, dan, EUR(0)));
BEAST_EXPECT(expectLine(
env, bob, STAmount{USD, INT64_C(-10'000000000001), -12}));
BEAST_EXPECT(expectLine(
env, bob, STAmount{USDA, UINT64_C(60'000000000001), -12}));
BEAST_EXPECT(expectLine(env, carol, EUR(50)));
}
} }
} }
@@ -2495,21 +2396,11 @@ private:
// alice buys 107.1428USD with 120GBP and pays 25% tr fee on 120GBP // alice buys 107.1428USD with 120GBP and pays 25% tr fee on 120GBP
// 1,000 - 120*1.25 = 850GBP // 1,000 - 120*1.25 = 850GBP
BEAST_EXPECT(expectLine(env, alice, GBP(850))); BEAST_EXPECT(expectLine(env, alice, GBP(850)));
if (!features[fixAMMv1_1]) BEAST_EXPECT(amm.expectBalances(
{ GBP(1'120),
// 120GBP is swapped in for 107.1428USD STAmount{USD, UINT64_C(892'8571428571429), -13},
BEAST_EXPECT(amm.expectBalances( amm.tokens()));
GBP(1'120),
STAmount{USD, UINT64_C(892'8571428571428), -13},
amm.tokens()));
}
else
{
BEAST_EXPECT(amm.expectBalances(
GBP(1'120),
STAmount{USD, UINT64_C(892'8571428571429), -13},
amm.tokens()));
}
// 25% of 85.7142USD is paid in tr fee // 25% of 85.7142USD is paid in tr fee
// 85.7142*1.25 = 107.1428USD // 85.7142*1.25 = 107.1428USD
BEAST_EXPECT(expectLine( BEAST_EXPECT(expectLine(
@@ -2584,38 +2475,20 @@ private:
env.close(); env.close();
BEAST_EXPECT(expectLine(env, alice, GBP(850))); BEAST_EXPECT(expectLine(env, alice, GBP(850)));
if (!features[fixAMMv1_1]) // alice buys 107.1428EUR with 120GBP and pays 25% tr fee on
{ // 120GBP 1,000 - 120*1.25 = 850GBP 120GBP is swapped in for
// alice buys 107.1428EUR with 120GBP and pays 25% tr fee on // 107.1428EUR
// 120GBP 1,000 - 120*1.25 = 850GBP 120GBP is swapped in for BEAST_EXPECT(amm1.expectBalances(
// 107.1428EUR GBP(1'120),
BEAST_EXPECT(amm1.expectBalances( STAmount{EUR, UINT64_C(892'8571428571429), -13},
GBP(1'120), amm1.tokens()));
STAmount{EUR, UINT64_C(892'8571428571428), -13}, // 25% on 85.7142EUR is paid in tr fee 85.7142*1.25 =
amm1.tokens())); // 107.1428EUR 85.7142EUR is swapped in for 78.9473USD
// 25% on 85.7142EUR is paid in tr fee 85.7142*1.25 = BEAST_EXPECT(amm2.expectBalances(
// 107.1428EUR 85.7142EUR is swapped in for 78.9473USD STAmount(EUR, UINT64_C(1'085'714285714286), -12),
BEAST_EXPECT(amm2.expectBalances( STAmount{USD, UINT64_C(921'052631578948), -12},
STAmount(EUR, UINT64_C(1'085'714285714286), -12), amm2.tokens()));
STAmount{USD, UINT64_C(921'0526315789471), -13},
amm2.tokens()));
}
else
{
// alice buys 107.1428EUR with 120GBP and pays 25% tr fee on
// 120GBP 1,000 - 120*1.25 = 850GBP 120GBP is swapped in for
// 107.1428EUR
BEAST_EXPECT(amm1.expectBalances(
GBP(1'120),
STAmount{EUR, UINT64_C(892'8571428571429), -13},
amm1.tokens()));
// 25% on 85.7142EUR is paid in tr fee 85.7142*1.25 =
// 107.1428EUR 85.7142EUR is swapped in for 78.9473USD
BEAST_EXPECT(amm2.expectBalances(
STAmount(EUR, UINT64_C(1'085'714285714286), -12),
STAmount{USD, UINT64_C(921'052631578948), -12},
amm2.tokens()));
}
// 25% on 63.1578USD is paid in tr fee 63.1578*1.25 = 78.9473USD // 25% on 63.1578USD is paid in tr fee 63.1578*1.25 = 78.9473USD
BEAST_EXPECT(expectLine( BEAST_EXPECT(expectLine(
env, carol, STAmount(USD, UINT64_C(1'063'157894736842), -12))); env, carol, STAmount(USD, UINT64_C(1'063'157894736842), -12)));
@@ -2701,31 +2574,17 @@ private:
txflags(tfNoRippleDirect | tfPartialPayment | tfLimitQuality)); txflags(tfNoRippleDirect | tfPartialPayment | tfLimitQuality));
env.close(); env.close();
if (!features[fixAMMv1_1]) // alice buys 28.125USD with 24GBP and pays 25% tr fee
{ // on 24GBP
// alice buys 28.125USD with 24GBP and pays 25% tr fee // 1,200 - 24*1.25 =~ 1,170GBP
// on 24GBP BEAST_EXPECT(expectLine(
// 1,200 - 24*1.25 = 1,170GBP env, alice, STAmount{GBP, UINT64_C(1'169'999999999999), -12}));
BEAST_EXPECT(expectLine(env, alice, GBP(1'170))); // 24GBP is swapped in for 28.125USD
// 24GBP is swapped in for 28.125USD BEAST_EXPECT(amm.expectBalances(
BEAST_EXPECT(amm.expectBalances( STAmount{GBP, UINT64_C(1'024'000000000001), -12},
GBP(1'024), USD(1'171.875), amm.tokens())); USD(1'171.875),
} amm.tokens()));
else
{
// alice buys 28.125USD with 24GBP and pays 25% tr fee
// on 24GBP
// 1,200 - 24*1.25 =~ 1,170GBP
BEAST_EXPECT(expectLine(
env,
alice,
STAmount{GBP, UINT64_C(1'169'999999999999), -12}));
// 24GBP is swapped in for 28.125USD
BEAST_EXPECT(amm.expectBalances(
STAmount{GBP, UINT64_C(1'024'000000000001), -12},
USD(1'171.875),
amm.tokens()));
}
// 25% on 22.5USD is paid in tr fee // 25% on 22.5USD is paid in tr fee
// 22.5*1.25 = 28.125USD // 22.5*1.25 = 28.125USD
BEAST_EXPECT(expectLine(env, carol, USD(1'222.5))); BEAST_EXPECT(expectLine(env, carol, USD(1'222.5)));
@@ -2758,66 +2617,32 @@ private:
txflags(tfNoRippleDirect | tfPartialPayment | tfLimitQuality)); txflags(tfNoRippleDirect | tfPartialPayment | tfLimitQuality));
env.close(); env.close();
if (!features[fixAMMv1_1]) // alice buys 70.4210EUR with 70.4210GBP via the offer
{ // and pays 25% tr fee on 70.4210GBP
// alice buys 70.4210EUR with 70.4210GBP via the offer // 1,400 - 70.4210*1.25 = 1400 - 88.0262 = 1311.9736GBP
// and pays 25% tr fee on 70.4210GBP BEAST_EXPECT(expectLine(
// 1,400 - 70.4210*1.25 = 1400 - 88.0262 = 1311.9736GBP env, alice, STAmount{GBP, UINT64_C(1'311'973684210525), -12}));
BEAST_EXPECT(expectLine( // ed doesn't pay tr fee, the balances reflect consumed offer
env, // 70.4210GBP/70.4210EUR
alice, BEAST_EXPECT(expectLine(
STAmount{GBP, UINT64_C(1'311'973684210527), -12})); env,
// ed doesn't pay tr fee, the balances reflect consumed offer ed,
// 70.4210GBP/70.4210EUR STAmount{EUR, UINT64_C(1'329'57894736842), -11},
BEAST_EXPECT(expectLine( STAmount{GBP, UINT64_C(1'470'42105263158), -11}));
env, BEAST_EXPECT(expectOffers(
ed, env,
STAmount{EUR, UINT64_C(1'329'578947368421), -12}, ed,
STAmount{GBP, UINT64_C(1'470'421052631579), -12})); 1,
BEAST_EXPECT(expectOffers( {Amounts{
env, STAmount{GBP, UINT64_C(929'57894736842), -11},
ed, STAmount{EUR, UINT64_C(929'57894736842), -11}}}));
1, // 25% on 56.3368EUR is paid in tr fee 56.3368*1.25 = 70.4210EUR
{Amounts{ // 56.3368EUR is swapped in for 74.6651USD
STAmount{GBP, UINT64_C(929'5789473684212), -13}, BEAST_EXPECT(amm.expectBalances(
STAmount{EUR, UINT64_C(929'5789473684212), -13}}})); STAmount{EUR, UINT64_C(1'056'336842105264), -12},
// 25% on 56.3368EUR is paid in tr fee 56.3368*1.25 = 70.4210EUR STAmount{USD, UINT64_C(1'325'334821428571), -12},
// 56.3368EUR is swapped in for 74.6651USD amm.tokens()));
BEAST_EXPECT(amm.expectBalances(
STAmount{EUR, UINT64_C(1'056'336842105263), -12},
STAmount{USD, UINT64_C(1'325'334821428571), -12},
amm.tokens()));
}
else
{
// alice buys 70.4210EUR with 70.4210GBP via the offer
// and pays 25% tr fee on 70.4210GBP
// 1,400 - 70.4210*1.25 = 1400 - 88.0262 = 1311.9736GBP
BEAST_EXPECT(expectLine(
env,
alice,
STAmount{GBP, UINT64_C(1'311'973684210525), -12}));
// ed doesn't pay tr fee, the balances reflect consumed offer
// 70.4210GBP/70.4210EUR
BEAST_EXPECT(expectLine(
env,
ed,
STAmount{EUR, UINT64_C(1'329'57894736842), -11},
STAmount{GBP, UINT64_C(1'470'42105263158), -11}));
BEAST_EXPECT(expectOffers(
env,
ed,
1,
{Amounts{
STAmount{GBP, UINT64_C(929'57894736842), -11},
STAmount{EUR, UINT64_C(929'57894736842), -11}}}));
// 25% on 56.3368EUR is paid in tr fee 56.3368*1.25 = 70.4210EUR
// 56.3368EUR is swapped in for 74.6651USD
BEAST_EXPECT(amm.expectBalances(
STAmount{EUR, UINT64_C(1'056'336842105264), -12},
STAmount{USD, UINT64_C(1'325'334821428571), -12},
amm.tokens()));
}
// 25% on 59.7321USD is paid in tr fee 59.7321*1.25 = 74.6651USD // 25% on 59.7321USD is paid in tr fee 59.7321*1.25 = 74.6651USD
BEAST_EXPECT(expectLine( BEAST_EXPECT(expectLine(
env, carol, STAmount(USD, UINT64_C(1'459'732142857143), -12))); env, carol, STAmount(USD, UINT64_C(1'459'732142857143), -12)));
@@ -2850,40 +2675,19 @@ private:
txflags(tfNoRippleDirect | tfPartialPayment | tfLimitQuality)); txflags(tfNoRippleDirect | tfPartialPayment | tfLimitQuality));
env.close(); env.close();
if (!features[fixAMMv1_1]) // alice buys 53.3322EUR with 56.3368GBP via the amm
{ // and pays 25% tr fee on 56.3368GBP
// alice buys 53.3322EUR with 56.3368GBP via the amm // 1,400 - 56.3368*1.25 = 1400 - 70.4210 = 1329.5789GBP
// and pays 25% tr fee on 56.3368GBP BEAST_EXPECT(expectLine(
// 1,400 - 56.3368*1.25 = 1400 - 70.4210 = 1329.5789GBP env, alice, STAmount{GBP, UINT64_C(1'329'57894736842), -11}));
BEAST_EXPECT(expectLine( //// 25% on 56.3368EUR is paid in tr fee 56.3368*1.25
env, ///= 70.4210EUR
alice, // 56.3368GBP is swapped in for 53.3322EUR
STAmount{GBP, UINT64_C(1'329'578947368421), -12})); BEAST_EXPECT(amm.expectBalances(
//// 25% on 56.3368EUR is paid in tr fee 56.3368*1.25 STAmount{GBP, UINT64_C(1'056'336842105264), -12},
///= 70.4210EUR STAmount{EUR, UINT64_C(946'6677295918366), -13},
// 56.3368GBP is swapped in for 53.3322EUR amm.tokens()));
BEAST_EXPECT(amm.expectBalances(
STAmount{GBP, UINT64_C(1'056'336842105263), -12},
STAmount{EUR, UINT64_C(946'6677295918366), -13},
amm.tokens()));
}
else
{
// alice buys 53.3322EUR with 56.3368GBP via the amm
// and pays 25% tr fee on 56.3368GBP
// 1,400 - 56.3368*1.25 = 1400 - 70.4210 = 1329.5789GBP
BEAST_EXPECT(expectLine(
env,
alice,
STAmount{GBP, UINT64_C(1'329'57894736842), -11}));
//// 25% on 56.3368EUR is paid in tr fee 56.3368*1.25
///= 70.4210EUR
// 56.3368GBP is swapped in for 53.3322EUR
BEAST_EXPECT(amm.expectBalances(
STAmount{GBP, UINT64_C(1'056'336842105264), -12},
STAmount{EUR, UINT64_C(946'6677295918366), -13},
amm.tokens()));
}
// 25% on 42.6658EUR is paid in tr fee 42.6658*1.25 = 53.3322EUR // 25% on 42.6658EUR is paid in tr fee 42.6658*1.25 = 53.3322EUR
// 42.6658EUR/59.7321USD // 42.6658EUR/59.7321USD
BEAST_EXPECT(expectLine( BEAST_EXPECT(expectLine(
@@ -2928,48 +2732,23 @@ private:
txflags(tfNoRippleDirect | tfPartialPayment | tfLimitQuality)); txflags(tfNoRippleDirect | tfPartialPayment | tfLimitQuality));
env.close(); env.close();
if (!features[fixAMMv1_1]) // alice buys 53.3322EUR with 107.5308GBP
{ // 25% on 86.0246GBP is paid in tr fee
// alice buys 53.3322EUR with 107.5308GBP // 1,400 - 86.0246*1.25 = 1400 - 107.5308 = 1229.4691GBP
// 25% on 86.0246GBP is paid in tr fee BEAST_EXPECT(expectLine(
// 1,400 - 86.0246*1.25 = 1400 - 107.5308 = 1229.4691GBP env, alice, STAmount{GBP, UINT64_C(1'292'469135802466), -12}));
BEAST_EXPECT(expectLine( // 86.0246GBP is swapped in for 79.2106EUR
env, BEAST_EXPECT(amm1.expectBalances(
alice, STAmount{GBP, UINT64_C(1'086'024691358027), -12},
STAmount{GBP, UINT64_C(1'292'469135802469), -12})); STAmount{EUR, UINT64_C(920'7893779556188), -13},
// 86.0246GBP is swapped in for 79.2106EUR amm1.tokens()));
BEAST_EXPECT(amm1.expectBalances( // 25% on 63.3684EUR is paid in tr fee 63.3684*1.25 = 79.2106EUR
STAmount{GBP, UINT64_C(1'086'024691358025), -12}, // 63.3684EUR is swapped in for 83.4291USD
STAmount{EUR, UINT64_C(920'78937795562), -11}, BEAST_EXPECT(amm2.expectBalances(
amm1.tokens())); STAmount{EUR, UINT64_C(1'063'368497635505), -12},
// 25% on 63.3684EUR is paid in tr fee 63.3684*1.25 = 79.2106EUR STAmount{USD, UINT64_C(1'316'570881226053), -12},
// 63.3684EUR is swapped in for 83.4291USD amm2.tokens()));
BEAST_EXPECT(amm2.expectBalances(
STAmount{EUR, UINT64_C(1'063'368497635504), -12},
STAmount{USD, UINT64_C(1'316'570881226053), -12},
amm2.tokens()));
}
else
{
// alice buys 53.3322EUR with 107.5308GBP
// 25% on 86.0246GBP is paid in tr fee
// 1,400 - 86.0246*1.25 = 1400 - 107.5308 = 1229.4691GBP
BEAST_EXPECT(expectLine(
env,
alice,
STAmount{GBP, UINT64_C(1'292'469135802466), -12}));
// 86.0246GBP is swapped in for 79.2106EUR
BEAST_EXPECT(amm1.expectBalances(
STAmount{GBP, UINT64_C(1'086'024691358027), -12},
STAmount{EUR, UINT64_C(920'7893779556188), -13},
amm1.tokens()));
// 25% on 63.3684EUR is paid in tr fee 63.3684*1.25 = 79.2106EUR
// 63.3684EUR is swapped in for 83.4291USD
BEAST_EXPECT(amm2.expectBalances(
STAmount{EUR, UINT64_C(1'063'368497635505), -12},
STAmount{USD, UINT64_C(1'316'570881226053), -12},
amm2.tokens()));
}
// 25% on 66.7432USD is paid in tr fee 66.7432*1.25 = 83.4291USD // 25% on 66.7432USD is paid in tr fee 66.7432*1.25 = 83.4291USD
BEAST_EXPECT(expectLine( BEAST_EXPECT(expectLine(
env, carol, STAmount(USD, UINT64_C(1'466'743295019157), -12))); env, carol, STAmount(USD, UINT64_C(1'466'743295019157), -12)));
@@ -2999,34 +2778,18 @@ private:
txflags(tfNoRippleDirect | tfPartialPayment | tfLimitQuality)); txflags(tfNoRippleDirect | tfPartialPayment | tfLimitQuality));
env.close(); env.close();
if (!features[fixAMMv1_1]) // 108.1481GBP is swapped in for 97.5935EUR
{ BEAST_EXPECT(amm1.expectBalances(
// 108.1481GBP is swapped in for 97.5935EUR STAmount{GBP, UINT64_C(1'108'148148148151), -12},
BEAST_EXPECT(amm1.expectBalances( STAmount{EUR, UINT64_C(902'4064171122975), -13},
STAmount{GBP, UINT64_C(1'108'148148148149), -12}, amm1.tokens()));
STAmount{EUR, UINT64_C(902'4064171122988), -13}, // 25% on 78.0748EUR is paid in tr fee 78.0748*1.25 = 97.5935EUR
amm1.tokens())); // 78.0748EUR is swapped in for 101.3888USD
// 25% on 78.0748EUR is paid in tr fee 78.0748*1.25 = 97.5935EUR BEAST_EXPECT(amm2.expectBalances(
// 78.0748EUR is swapped in for 101.3888USD STAmount{EUR, UINT64_C(1'078'074866310162), -12},
BEAST_EXPECT(amm2.expectBalances( STAmount{USD, UINT64_C(1'298'611111111111), -12},
STAmount{EUR, UINT64_C(1'078'074866310161), -12}, amm2.tokens()));
STAmount{USD, UINT64_C(1'298'611111111111), -12},
amm2.tokens()));
}
else
{
// 108.1481GBP is swapped in for 97.5935EUR
BEAST_EXPECT(amm1.expectBalances(
STAmount{GBP, UINT64_C(1'108'148148148151), -12},
STAmount{EUR, UINT64_C(902'4064171122975), -13},
amm1.tokens()));
// 25% on 78.0748EUR is paid in tr fee 78.0748*1.25 = 97.5935EUR
// 78.0748EUR is swapped in for 101.3888USD
BEAST_EXPECT(amm2.expectBalances(
STAmount{EUR, UINT64_C(1'078'074866310162), -12},
STAmount{USD, UINT64_C(1'298'611111111111), -12},
amm2.tokens()));
}
// 25% on 81.1111USD is paid in tr fee 81.1111*1.25 = 101.3888USD // 25% on 81.1111USD is paid in tr fee 81.1111*1.25 = 101.3888USD
BEAST_EXPECT(expectLine( BEAST_EXPECT(expectLine(
env, carol, STAmount{USD, UINT64_C(1'481'111111111111), -12})); env, carol, STAmount{USD, UINT64_C(1'481'111111111111), -12}));
@@ -3172,12 +2935,8 @@ private:
// Alice offers to buy 1000 XRP for 1000 USD. She takes Bob's first // Alice offers to buy 1000 XRP for 1000 USD. She takes Bob's first
// offer, removes 999 more as unfunded, then hits the step limit. // offer, removes 999 more as unfunded, then hits the step limit.
env(offer(alice, USD(1'000), XRP(1'000))); env(offer(alice, USD(1'000), XRP(1'000)));
if (!features[fixAMMv1_1]) env.require(
env.require(balance( balance(alice, STAmount{USD, UINT64_C(2'050125257867587), -15}));
alice, STAmount{USD, UINT64_C(2'050126257867561), -15}));
else
env.require(balance(
alice, STAmount{USD, UINT64_C(2'050125257867587), -15}));
env.require(owners(alice, 2)); env.require(owners(alice, 2));
env.require(balance(bob, USD(0))); env.require(balance(bob, USD(0)));
env.require(owners(bob, 1'001)); env.require(owners(bob, 1'001));
@@ -3283,33 +3042,19 @@ private:
env(offer(bob, XRP(100), USD(100))); env(offer(bob, XRP(100), USD(100)));
env(offer(bob, XRP(1'000), USD(100))); env(offer(bob, XRP(1'000), USD(100)));
AMM ammDan(env, dan, XRP(1'000), USD(1'100)); AMM ammDan(env, dan, XRP(1'000), USD(1'100));
if (!features[fixAMMv1_1])
{ env(pay(alice, carol, USD(10'000)),
env(pay(alice, carol, USD(10'000)), paths(XRP),
paths(XRP), delivermin(USD(200)),
delivermin(USD(200)), txflags(tfPartialPayment),
txflags(tfPartialPayment), sendmax(XRPAmount(200'000'001)));
sendmax(XRP(200))); env.require(balance(bob, USD(0)));
env.require(balance(bob, USD(0))); env.require(
env.require(balance(carol, USD(200))); balance(carol, STAmount{USD, UINT64_C(200'00000090909), -11}));
BEAST_EXPECT(ammDan.expectBalances( BEAST_EXPECT(ammDan.expectBalances(
XRP(1'100), USD(1'000), ammDan.tokens())); XRPAmount{1'100'000'001},
} STAmount{USD, UINT64_C(999'99999909091), -11},
else ammDan.tokens()));
{
env(pay(alice, carol, USD(10'000)),
paths(XRP),
delivermin(USD(200)),
txflags(tfPartialPayment),
sendmax(XRPAmount(200'000'001)));
env.require(balance(bob, USD(0)));
env.require(balance(
carol, STAmount{USD, UINT64_C(200'00000090909), -11}));
BEAST_EXPECT(ammDan.expectBalances(
XRPAmount{1'100'000'001},
STAmount{USD, UINT64_C(999'99999909091), -11},
ammDan.tokens()));
}
} }
} }
@@ -4100,9 +3845,7 @@ private:
testBookStep(all); testBookStep(all);
testBookStep(all | ownerPaysFee); testBookStep(all | ownerPaysFee);
testTransferRate(all | ownerPaysFee); testTransferRate(all | ownerPaysFee);
testTransferRate((all - fixAMMv1_1) | ownerPaysFee);
testTransferRateNoOwnerFee(all); testTransferRateNoOwnerFee(all);
testTransferRateNoOwnerFee(all - fixAMMv1_1);
testLimitQuality(); testLimitQuality();
testXRPPathLoop(); testXRPPathLoop();
} }
@@ -4113,7 +3856,6 @@ private:
using namespace jtx; using namespace jtx;
FeatureBitset const all{supported_amendments()}; FeatureBitset const all{supported_amendments()};
testStepLimit(all); testStepLimit(all);
testStepLimit(all - fixAMMv1_1);
} }
void void
@@ -4122,7 +3864,6 @@ private:
using namespace jtx; using namespace jtx;
FeatureBitset const all{supported_amendments()}; FeatureBitset const all{supported_amendments()};
test_convert_all_of_an_asset(all); test_convert_all_of_an_asset(all);
test_convert_all_of_an_asset(all - fixAMMv1_1);
} }
void void

File diff suppressed because it is too large Load Diff

View File

@@ -61,8 +61,7 @@ class LPTokenTransfer_test : public jtx::AMMTest
env.close(); env.close();
// bob can still send lptoken to carol even tho carol's USD is // bob can still send lptoken to carol even tho carol's USD is
// frozen, regardless of whether fixFrozenLPTokenTransfer is enabled or // frozen
// not
// Note: Deep freeze is not considered for LPToken transfer // Note: Deep freeze is not considered for LPToken transfer
env(pay(bob, carol, STAmount{lpIssue, 5})); env(pay(bob, carol, STAmount{lpIssue, 5}));
env.close(); env.close();
@@ -72,16 +71,8 @@ class LPTokenTransfer_test : public jtx::AMMTest
ter(tecNO_PERMISSION)); ter(tecNO_PERMISSION));
env.close(); env.close();
if (features[fixFrozenLPTokenTransfer]) // carol is frozen on USD and therefore can't send lptoken to bob
{ env(pay(carol, bob, STAmount{lpIssue, 5}), ter(tecPATH_DRY));
// carol is frozen on USD and therefore can't send lptoken to bob
env(pay(carol, bob, STAmount{lpIssue, 5}), ter(tecPATH_DRY));
}
else
{
// carol can still send lptoken with frozen USD
env(pay(carol, bob, STAmount{lpIssue, 5}));
}
} }
void void
@@ -119,40 +110,26 @@ class LPTokenTransfer_test : public jtx::AMMTest
env.close(); env.close();
// exercises alice's ability to consume carol's offer to sell lptoken // exercises alice's ability to consume carol's offer to sell lptoken
// when carol's USD is frozen pre/post fixFrozenLPTokenTransfer // when carol's USD is frozen
// amendment
if (features[fixFrozenLPTokenTransfer])
{
// with fixFrozenLPTokenTransfer, alice fails to consume carol's
// offer since carol's USD is frozen
env(pay(alice, bob, STAmount{lpIssue, 10}),
txflags(tfPartialPayment),
sendmax(XRP(10)),
ter(tecPATH_DRY));
env.close();
BEAST_EXPECT(expectOffers(env, carol, 1));
// gateway unfreezes carol's USD // alice fails to consume carol's offer since carol's USD is frozen
env(trust(gw, carol["USD"](1'000'000'000), tfClearFreeze)); env(pay(alice, bob, STAmount{lpIssue, 10}),
env.close(); txflags(tfPartialPayment),
sendmax(XRP(10)),
ter(tecPATH_DRY));
env.close();
BEAST_EXPECT(expectOffers(env, carol, 1));
// alice successfully consumes carol's offer // gateway unfreezes carol's USD
env(pay(alice, bob, STAmount{lpIssue, 10}), env(trust(gw, carol["USD"](1'000'000'000), tfClearFreeze));
txflags(tfPartialPayment), env.close();
sendmax(XRP(10)));
env.close(); // alice successfully consumes carol's offer
BEAST_EXPECT(expectOffers(env, carol, 0)); env(pay(alice, bob, STAmount{lpIssue, 10}),
} txflags(tfPartialPayment),
else sendmax(XRP(10)));
{ env.close();
// without fixFrozenLPTokenTransfer, alice can consume carol's offer BEAST_EXPECT(expectOffers(env, carol, 0));
// even when carol's USD is frozen
env(pay(alice, bob, STAmount{lpIssue, 10}),
txflags(tfPartialPayment),
sendmax(XRP(10)));
env.close();
BEAST_EXPECT(expectOffers(env, carol, 0));
}
// make sure carol's USD is not frozen // make sure carol's USD is not frozen
env(trust(gw, carol["USD"](1'000'000'000), tfClearFreeze)); env(trust(gw, carol["USD"](1'000'000'000), tfClearFreeze));
@@ -205,37 +182,26 @@ class LPTokenTransfer_test : public jtx::AMMTest
env.close(); env.close();
// exercises carol's ability to create a new offer to sell lptoken with // exercises carol's ability to create a new offer to sell lptoken with
// frozen USD, before and after fixFrozenLPTokenTransfer // frozen USD
if (features[fixFrozenLPTokenTransfer])
{
// with fixFrozenLPTokenTransfer, carol can't create an offer to
// sell lptoken when one of the assets is frozen
// carol can't create an offer to sell lptoken // carol can't create an offer to sell lptoken when one of the assets is
env(offer(carol, XRP(10), STAmount{lpIssue, 10}), // frozen
txflags(tfPassive),
ter(tecUNFUNDED_OFFER));
env.close();
BEAST_EXPECT(expectOffers(env, carol, 0));
// gateway unfreezes carol's USD // carol can't create an offer to sell lptoken
env(trust(gw, carol["USD"](1'000'000'000), tfClearFreeze)); env(offer(carol, XRP(10), STAmount{lpIssue, 10}),
env.close(); txflags(tfPassive),
ter(tecUNFUNDED_OFFER));
env.close();
BEAST_EXPECT(expectOffers(env, carol, 0));
// carol can create an offer to sell lptoken after USD is unfrozen // gateway unfreezes carol's USD
env(offer(carol, XRP(10), STAmount{lpIssue, 10}), env(trust(gw, carol["USD"](1'000'000'000), tfClearFreeze));
txflags(tfPassive)); env.close();
env.close();
BEAST_EXPECT(expectOffers(env, carol, 1)); // carol can create an offer to sell lptoken after USD is unfrozen
} env(offer(carol, XRP(10), STAmount{lpIssue, 10}), txflags(tfPassive));
else env.close();
{ BEAST_EXPECT(expectOffers(env, carol, 1));
// without fixFrozenLPTokenTransfer, carol can create an offer
env(offer(carol, XRP(10), STAmount{lpIssue, 10}),
txflags(tfPassive));
env.close();
BEAST_EXPECT(expectOffers(env, carol, 1));
}
// gateway freezes carol's USD // gateway freezes carol's USD
env(trust(gw, carol["USD"](0), tfSetFreeze)); env(trust(gw, carol["USD"](0), tfSetFreeze));
@@ -276,38 +242,22 @@ class LPTokenTransfer_test : public jtx::AMMTest
env.close(); env.close();
// alice creates an offer which exhibits different behavior on offer // alice creates an offer which exhibits different behavior on offer
// crossing depending on if fixFrozenLPTokenTransfer is enabled // crossing depending on
env(offer(alice, STAmount{token1, 100}, STAmount{token2, 100})); env(offer(alice, STAmount{token1, 100}, STAmount{token2, 100}));
env.close(); env.close();
// exercises carol's offer's ability to cross with alice's offer when // exercises carol's offer's ability to cross with alice's offer when
// carol's USD is frozen, before and after fixFrozenLPTokenTransfer // carol's USD is frozen
if (features[fixFrozenLPTokenTransfer])
{ // alice's offer can no longer cross with carol's offer
// with fixFrozenLPTokenTransfer enabled, alice's offer can no BEAST_EXPECT(
// longer cross with carol's offer expectLine(env, alice, STAmount{token1, 10'000'000}) &&
BEAST_EXPECT( expectLine(env, alice, STAmount{token2, 10'000'000}));
expectLine(env, alice, STAmount{token1, 10'000'000}) && BEAST_EXPECT(
expectLine(env, alice, STAmount{token2, 10'000'000})); expectLine(env, carol, STAmount{token2, 10'000'000}) &&
BEAST_EXPECT( expectLine(env, carol, STAmount{token1, 10'000'000}));
expectLine(env, carol, STAmount{token2, 10'000'000}) && BEAST_EXPECT(
expectLine(env, carol, STAmount{token1, 10'000'000})); expectOffers(env, alice, 1) && expectOffers(env, carol, 0));
BEAST_EXPECT(
expectOffers(env, alice, 1) && expectOffers(env, carol, 0));
}
else
{
// alice's offer still crosses with carol's offer despite carol's
// token1 is frozen
BEAST_EXPECT(
expectLine(env, alice, STAmount{token1, 10'000'100}) &&
expectLine(env, alice, STAmount{token2, 9'999'900}));
BEAST_EXPECT(
expectLine(env, carol, STAmount{token2, 10'000'100}) &&
expectLine(env, carol, STAmount{token1, 9'999'900}));
BEAST_EXPECT(
expectOffers(env, alice, 0) && expectOffers(env, carol, 0));
}
} }
void void
@@ -340,12 +290,9 @@ class LPTokenTransfer_test : public jtx::AMMTest
env(check::create(carol, bob, STAmount{lpIssue, 10})); env(check::create(carol, bob, STAmount{lpIssue, 10}));
env.close(); env.close();
// with fixFrozenLPTokenTransfer enabled, bob fails to cash the check // bob fails to cash the check
if (features[fixFrozenLPTokenTransfer]) env(check::cash(bob, carolChkId, STAmount{lpIssue, 10}),
env(check::cash(bob, carolChkId, STAmount{lpIssue, 10}), ter(tecPATH_PARTIAL));
ter(tecPATH_PARTIAL));
else
env(check::cash(bob, carolChkId, STAmount{lpIssue, 10}));
env.close(); env.close();
@@ -398,69 +345,43 @@ class LPTokenTransfer_test : public jtx::AMMTest
// exercises one's ability to transfer NFT using lptoken when one of the // exercises one's ability to transfer NFT using lptoken when one of the
// assets is frozen // assets is frozen
if (features[fixFrozenLPTokenTransfer])
{
// with fixFrozenLPTokenTransfer, freezing USD will prevent buy/sell
// offers with lptokens from being created/accepted
// carol fails to accept bob's offer with lptoken because carol's // Freezing USD will prevent buy/sell
// USD is frozen // offers with lptokens from being created/accepted
env(token::acceptSellOffer(carol, sellOfferIndex),
ter(tecINSUFFICIENT_FUNDS));
env.close();
// gateway unfreezes carol's USD // carol fails to accept bob's offer with lptoken because carol's
env(trust(gw, carol["USD"](1'000'000), tfClearFreeze)); // USD is frozen
env.close(); env(token::acceptSellOffer(carol, sellOfferIndex),
ter(tecINSUFFICIENT_FUNDS));
env.close();
// carol can now accept the offer and own the nft // gateway unfreezes carol's USD
env(token::acceptSellOffer(carol, sellOfferIndex)); env(trust(gw, carol["USD"](1'000'000), tfClearFreeze));
env.close(); env.close();
// gateway freezes bobs's USD // carol can now accept the offer and own the nft
env(trust(gw, bob["USD"](0), tfSetFreeze)); env(token::acceptSellOffer(carol, sellOfferIndex));
env.close(); env.close();
// bob fails to create a buy offer with lptoken for carol's nft // gateway freezes bobs's USD
// since bob's USD is frozen env(trust(gw, bob["USD"](0), tfSetFreeze));
env(token::createOffer(bob, nftID, STAmount{lpIssue, 10}), env.close();
token::owner(carol),
ter(tecUNFUNDED_OFFER));
env.close();
// gateway unfreezes bob's USD // bob fails to create a buy offer with lptoken for carol's nft
env(trust(gw, bob["USD"](1'000'000), tfClearFreeze)); // since bob's USD is frozen
env.close(); env(token::createOffer(bob, nftID, STAmount{lpIssue, 10}),
token::owner(carol),
ter(tecUNFUNDED_OFFER));
env.close();
// bob can now create a buy offer // gateway unfreezes bob's USD
env(token::createOffer(bob, nftID, STAmount{lpIssue, 10}), env(trust(gw, bob["USD"](1'000'000), tfClearFreeze));
token::owner(carol)); env.close();
env.close();
}
else
{
// without fixFrozenLPTokenTransfer, freezing USD will still allow
// buy/sell offers to be created/accepted with lptoken
// carol can still accept bob's offer despite carol's USD is frozen // bob can now create a buy offer
env(token::acceptSellOffer(carol, sellOfferIndex)); env(token::createOffer(bob, nftID, STAmount{lpIssue, 10}),
env.close(); token::owner(carol));
env.close();
// gateway freezes bob's USD
env(trust(gw, bob["USD"](0), tfSetFreeze));
env.close();
// bob creates a buy offer with lptoken despite bob's USD is frozen
uint256 const buyOfferIndex =
keylet::nftoffer(bob, env.seq(bob)).key;
env(token::createOffer(bob, nftID, STAmount{lpIssue, 10}),
token::owner(carol));
env.close();
// carol accepts bob's offer
env(token::acceptBuyOffer(carol, buyOfferIndex));
env.close();
}
} }
public: public:
@@ -469,15 +390,12 @@ public:
{ {
FeatureBitset const all{jtx::supported_amendments()}; FeatureBitset const all{jtx::supported_amendments()};
for (auto const features : {all, all - fixFrozenLPTokenTransfer}) testDirectStep(all);
{ testBookStep(all);
testDirectStep(features); testOfferCreation(all);
testBookStep(features); testOfferCrossing(all);
testOfferCreation(features); testCheck(all);
testOfferCrossing(features); testNFTOffers(all);
testCheck(features);
testNFTOffers(features);
}
} }
}; };

View File

@@ -336,76 +336,6 @@ changeSpotPriceQuality(
Rules const& rules, Rules const& rules,
beast::Journal j) beast::Journal j)
{ {
if (!rules.enabled(fixAMMv1_1))
{
// Finds takerPays (i) and takerGets (o) such that given pool
// composition poolGets(I) and poolPays(O): (O - o) / (I + i) = quality.
// Where takerGets is calculated as the swapAssetIn (see below).
// The above equation produces the quadratic equation:
// i^2*(1-fee) + i*I*(2-fee) + I^2 - I*O/quality,
// which is solved for i, and o is found with swapAssetIn().
auto const f = feeMult(tfee); // 1 - fee
auto const& a = f;
auto const b = pool.in * (1 + f);
Number const c =
pool.in * pool.in - pool.in * pool.out * quality.rate();
if (auto const res = b * b - 4 * a * c; res < 0)
return std::nullopt; // LCOV_EXCL_LINE
else if (auto const nTakerPaysPropose = (-b + root2(res)) / (2 * a);
nTakerPaysPropose > 0)
{
auto const nTakerPays = [&]() {
// The fee might make the AMM offer quality less than CLOB
// quality. Therefore, AMM offer has to satisfy this constraint:
// o / i >= q. Substituting o with swapAssetIn() gives: i <= O /
// q - I / (1 - fee).
auto const nTakerPaysConstraint =
pool.out * quality.rate() - pool.in / f;
if (nTakerPaysPropose > nTakerPaysConstraint)
return nTakerPaysConstraint;
return nTakerPaysPropose;
}();
if (nTakerPays <= 0)
{
JLOG(j.trace())
<< "changeSpotPriceQuality calc failed: "
<< to_string(pool.in) << " " << to_string(pool.out) << " "
<< quality << " " << tfee;
return std::nullopt;
}
auto const takerPays =
toAmount<TIn>(getIssue(pool.in), nTakerPays, Number::upward);
// should not fail
if (auto const amounts =
TAmounts<TIn, TOut>{
takerPays, swapAssetIn(pool, takerPays, tfee)};
Quality{amounts} < quality &&
!withinRelativeDistance(
Quality{amounts}, quality, Number(1, -7)))
{
JLOG(j.error())
<< "changeSpotPriceQuality failed: " << to_string(pool.in)
<< " " << to_string(pool.out) << " " << " " << quality
<< " " << tfee << " " << to_string(amounts.in) << " "
<< to_string(amounts.out);
Throw<std::runtime_error>("changeSpotPriceQuality failed");
}
else
{
JLOG(j.trace())
<< "changeSpotPriceQuality succeeded: "
<< to_string(pool.in) << " " << to_string(pool.out) << " "
<< " " << quality << " " << tfee << " "
<< to_string(amounts.in) << " " << to_string(amounts.out);
return amounts;
}
}
JLOG(j.trace()) << "changeSpotPriceQuality calc failed: "
<< to_string(pool.in) << " " << to_string(pool.out)
<< " " << quality << " " << tfee;
return std::nullopt;
}
// Generate the offer starting with XRP side. Return seated offer amounts // Generate the offer starting with XRP side. Return seated offer amounts
// if the offer can be generated, otherwise nullopt. // if the offer can be generated, otherwise nullopt.
auto const amounts = [&]() { auto const amounts = [&]() {
@@ -467,61 +397,49 @@ swapAssetIn(
TIn const& assetIn, TIn const& assetIn,
std::uint16_t tfee) std::uint16_t tfee)
{ {
if (auto const& rules = getCurrentTransactionRules(); // set rounding to always favor the amm. Clip to zero.
rules && rules->enabled(fixAMMv1_1)) // calculate:
{ // pool.out -
// set rounding to always favor the amm. Clip to zero. // (pool.in * pool.out) / (pool.in + assetIn * feeMult(tfee)),
// calculate: // and explicitly set the rounding modes
// pool.out - // Favoring the amm means we should:
// (pool.in * pool.out) / (pool.in + assetIn * feeMult(tfee)), // minimize:
// and explicitly set the rounding modes // pool.out -
// Favoring the amm means we should: // (pool.in * pool.out) / (pool.in + assetIn * feeMult(tfee)),
// minimize: // maximize:
// pool.out - // (pool.in * pool.out) / (pool.in + assetIn * feeMult(tfee)),
// (pool.in * pool.out) / (pool.in + assetIn * feeMult(tfee)), // (pool.in * pool.out)
// maximize: // minimize:
// (pool.in * pool.out) / (pool.in + assetIn * feeMult(tfee)), // (pool.in + assetIn * feeMult(tfee)),
// (pool.in * pool.out) // minimize:
// minimize: // assetIn * feeMult(tfee)
// (pool.in + assetIn * feeMult(tfee)), // feeMult is: (1-fee), fee is tfee/100000
// minimize: // minimize:
// assetIn * feeMult(tfee) // 1-fee
// feeMult is: (1-fee), fee is tfee/100000 // maximize:
// minimize: // fee
// 1-fee saveNumberRoundMode _{Number::getround()};
// maximize:
// fee
saveNumberRoundMode _{Number::getround()};
Number::setround(Number::upward); Number::setround(Number::upward);
auto const numerator = pool.in * pool.out; auto const numerator = pool.in * pool.out;
auto const fee = getFee(tfee); auto const fee = getFee(tfee);
Number::setround(Number::downward); Number::setround(Number::downward);
auto const denom = pool.in + assetIn * (1 - fee); auto const denom = pool.in + assetIn * (1 - fee);
if (denom.signum() <= 0) if (denom.signum() <= 0)
return toAmount<TOut>(getIssue(pool.out), 0); return toAmount<TOut>(getIssue(pool.out), 0);
Number::setround(Number::upward); Number::setround(Number::upward);
auto const ratio = numerator / denom; auto const ratio = numerator / denom;
Number::setround(Number::downward); Number::setround(Number::downward);
auto const swapOut = pool.out - ratio; auto const swapOut = pool.out - ratio;
if (swapOut.signum() < 0) if (swapOut.signum() < 0)
return toAmount<TOut>(getIssue(pool.out), 0); return toAmount<TOut>(getIssue(pool.out), 0);
return toAmount<TOut>(getIssue(pool.out), swapOut, Number::downward); return toAmount<TOut>(getIssue(pool.out), swapOut, Number::downward);
}
else
{
return toAmount<TOut>(
getIssue(pool.out),
pool.out -
(pool.in * pool.out) / (pool.in + assetIn * feeMult(tfee)),
Number::downward);
}
} }
/** Swap assetOut out of the pool and swap in a proportional amount /** Swap assetOut out of the pool and swap in a proportional amount
@@ -540,61 +458,49 @@ swapAssetOut(
TOut const& assetOut, TOut const& assetOut,
std::uint16_t tfee) std::uint16_t tfee)
{ {
if (auto const& rules = getCurrentTransactionRules(); // set rounding to always favor the amm. Clip to zero.
rules && rules->enabled(fixAMMv1_1)) // calculate:
// ((pool.in * pool.out) / (pool.out - assetOut) - pool.in) /
// (1-tfee/100000)
// maximize:
// ((pool.in * pool.out) / (pool.out - assetOut) - pool.in)
// maximize:
// (pool.in * pool.out) / (pool.out - assetOut)
// maximize:
// (pool.in * pool.out)
// minimize
// (pool.out - assetOut)
// minimize:
// (1-tfee/100000)
// maximize:
// tfee/100000
saveNumberRoundMode _{Number::getround()};
Number::setround(Number::upward);
auto const numerator = pool.in * pool.out;
Number::setround(Number::downward);
auto const denom = pool.out - assetOut;
if (denom.signum() <= 0)
{ {
// set rounding to always favor the amm. Clip to zero. return toMaxAmount<TIn>(getIssue(pool.in));
// calculate:
// ((pool.in * pool.out) / (pool.out - assetOut) - pool.in) /
// (1-tfee/100000)
// maximize:
// ((pool.in * pool.out) / (pool.out - assetOut) - pool.in)
// maximize:
// (pool.in * pool.out) / (pool.out - assetOut)
// maximize:
// (pool.in * pool.out)
// minimize
// (pool.out - assetOut)
// minimize:
// (1-tfee/100000)
// maximize:
// tfee/100000
saveNumberRoundMode _{Number::getround()};
Number::setround(Number::upward);
auto const numerator = pool.in * pool.out;
Number::setround(Number::downward);
auto const denom = pool.out - assetOut;
if (denom.signum() <= 0)
{
return toMaxAmount<TIn>(getIssue(pool.in));
}
Number::setround(Number::upward);
auto const ratio = numerator / denom;
auto const numerator2 = ratio - pool.in;
auto const fee = getFee(tfee);
Number::setround(Number::downward);
auto const feeMult = 1 - fee;
Number::setround(Number::upward);
auto const swapIn = numerator2 / feeMult;
if (swapIn.signum() < 0)
return toAmount<TIn>(getIssue(pool.in), 0);
return toAmount<TIn>(getIssue(pool.in), swapIn, Number::upward);
}
else
{
return toAmount<TIn>(
getIssue(pool.in),
((pool.in * pool.out) / (pool.out - assetOut) - pool.in) /
feeMult(tfee),
Number::upward);
} }
Number::setround(Number::upward);
auto const ratio = numerator / denom;
auto const numerator2 = ratio - pool.in;
auto const fee = getFee(tfee);
Number::setround(Number::downward);
auto const feeMult = 1 - fee;
Number::setround(Number::upward);
auto const swapIn = numerator2 / feeMult;
if (swapIn.signum() < 0)
return toAmount<TIn>(getIssue(pool.in), 0);
return toAmount<TIn>(getIssue(pool.in), swapIn, Number::upward);
} }
/** Return square of n. /** Return square of n.

View File

@@ -165,13 +165,6 @@ adjustAmountsByLPTokens(
if (lpTokensActual < lpTokens) if (lpTokensActual < lpTokens)
{ {
bool const ammRoundingEnabled = [&]() {
if (auto const& rules = getCurrentTransactionRules();
rules && rules->enabled(fixAMMv1_1))
return true;
return false;
}();
// Equal trade // Equal trade
if (amount2) if (amount2)
{ {
@@ -179,14 +172,7 @@ adjustAmountsByLPTokens(
auto const amountActual = toSTAmount(amount.issue(), fr * amount); auto const amountActual = toSTAmount(amount.issue(), fr * amount);
auto const amount2Actual = auto const amount2Actual =
toSTAmount(amount2->issue(), fr * *amount2); toSTAmount(amount2->issue(), fr * *amount2);
if (!ammRoundingEnabled) return std::make_tuple(amountActual, amount2Actual, lpTokensActual);
return std::make_tuple(
amountActual < amount ? amountActual : amount,
amount2Actual < amount2 ? amount2Actual : amount2,
lpTokensActual);
else
return std::make_tuple(
amountActual, amount2Actual, lpTokensActual);
} }
// Single trade // Single trade
@@ -194,19 +180,11 @@ adjustAmountsByLPTokens(
if (isDeposit) if (isDeposit)
return ammAssetIn( return ammAssetIn(
amountBalance, lptAMMBalance, lpTokensActual, tfee); amountBalance, lptAMMBalance, lpTokensActual, tfee);
else if (!ammRoundingEnabled) return withdrawByTokens(
return withdrawByTokens( amountBalance, lptAMMBalance, lpTokensActual, tfee);
amountBalance, lptAMMBalance, lpTokens, tfee);
else
return withdrawByTokens(
amountBalance, lptAMMBalance, lpTokensActual, tfee);
}(); }();
if (!ammRoundingEnabled)
return amountActual < amount return std::make_tuple(amountActual, std::nullopt, lpTokensActual);
? std::make_tuple(amountActual, std::nullopt, lpTokensActual)
: std::make_tuple(amount, std::nullopt, lpTokensActual);
else
return std::make_tuple(amountActual, std::nullopt, lpTokensActual);
} }
XRPL_ASSERT( XRPL_ASSERT(

View File

@@ -118,8 +118,7 @@ ammLPHolds(
{ {
// This function looks similar to `accountHolds`. However, it only checks if // This function looks similar to `accountHolds`. However, it only checks if
// a LPToken holder has enough balance. On the other hand, `accountHolds` // a LPToken holder has enough balance. On the other hand, `accountHolds`
// checks if the underlying assets of LPToken are frozen with the // checks if the underlying assets of LPToken are frozen
// fixFrozenLPTokenTransfer amendment
auto const currency = ammLPTCurrency(cur1, cur2); auto const currency = ammLPTCurrency(cur1, cur2);
STAmount amount; STAmount amount;

View File

@@ -138,13 +138,9 @@ private:
generateFibSeqOffer(TAmounts<TIn, TOut> const& balances) const; generateFibSeqOffer(TAmounts<TIn, TOut> const& balances) const;
/** Generate max offer. /** Generate max offer.
* If `fixAMMOverflowOffer` is active, the offer is generated as: * The offer is generated as:
* takerGets = 99% * balances.out takerPays = swapOut(takerGets). * takerGets = 99% * balances.out takerPays = swapOut(takerGets).
* Return nullopt if takerGets is 0 or takerGets == balances.out. * Return nullopt if takerGets is 0 or takerGets == balances.out.
*
* If `fixAMMOverflowOffer` is not active, the offer is generated as:
* takerPays = max input amount;
* takerGets = swapIn(takerPays).
*/ */
std::optional<AMMOffer<TIn, TOut>> std::optional<AMMOffer<TIn, TOut>>
maxOffer(TAmounts<TIn, TOut> const& balances, Rules const& rules) const; maxOffer(TAmounts<TIn, TOut> const& balances, Rules const& rules) const;

View File

@@ -123,26 +123,14 @@ AMMLiquidity<TIn, TOut>::maxOffer(
TAmounts<TIn, TOut> const& balances, TAmounts<TIn, TOut> const& balances,
Rules const& rules) const Rules const& rules) const
{ {
if (!rules.enabled(fixAMMOverflowOffer)) auto const out = maxOut<TOut>(balances.out, issueOut());
{ if (out <= TOut{0} || out >= balances.out)
return AMMOffer<TIn, TOut>( return std::nullopt;
*this, return AMMOffer<TIn, TOut>(
{maxAmount<TIn>(), *this,
swapAssetIn(balances, maxAmount<TIn>(), tradingFee_)}, {swapAssetOut(balances, out, tradingFee_), out},
balances, balances,
Quality{balances}); Quality{balances});
}
else
{
auto const out = maxOut<TOut>(balances.out, issueOut());
if (out <= TOut{0} || out >= balances.out)
return std::nullopt;
return AMMOffer<TIn, TOut>(
*this,
{swapAssetOut(balances, out, tradingFee_), out},
balances,
Quality{balances});
}
} }
template <typename TIn, typename TOut> template <typename TIn, typename TOut>
@@ -213,22 +201,13 @@ AMMLiquidity<TIn, TOut>::getOffer(
return AMMOffer<TIn, TOut>( return AMMOffer<TIn, TOut>(
*this, *amounts, balances, Quality{*amounts}); *this, *amounts, balances, Quality{*amounts});
} }
else if (view.rules().enabled(fixAMMv1_2)) else if (auto const maxAMMOffer = maxOffer(balances, view.rules());
maxAMMOffer &&
Quality{maxAMMOffer->amount()} > *clobQuality)
{ {
if (auto const maxAMMOffer = maxOffer(balances, view.rules()); return maxAMMOffer;
maxAMMOffer &&
Quality{maxAMMOffer->amount()} > *clobQuality)
return maxAMMOffer;
} }
} }
catch (std::overflow_error const& e)
{
JLOG(j_.error()) << "AMMLiquidity::getOffer overflow " << e.what();
if (!view.rules().enabled(fixAMMOverflowOffer))
return maxOffer(balances, view.rules());
else
return std::nullopt;
}
catch (std::exception const& e) catch (std::exception const& e)
{ {
JLOG(j_.error()) << "AMMLiquidity::getOffer exception " << e.what(); JLOG(j_.error()) << "AMMLiquidity::getOffer exception " << e.what();

View File

@@ -533,10 +533,7 @@ public:
// Single path AMM offer has to factor in the transfer in rate // Single path AMM offer has to factor in the transfer in rate
// when calculating the upper bound quality and the quality function // when calculating the upper bound quality and the quality function
// because single path AMM's offer quality is not constant. // because single path AMM's offer quality is not constant.
if (!rules.enabled(fixAMMv1_1)) if (offerType == OfferType::CLOB ||
return ofrQ;
else if (
offerType == OfferType::CLOB ||
(this->ammLiquidity_ && this->ammLiquidity_->multiPath())) (this->ammLiquidity_ && this->ammLiquidity_->multiPath()))
return ofrQ; return ofrQ;
@@ -838,7 +835,7 @@ BookStep<TIn, TOut, TDerived>::forEachOffer(
// If offer crossing then use either LOB quality or nullopt // If offer crossing then use either LOB quality or nullopt
// to prevent AMM being blocked by a lower quality LOB. // to prevent AMM being blocked by a lower quality LOB.
auto const qualityThreshold = [&]() -> std::optional<Quality> { auto const qualityThreshold = [&]() -> std::optional<Quality> {
if (sb.rules().enabled(fixAMMv1_1) && lobQuality) if (lobQuality)
return static_cast<TDerived const*>(this)->qualityThreshold( return static_cast<TDerived const*>(this)->qualityThreshold(
*lobQuality); *lobQuality);
return lobQuality; return lobQuality;
@@ -881,11 +878,8 @@ BookStep<TIn, TOut, TDerived>::consumeOffer(
{ {
// purposely written as separate if statements so we get logging even // purposely written as separate if statements so we get logging even
// when the amendment isn't active. // when the amendment isn't active.
if (sb.rules().enabled(fixAMMOverflowOffer)) Throw<FlowException>(
{ tecINVARIANT_FAILED, "AMM pool product invariant failed.");
Throw<FlowException>(
tecINVARIANT_FAILED, "AMM pool product invariant failed.");
}
} }
// The offer owner gets the ofrAmt. The difference between ofrAmt and // The offer owner gets the ofrAmt. The difference between ofrAmt and
@@ -955,7 +949,7 @@ BookStep<TIn, TOut, TDerived>::tip(ReadView const& view) const
// as the result a LOB offer is partially crossed, and it might take a few // as the result a LOB offer is partially crossed, and it might take a few
// iterations to fully cross the offer. // iterations to fully cross the offer.
auto const qualityThreshold = [&]() -> std::optional<Quality> { auto const qualityThreshold = [&]() -> std::optional<Quality> {
if (view.rules().enabled(fixAMMv1_1) && lobQuality) if (lobQuality)
return static_cast<TDerived const*>(this)->qualityThreshold( return static_cast<TDerived const*>(this)->qualityThreshold(
*lobQuality); *lobQuality);
return std::nullopt; return std::nullopt;

View File

@@ -61,23 +61,20 @@ checkFreeze(
} }
} }
if (view.rules().enabled(fixFrozenLPTokenTransfer)) if (auto const sleDst = view.read(keylet::account(dst));
sleDst && sleDst->isFieldPresent(sfAMMID))
{ {
if (auto const sleDst = view.read(keylet::account(dst)); auto const sleAmm = view.read(keylet::amm((*sleDst)[sfAMMID]));
sleDst && sleDst->isFieldPresent(sfAMMID)) if (!sleAmm)
{ return tecINTERNAL; // LCOV_EXCL_LINE
auto const sleAmm = view.read(keylet::amm((*sleDst)[sfAMMID]));
if (!sleAmm)
return tecINTERNAL; // LCOV_EXCL_LINE
if (isLPTokenFrozen( if (isLPTokenFrozen(
view, view,
src, src,
(*sleAmm)[sfAsset].get<Issue>(), (*sleAmm)[sfAsset].get<Issue>(),
(*sleAmm)[sfAsset2].get<Issue>())) (*sleAmm)[sfAsset2].get<Issue>()))
{ {
return terNO_LINE; return terNO_LINE;
}
} }
} }

View File

@@ -311,26 +311,24 @@ AMMWithdraw::applyGuts(Sandbox& sb)
// Due to rounding, the LPTokenBalance of the last LP // Due to rounding, the LPTokenBalance of the last LP
// might not match the LP's trustline balance // might not match the LP's trustline balance
if (sb.rules().enabled(fixAMMv1_1))
if (auto const res =
isOnlyLiquidityProvider(sb, lpTokens.issue(), account_);
!res)
return {res.error(), false};
else if (res.value())
{ {
if (auto const res = if (withinRelativeDistance(
isOnlyLiquidityProvider(sb, lpTokens.issue(), account_); lpTokens,
!res) ammSle->getFieldAmount(sfLPTokenBalance),
return {res.error(), false}; Number{1, -3}))
else if (res.value())
{ {
if (withinRelativeDistance( ammSle->setFieldAmount(sfLPTokenBalance, lpTokens);
lpTokens, sb.update(ammSle);
ammSle->getFieldAmount(sfLPTokenBalance), }
Number{1, -3})) else
{ {
ammSle->setFieldAmount(sfLPTokenBalance, lpTokens); return {tecAMM_INVALID_TOKENS, false};
sb.update(ammSle);
}
else
{
return {tecAMM_INVALID_TOKENS, false};
}
} }
} }
@@ -542,8 +540,7 @@ AMMWithdraw::withdraw(
// Should not happen since the only LP on last withdraw // Should not happen since the only LP on last withdraw
// has the balance set to the lp token trustline balance. // has the balance set to the lp token trustline balance.
if (view.rules().enabled(fixAMMv1_1) && if (lpTokensWithdrawActual > lpTokensAMMBalance)
lpTokensWithdrawActual > lpTokensAMMBalance)
{ {
// LCOV_EXCL_START // LCOV_EXCL_START
JLOG(journal.debug()) JLOG(journal.debug())
@@ -597,9 +594,8 @@ AMMWithdraw::withdraw(
} }
// Check the reserve in case a trustline has to be created // Check the reserve in case a trustline has to be created
bool const enabledFixAMMv1_2 = view.rules().enabled(fixAMMv1_2);
auto sufficientReserve = [&](Issue const& issue) -> TER { auto sufficientReserve = [&](Issue const& issue) -> TER {
if (!enabledFixAMMv1_2 || isXRP(issue)) if (isXRP(issue))
return tesSUCCESS; return tesSUCCESS;
if (!view.exists(keylet::line(account, issue))) if (!view.exists(keylet::line(account, issue)))
{ {

View File

@@ -335,29 +335,26 @@ accountHolds(
return false; return false;
} }
// when fixFrozenLPTokenTransfer is enabled, if currency is lptoken, // If currency is lptoken, we need to check if the associated assets
// we need to check if the associated assets have been frozen // have been frozen
if (view.rules().enabled(fixFrozenLPTokenTransfer)) auto const sleIssuer = view.read(keylet::account(issuer));
if (!sleIssuer)
{ {
auto const sleIssuer = view.read(keylet::account(issuer)); return false; // LCOV_EXCL_LINE
if (!sleIssuer) }
{ else if (sleIssuer->isFieldPresent(sfAMMID))
return false; // LCOV_EXCL_LINE {
} auto const sleAmm =
else if (sleIssuer->isFieldPresent(sfAMMID)) view.read(keylet::amm((*sleIssuer)[sfAMMID]));
{
auto const sleAmm =
view.read(keylet::amm((*sleIssuer)[sfAMMID]));
if (!sleAmm || if (!sleAmm ||
isLPTokenFrozen( isLPTokenFrozen(
view, view,
account, account,
(*sleAmm)[sfAsset].get<Issue>(), (*sleAmm)[sfAsset].get<Issue>(),
(*sleAmm)[sfAsset2].get<Issue>())) (*sleAmm)[sfAsset2].get<Issue>()))
{ {
return false; return false;
}
} }
} }
} }
@@ -1443,18 +1440,9 @@ accountSendIOU(
WaiveTransferFee waiveFee, WaiveTransferFee waiveFee,
bool const senderPaysXferFees) bool const senderPaysXferFees)
{ {
if (view.rules().enabled(fixAMMv1_1)) if (saAmount < beast::zero || saAmount.holds<MPTIssue>())
{ {
if (saAmount < beast::zero || saAmount.holds<MPTIssue>()) return tecINTERNAL;
{
return tecINTERNAL;
}
}
else
{
XRPL_ASSERT(
saAmount >= beast::zero && !saAmount.holds<MPTIssue>(),
"ripple::accountSendIOU : minimum amount and not MPT");
} }
/* If we aren't sending anything or if the sender is the same as the /* If we aren't sending anything or if the sender is the same as the