mirror of
https://github.com/XRPLF/rippled.git
synced 2026-09-30 08:48:08 +00:00
Merge branch 'ripple/se/fees' of https://github.com/XRPLF/rippled into ripple/smart-escrow
This commit is contained in:
@@ -57,6 +57,7 @@
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <functional>
|
||||
#include <initializer_list>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <stdexcept>
|
||||
@@ -85,6 +86,13 @@ private:
|
||||
return jtx::testableAmendments() - featureSingleAssetVault - featureLendingProtocol;
|
||||
}
|
||||
|
||||
// Seed from the local testableAmendments() which strips SAV and Lending.
|
||||
static std::vector<FeatureBitset>
|
||||
amendmentCombinations(std::initializer_list<uint256> features)
|
||||
{
|
||||
return jtx::amendmentCombinations(features, testableAmendments());
|
||||
}
|
||||
|
||||
void
|
||||
testInstanceCreate()
|
||||
{
|
||||
@@ -746,18 +754,19 @@ private:
|
||||
testAMM(
|
||||
[&](AMM& ammAlice, Env& env) {
|
||||
env(fset(gw_, asfGlobalFreeze));
|
||||
if (!features[featureAMMClawback])
|
||||
auto const freezeBlocksAll =
|
||||
features[featureAMMClawback] || features[fixCleanup3_3_0];
|
||||
if (!freezeBlocksAll)
|
||||
{
|
||||
// If the issuer set global freeze, the holder still can
|
||||
// deposit the other non-frozen token when AMMClawback is
|
||||
// not enabled.
|
||||
// deposit the other non-frozen token when neither
|
||||
// AMMClawback nor fixCleanup3_3_0 is enabled.
|
||||
ammAlice.deposit(carol_, XRP(100));
|
||||
}
|
||||
else
|
||||
{
|
||||
// If the issuer set global freeze, the holder cannot
|
||||
// deposit the other non-frozen token when AMMClawback is
|
||||
// enabled.
|
||||
// deposit the other non-frozen token.
|
||||
ammAlice.deposit(
|
||||
carol_, XRP(100), std::nullopt, std::nullopt, std::nullopt, Ter(tecFROZEN));
|
||||
}
|
||||
@@ -786,16 +795,18 @@ private:
|
||||
[&](AMM& ammAlice, Env& env) {
|
||||
env(trust(gw_, carol_["USD"](0), tfSetFreeze));
|
||||
env.close();
|
||||
if (!features[featureAMMClawback])
|
||||
auto const freezeBlocksAll =
|
||||
features[featureAMMClawback] || features[fixCleanup3_3_0];
|
||||
if (!freezeBlocksAll)
|
||||
{
|
||||
// Can deposit non-frozen token if AMMClawback is not
|
||||
// enabled
|
||||
// Can deposit non-frozen token if neither AMMClawback
|
||||
// nor fixCleanup3_3_0 is enabled
|
||||
ammAlice.deposit(carol_, XRP(100));
|
||||
}
|
||||
else
|
||||
{
|
||||
// Cannot deposit non-frozen token if the other token is
|
||||
// frozen when AMMClawback is enabled
|
||||
// frozen
|
||||
ammAlice.deposit(
|
||||
carol_, XRP(100), std::nullopt, std::nullopt, std::nullopt, Ter(tecFROZEN));
|
||||
}
|
||||
@@ -810,8 +821,18 @@ private:
|
||||
STAmount{Issue{gw_["USD"].currency, ammAlice.ammAccount()}, 0},
|
||||
tfSetFreeze));
|
||||
env.close();
|
||||
// Can deposit non-frozen token
|
||||
ammAlice.deposit(carol_, XRP(100));
|
||||
// Post-fixCleanup3_3_0: checkDepositFreeze checks both pool
|
||||
// assets against the AMM account, so depositing the
|
||||
// non-frozen token is also blocked.
|
||||
if (!features[fixCleanup3_3_0])
|
||||
{
|
||||
ammAlice.deposit(carol_, XRP(100));
|
||||
}
|
||||
else
|
||||
{
|
||||
ammAlice.deposit(
|
||||
carol_, XRP(100), std::nullopt, std::nullopt, std::nullopt, Ter(tecFROZEN));
|
||||
}
|
||||
ammAlice.deposit(carol_, 1'000'000, std::nullopt, std::nullopt, Ter(tecFROZEN));
|
||||
ammAlice.deposit(
|
||||
carol_, USD(100), std::nullopt, std::nullopt, std::nullopt, Ter(tecFROZEN));
|
||||
@@ -861,11 +882,10 @@ private:
|
||||
AMM amm(env, alice_, XRP(10), gw_["USD"](10), Ter(tesSUCCESS));
|
||||
env.close();
|
||||
|
||||
if (features[featureAMMClawback])
|
||||
if (features[featureAMMClawback] || features[fixCleanup3_3_0])
|
||||
{
|
||||
// if featureAMMClawback is enabled, bob_ can not deposit XRP
|
||||
// because he's not authorized to hold the paired token
|
||||
// gw_["USD"].
|
||||
// bob_ can not deposit XRP because he's not authorized to
|
||||
// hold the paired token gw_["USD"].
|
||||
amm.deposit(
|
||||
bob_, XRP(10), std::nullopt, std::nullopt, std::nullopt, Ter(tecNO_AUTH));
|
||||
}
|
||||
@@ -1734,36 +1754,57 @@ private:
|
||||
});
|
||||
|
||||
// Globally frozen asset
|
||||
testAMM([&](AMM& ammAlice, Env& env) {
|
||||
ammAlice.deposit({.account = gw_, .asset1In = USD(1'000), .asset2In = XRP(1'000)});
|
||||
env(fset(gw_, asfGlobalFreeze));
|
||||
env.close();
|
||||
// Can withdraw non-frozen token
|
||||
for (auto const& account : {alice_, gw_})
|
||||
{
|
||||
ammAlice.withdraw(account, XRP(100));
|
||||
ammAlice.withdraw(account, USD(100), std::nullopt, std::nullopt, Ter(tecFROZEN));
|
||||
ammAlice.withdraw(account, 1'000, std::nullopt, std::nullopt, Ter(tecFROZEN));
|
||||
}
|
||||
});
|
||||
testAMM(
|
||||
[&](AMM& ammAlice, Env& env) {
|
||||
auto const fix330 = env.current()->rules().enabled(fixCleanup3_3_0);
|
||||
ammAlice.deposit({.account = gw_, .asset1In = USD(1'000), .asset2In = XRP(1'000)});
|
||||
env(fset(gw_, asfGlobalFreeze));
|
||||
env.close();
|
||||
// Can withdraw non-frozen token
|
||||
for (auto const& account : {alice_, gw_})
|
||||
{
|
||||
ammAlice.withdraw(account, XRP(100));
|
||||
// Post-fixCleanup3_3_0 the issuer can withdraw their own
|
||||
// frozen token from the pool.
|
||||
auto const frozenErr =
|
||||
(fix330 && account == gw_) ? Ter(tesSUCCESS) : Ter(tecFROZEN);
|
||||
ammAlice.withdraw(account, USD(100), std::nullopt, std::nullopt, frozenErr);
|
||||
ammAlice.withdraw(account, 1'000, std::nullopt, std::nullopt, frozenErr);
|
||||
}
|
||||
},
|
||||
std::nullopt,
|
||||
0,
|
||||
std::nullopt,
|
||||
amendmentCombinations({fixCleanup3_3_0}));
|
||||
|
||||
// Individually frozen (AMM) account
|
||||
testAMM([&](AMM& ammAlice, Env& env) {
|
||||
env(trust(gw_, alice_["USD"](0), tfSetFreeze));
|
||||
env.close();
|
||||
// Can withdraw non-frozen token
|
||||
ammAlice.withdraw(alice_, XRP(100));
|
||||
ammAlice.withdraw(alice_, 1'000, std::nullopt, std::nullopt, Ter(tecFROZEN));
|
||||
ammAlice.withdraw(alice_, USD(100), std::nullopt, std::nullopt, Ter(tecFROZEN));
|
||||
env(trust(gw_, alice_["USD"](0), tfClearFreeze));
|
||||
// Individually frozen AMM
|
||||
env(trust(
|
||||
gw_, STAmount{Issue{gw_["USD"].currency, ammAlice.ammAccount()}, 0}, tfSetFreeze));
|
||||
// Can withdraw non-frozen token
|
||||
ammAlice.withdraw(alice_, XRP(100));
|
||||
ammAlice.withdraw(alice_, 1'000, std::nullopt, std::nullopt, Ter(tecFROZEN));
|
||||
ammAlice.withdraw(alice_, USD(100), std::nullopt, std::nullopt, Ter(tecFROZEN));
|
||||
});
|
||||
testAMM(
|
||||
[&](AMM& ammAlice, Env& env) {
|
||||
auto const fix330 = env.current()->rules().enabled(fixCleanup3_3_0);
|
||||
env(trust(gw_, alice_["USD"](0), tfSetFreeze));
|
||||
env.close();
|
||||
// Can withdraw non-frozen token
|
||||
ammAlice.withdraw(alice_, XRP(100));
|
||||
// Post-fixCleanup3_3_0 regular freeze no longer blocks
|
||||
// self-withdrawal; only deep freeze does.
|
||||
auto const indivFreezeErr = fix330 ? Ter(tesSUCCESS) : Ter(tecFROZEN);
|
||||
ammAlice.withdraw(alice_, 1'000, std::nullopt, std::nullopt, indivFreezeErr);
|
||||
ammAlice.withdraw(alice_, USD(100), std::nullopt, std::nullopt, indivFreezeErr);
|
||||
env(trust(gw_, alice_["USD"](0), tfClearFreeze));
|
||||
// Individually frozen AMM — still blocked regardless of
|
||||
// fixCleanup3_3_0 because the AMM account itself is frozen.
|
||||
env(trust(
|
||||
gw_,
|
||||
STAmount{Issue{gw_["USD"].currency, ammAlice.ammAccount()}, 0},
|
||||
tfSetFreeze));
|
||||
ammAlice.withdraw(alice_, XRP(100));
|
||||
ammAlice.withdraw(alice_, 1'000, std::nullopt, std::nullopt, Ter(tecFROZEN));
|
||||
ammAlice.withdraw(alice_, USD(100), std::nullopt, std::nullopt, Ter(tecFROZEN));
|
||||
},
|
||||
std::nullopt,
|
||||
0,
|
||||
std::nullopt,
|
||||
amendmentCombinations({fixCleanup3_3_0}));
|
||||
|
||||
// Carol withdraws more than she owns
|
||||
testAMM([&](AMM& ammAlice, Env&) {
|
||||
@@ -1842,8 +1883,18 @@ private:
|
||||
// are rounded to all LP tokens.
|
||||
testAMM(
|
||||
[&](AMM& ammAlice, Env& env) {
|
||||
auto const err =
|
||||
env.enabled(fixAMMv1_3) ? Ter(tecINVARIANT_FAILED) : Ter(tecAMM_BALANCE);
|
||||
// Without fixAMMv1_3: sub-method returns tecAMM_BALANCE early.
|
||||
// With fixAMMv1_3 but without fixCleanup3_3_0: sub-method succeeds
|
||||
// but invariant check catches the precision violation.
|
||||
// With fixCleanup3_3_0: caught in the transaction layer before
|
||||
// the invariant checker runs.
|
||||
auto const err = [&] {
|
||||
if (!env.enabled(fixAMMv1_3))
|
||||
return Ter(tecAMM_BALANCE);
|
||||
if (env.enabled(fixCleanup3_3_0))
|
||||
return Ter(tecPRECISION_LOSS);
|
||||
return Ter(tecINVARIANT_FAILED);
|
||||
}();
|
||||
ammAlice.withdraw(
|
||||
alice_,
|
||||
STAmount{USD, UINT64_C(9'999'999999999999), -12},
|
||||
@@ -1851,7 +1902,7 @@ private:
|
||||
std::nullopt,
|
||||
err);
|
||||
},
|
||||
{.features = {all, all - fixAMMv1_3}, .noLog = true});
|
||||
{.features = {all, all - fixAMMv1_3, all - fixCleanup3_3_0}, .noLog = true});
|
||||
|
||||
// Tiny withdraw
|
||||
testAMM([&](AMM& ammAlice, Env&) {
|
||||
@@ -2229,6 +2280,31 @@ private:
|
||||
ammAlice.withdraw(alice_, XRPAmount{9'999'999'999});
|
||||
BEAST_EXPECT(ammAlice.expectBalances(XRPAmount{1}, USD(10'000), IOUAmount{100}));
|
||||
});
|
||||
|
||||
// singleWithdrawEPrice: crafted ePrice = lptAMMBalance*f/amountBalance
|
||||
// makes the denominator (T*f - A*E) exactly zero.
|
||||
// Pre-fixCleanup3_3_0: std::overflow_error escapes to the
|
||||
// transactor backstop and is returned as tefEXCEPTION.
|
||||
// Post-fixCleanup3_3_0: denominator check returns tecAMM_FAILED.
|
||||
//
|
||||
// Pool: USD(100)/EUR(100), baseFee=1000 (1%).
|
||||
// Alice is the creator so her discounted fee is 100 (0.1%), f=0.001.
|
||||
// ePrice = lptAMMBalance(100) * f(0.001) / amountBalance(100) = 0.001
|
||||
testAMM(
|
||||
[&](AMM& ammAlice, Env& env) {
|
||||
auto const err =
|
||||
env.enabled(fixCleanup3_3_0) ? Ter(tecAMM_FAILED) : Ter(tefEXCEPTION);
|
||||
ammAlice.withdraw(
|
||||
WithdrawArg{
|
||||
.account = alice_,
|
||||
.asset1Out = USD(0),
|
||||
.maxEP = IOUAmount{1, -3}, // ePrice=0.001 → denom=0
|
||||
.err = err});
|
||||
},
|
||||
{{USD(100), EUR(100)}},
|
||||
1000,
|
||||
std::nullopt,
|
||||
{all - fixCleanup3_3_0, all});
|
||||
}
|
||||
|
||||
void
|
||||
@@ -2387,8 +2463,8 @@ private:
|
||||
// The vote is not added to the slots
|
||||
ammAlice.vote(carol_, 1'000);
|
||||
auto const info = ammAlice.ammRpcInfo()[jss::amm][jss::vote_slots];
|
||||
for (std::uint32_t i = 0; i < info.size(); ++i)
|
||||
BEAST_EXPECT(info[i][jss::account] != carol_.human());
|
||||
for (auto const& entry : info)
|
||||
BEAST_EXPECT(entry[jss::account] != carol_.human());
|
||||
// But the slots are refreshed and the fee is changed
|
||||
BEAST_EXPECT(ammAlice.expectTradingFee(82));
|
||||
});
|
||||
@@ -2625,10 +2701,6 @@ private:
|
||||
using namespace jtx;
|
||||
using namespace std::chrono;
|
||||
|
||||
// For now, just disable SAV entirely, which locks in the small Number
|
||||
// mantissas
|
||||
features = features - featureSingleAssetVault - featureLendingProtocol;
|
||||
|
||||
// Auction slot initially is owned by AMM creator, who pays 0 price.
|
||||
|
||||
// Bid 110 tokens. Pay bidMin.
|
||||
@@ -3337,11 +3409,6 @@ private:
|
||||
testcase("Basic Payment");
|
||||
using namespace jtx;
|
||||
|
||||
// For now, just disable SAV entirely, which locks in the small Number
|
||||
// mantissas
|
||||
features =
|
||||
features - featureSingleAssetVault - featureLendingProtocol - featureLendingProtocol;
|
||||
|
||||
// Payment 100USD for 100XRP.
|
||||
// Force one path with tfNoRippleDirect.
|
||||
testAMM(
|
||||
@@ -4342,15 +4409,10 @@ private:
|
||||
testAmendment()
|
||||
{
|
||||
testcase("Amendment");
|
||||
FeatureBitset const all{testableAmendments()};
|
||||
FeatureBitset const noAMM{all - featureAMM};
|
||||
FeatureBitset const noNumber{all - fixUniversalNumber};
|
||||
FeatureBitset const noAMMAndNumber{all - featureAMM - fixUniversalNumber};
|
||||
using namespace jtx;
|
||||
Env env{*this, testableAmendments() - featureAMM};
|
||||
|
||||
for (auto const& feature : {noAMM, noNumber, noAMMAndNumber})
|
||||
{
|
||||
Env env{*this, feature};
|
||||
fund(env, gw_, {alice_}, {USD(1'000)}, Fund::All);
|
||||
AMM amm(env, alice_, XRP(1'000), USD(1'000), Ter(temDISABLED));
|
||||
|
||||
@@ -4374,7 +4436,7 @@ private:
|
||||
auto const info = env.rpc(
|
||||
"json",
|
||||
"account_info",
|
||||
std::string("{\"account\": \"" + to_string(ammAlice.ammAccount()) + "\"}"));
|
||||
std::string(R"({"account": ")" + to_string(ammAlice.ammAccount()) + "\"}"));
|
||||
auto const flags = info[jss::result][jss::account_data][jss::Flags].asUInt();
|
||||
BEAST_EXPECT(flags == (lsfDisableMaster | lsfDefaultRipple | lsfDepositAuth));
|
||||
});
|
||||
@@ -5093,7 +5155,7 @@ private:
|
||||
Env env(
|
||||
*this,
|
||||
envconfig([](std::unique_ptr<Config> cfg) {
|
||||
cfg->FEES.reference_fee = XRPAmount(1);
|
||||
cfg->fees.referenceFee = XRPAmount(1);
|
||||
return cfg;
|
||||
}),
|
||||
all);
|
||||
@@ -5151,7 +5213,7 @@ private:
|
||||
Env env(
|
||||
*this,
|
||||
envconfig([](std::unique_ptr<Config> cfg) {
|
||||
cfg->FEES.reference_fee = XRPAmount(1);
|
||||
cfg->fees.referenceFee = XRPAmount(1);
|
||||
return cfg;
|
||||
}),
|
||||
all);
|
||||
@@ -5203,7 +5265,7 @@ private:
|
||||
auto const info = env.rpc(
|
||||
"json",
|
||||
"account_info",
|
||||
std::string("{\"account\": \"" + to_string(amm.ammAccount()) + "\"}"));
|
||||
std::string(R"({"account": ")" + to_string(amm.ammAccount()) + "\"}"));
|
||||
try
|
||||
{
|
||||
BEAST_EXPECT(
|
||||
@@ -5685,38 +5747,16 @@ private:
|
||||
};
|
||||
|
||||
// ledger is closed after each transaction, vote/withdraw don't fail
|
||||
// regardless whether the amendment is enabled or not
|
||||
test(all, tesSUCCESS, tesSUCCESS, tesSUCCESS, tesSUCCESS, 0, true);
|
||||
test(all - fixInnerObjTemplate, tesSUCCESS, tesSUCCESS, tesSUCCESS, tesSUCCESS, 0, true);
|
||||
// ledger is not closed after each transaction
|
||||
// vote/withdraw don't fail if the amendment is enabled
|
||||
test(all, tesSUCCESS, tesSUCCESS, tesSUCCESS, tesSUCCESS, 0, false);
|
||||
// vote/withdraw fail if the amendment is not enabled
|
||||
// second vote/withdraw still fail: second vote fails because
|
||||
// the initial trading fee is 0, consequently second withdraw fails
|
||||
// because the second vote fails
|
||||
test(
|
||||
all - fixInnerObjTemplate,
|
||||
tefEXCEPTION,
|
||||
tefEXCEPTION,
|
||||
tefEXCEPTION,
|
||||
tefEXCEPTION,
|
||||
0,
|
||||
false);
|
||||
// if non-zero trading/discounted fee then vote/withdraw
|
||||
// don't fail whether the ledger is closed or not and
|
||||
// the amendment is enabled or not
|
||||
// don't fail whether the ledger is closed or not
|
||||
test(all, tesSUCCESS, tesSUCCESS, tesSUCCESS, tesSUCCESS, 10, true);
|
||||
test(all - fixInnerObjTemplate, tesSUCCESS, tesSUCCESS, tesSUCCESS, tesSUCCESS, 10, true);
|
||||
test(all, tesSUCCESS, tesSUCCESS, tesSUCCESS, tesSUCCESS, 10, false);
|
||||
test(all - fixInnerObjTemplate, tesSUCCESS, tesSUCCESS, tesSUCCESS, tesSUCCESS, 10, false);
|
||||
// non-zero trading fee but discounted fee is 0, vote doesn't fail
|
||||
// but withdraw fails
|
||||
test(all, tesSUCCESS, tesSUCCESS, tesSUCCESS, tesSUCCESS, 9, false);
|
||||
// second vote sets the trading fee to non-zero, consequently
|
||||
// second withdraw doesn't fail even if the amendment is not
|
||||
// enabled and the ledger is not closed
|
||||
test(all - fixInnerObjTemplate, tesSUCCESS, tefEXCEPTION, tesSUCCESS, tesSUCCESS, 9, false);
|
||||
}
|
||||
|
||||
void
|
||||
@@ -6638,11 +6678,11 @@ private:
|
||||
});
|
||||
}
|
||||
|
||||
if (features[featureAMMClawback])
|
||||
if (features[featureAMMClawback] || features[fixCleanup3_3_0])
|
||||
{
|
||||
// Deposit one asset which is not the frozen token,
|
||||
// but the other asset is frozen. We should get tecFROZEN error
|
||||
// when feature AMMClawback is enabled.
|
||||
// but the other asset is frozen. tecFROZEN when either
|
||||
// AMMClawback or fixCleanup3_3_0 is enabled.
|
||||
Env env(*this, features);
|
||||
testAMMDeposit(env, [&](AMM& amm) {
|
||||
amm.deposit(
|
||||
@@ -6652,8 +6692,8 @@ private:
|
||||
else
|
||||
{
|
||||
// Deposit one asset which is not the frozen token,
|
||||
// but the other asset is frozen. We will get tecSUCCESS
|
||||
// when feature AMMClawback is not enabled.
|
||||
// but the other asset is frozen. tesSUCCESS only when
|
||||
// neither AMMClawback nor fixCleanup3_3_0 is enabled.
|
||||
Env env(*this, features);
|
||||
testAMMDeposit(env, [&](AMM& amm) {
|
||||
amm.deposit(
|
||||
@@ -7090,7 +7130,7 @@ private:
|
||||
Env env(
|
||||
*this,
|
||||
envconfig([](std::unique_ptr<Config> cfg) {
|
||||
cfg->FEES.reference_fee = XRPAmount(1);
|
||||
cfg->fees.referenceFee = XRPAmount(1);
|
||||
return cfg;
|
||||
}),
|
||||
features);
|
||||
@@ -7176,8 +7216,8 @@ private:
|
||||
FeatureBitset const all{testableAmendments()};
|
||||
testInvalidInstance();
|
||||
testInstanceCreate();
|
||||
testInvalidDeposit(all);
|
||||
testInvalidDeposit(all - featureAMMClawback);
|
||||
for (auto const& f : amendmentCombinations({fixCleanup3_3_0, featureAMMClawback}))
|
||||
testInvalidDeposit(f);
|
||||
testDeposit();
|
||||
testInvalidWithdraw();
|
||||
testWithdraw();
|
||||
@@ -7227,8 +7267,8 @@ private:
|
||||
testAMMClawback(all - featureAMMClawback - featureSingleAssetVault);
|
||||
testAMMClawback(all - featureAMMClawback);
|
||||
testAMMClawback(all - fixAMMv1_1 - fixAMMv1_3 - featureAMMClawback);
|
||||
testAMMDepositWithFrozenAssets(all);
|
||||
testAMMDepositWithFrozenAssets(all - featureAMMClawback);
|
||||
for (auto const& f : amendmentCombinations({fixCleanup3_3_0, featureAMMClawback}))
|
||||
testAMMDepositWithFrozenAssets(f);
|
||||
testAMMDepositWithFrozenAssets(all - fixAMMv1_1 - featureAMMClawback);
|
||||
testAMMDepositWithFrozenAssets(all - fixAMMv1_1 - fixAMMv1_3 - featureAMMClawback);
|
||||
testFixReserveCheckOnWithdrawal(all);
|
||||
|
||||
Reference in New Issue
Block a user