Compare commits

...

22 Commits

Author SHA1 Message Date
Richard Holland
f4ff7bd4de Merge branch 'dev' into fixImportIssuer 2026-04-29 10:43:25 +10:00
tequ
b79fde27f1 Merge branch 'dev' into fixImportIssuer 2026-04-29 08:53:20 +09:00
tequ
46fb152b12 Merge branch 'dev' into fixImportIssuer 2026-04-28 20:52:54 +09:00
tequ
ce0ae8294e Merge branch 'dev' into fixImportIssuer 2026-04-24 14:09:48 +09:00
Alloy Networks
cd00ed72d8 change build instructions url 2026-04-24 11:12:28 +10:00
tequ
05a3e04f2d Fix BEAST_ENHANCED_LOGGING not working and restore original behavior 2026-04-24 11:11:40 +10:00
tequ
66f7294120 Test: hint build_test_hooks.sh when hook wasm is empty in hso() 2026-04-24 11:10:46 +10:00
Nicholas Dudfield
7f6ac75617 Revert "chore: use improved levelization script with threading and argparse"
This reverts commit 5c1d7d9ae9.
2026-04-24 11:09:19 +10:00
Nicholas Dudfield
4150f0383c chore: use improved levelization script with threading and argparse 2026-04-24 11:09:19 +10:00
Nicholas Dudfield
25123b370a chore: replace levelization shell script with python
Backport of XRPLF/rippled#6325. The python version runs ~80x faster.
2026-04-24 11:09:19 +10:00
tequ
f90ed41802 enable ccache direct_mode 2026-04-24 11:06:51 +10:00
tequ
8c4c158d3a output ccache configuration in release-builder 2026-04-24 11:06:51 +10:00
tequ
2d2951875d fix: typo SignersListSet 2026-04-24 11:05:20 +10:00
tequ
9bfca63574 Update util_keylet fee test 2026-04-24 11:00:31 +10:00
tequ
1ba444ae7f Updated tests to align with the changes merged into the dev branch. 2026-04-24 11:00:31 +10:00
tequ
f96d9b6e51 Add tests for Hooks fee 2026-04-24 11:00:31 +10:00
tequ
9f33cad6db Disallow setting a AMM account as Issuer/Destination/Inform 2026-03-10 15:35:18 +09:00
tequ
8cfee6c8a3 Merge fixAMMClawbackRounding amendment into featureAMMClawback amendment 2026-02-25 19:07:45 +10:00
yinyiqian1
8673599d2b fixAMMClawbackRounding: adjust last holder's LPToken balance (#5513)
Due to rounding, the LPTokenBalance of the last LP might not match the LP's trustline balance. This was fixed for `AMMWithdraw` in `fixAMMv1_1` by adjusting the LPTokenBalance to be the same as the trustline balance. Since `AMMClawback` is also performing a withdrawal, we need to adjust LPTokenBalance as well in `AMMClawback.`

This change includes:
1. Refactored `verifyAndAdjustLPTokenBalance` function in `AMMUtils`, which both`AMMWithdraw` and `AMMClawback` call to adjust LPTokenBalance.
2. Added the unit test `testLastHolderLPTokenBalance` to test the scenario.
3. Modify the existing unit tests for `fixAMMClawbackRounding`.
2026-02-25 19:07:45 +10:00
tequ
ec65e622aa Merge fixAMMv1_3 amendment into featureAMM amendment 2026-02-25 16:20:43 +10:00
Gregory Tsipenyuk
65837f49e1 fix: Add AMMv1_3 amendment (#5203)
* Add AMM bid/create/deposit/swap/withdraw/vote invariants:
  - Deposit, Withdrawal invariants: `sqrt(asset1Balance * asset2Balance) >= LPTokens`.
  - Bid: `sqrt(asset1Balance * asset2Balance) > LPTokens` and the pool balances don't change.
  - Create: `sqrt(asset1Balance * assetBalance2) == LPTokens`.
  - Swap: `asset1BalanceAfter * asset2BalanceAfter >= asset1BalanceBefore * asset2BalanceBefore`
     and `LPTokens` don't change.
  - Vote: `LPTokens` and pool balances don't change.
  - All AMM and swap transactions: amounts and tokens are greater than zero, except on withdrawal if all tokens
    are withdrawn.
* Add AMM deposit and withdraw rounding to ensure AMM invariant:
  - On deposit, tokens out are rounded downward and deposit amount is rounded upward.
  - On withdrawal, tokens in are rounded upward and withdrawal amount is rounded downward.
* Add Order Book Offer invariant to verify consumed amounts. Consumed amounts are less than the offer.
* Fix Bid validation. `AuthAccount` can't have duplicate accounts or the submitter account.
2026-02-25 16:20:43 +10:00
RichardAH
e5b21f026e Merge pull request #692 from Xahau/sync-2.4.0-rebased
Sync: Ripple(d) 2.4.0
2026-02-24 16:07:09 +10:00
16 changed files with 191 additions and 14 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 = 113; static constexpr std::size_t numFeatures = 114;
/** 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

@@ -31,6 +31,7 @@
// 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 (ImportIssuer, Supported::yes, VoteBehavior::DefaultYes)
XRPL_FEATURE(HookAPISerializedType240, Supported::yes, VoteBehavior::DefaultNo) XRPL_FEATURE(HookAPISerializedType240, Supported::yes, VoteBehavior::DefaultNo)
XRPL_FEATURE(PermissionedDomains, Supported::no, VoteBehavior::DefaultNo) XRPL_FEATURE(PermissionedDomains, Supported::no, VoteBehavior::DefaultNo)
XRPL_FEATURE(DynamicNFT, Supported::no, VoteBehavior::DefaultNo) XRPL_FEATURE(DynamicNFT, Supported::no, VoteBehavior::DefaultNo)

View File

@@ -18,6 +18,7 @@
//============================================================================== //==============================================================================
#include <test/jtx.h> #include <test/jtx.h>
#include <test/jtx/AMM.h>
#include <xrpld/app/ledger/LedgerMaster.h> #include <xrpld/app/ledger/LedgerMaster.h>
#include <xrpl/protocol/Feature.h> #include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/jss.h> #include <xrpl/protocol/jss.h>
@@ -300,6 +301,28 @@ struct ClaimReward_test : public beast::unit_test::suite
env(tx, reward::issuer(issuer), ter(tecNO_ISSUER)); env(tx, reward::issuer(issuer), ter(tecNO_ISSUER));
env.close(); env.close();
} }
// tecNO_PERMISSION
// issuer is an AMM account
{
test::jtx::Env env{*this, network::makeNetworkConfig(21337)};
auto const alice = Account("alice");
auto const issuer = Account("issuer");
auto const USD = issuer["USD"];
env.fund(XRP(1000), alice, issuer);
env.close();
AMM amm(env, issuer, XRP(100), USD(100));
BEAST_EXPECT(amm.ammExists());
env(reward::claim(alice),
reward::issuer(amm.ammAccount()),
ter(tecNO_PERMISSION));
env.close();
}
} }
void void

View File

@@ -19,6 +19,7 @@
#include <test/app/Import_json.h> #include <test/app/Import_json.h>
#include <test/jtx.h> #include <test/jtx.h>
#include <test/jtx/AMM.h>
#include <xrpld/app/ledger/LedgerMaster.h> #include <xrpld/app/ledger/LedgerMaster.h>
#include <xrpld/app/misc/AmendmentTable.h> #include <xrpld/app/misc/AmendmentTable.h>
#include <xrpld/app/misc/HashRouter.h> #include <xrpld/app/misc/HashRouter.h>
@@ -2635,6 +2636,77 @@ class Import_test : public beast::unit_test::suite
ter(temDISABLED)); ter(temDISABLED));
} }
// tecNO_ISSUER, tecNO_PERMISSION
// issuer not found, issuer is an AMM account
{
auto const alice = Account("alice");
auto const bob = Account("bob");
auto const issuer = Account("issuer");
auto const USD = issuer["USD"];
for (bool const withFixImportIssuer : {true, false})
{
auto const amend =
withFixImportIssuer ? features : features - fixImportIssuer;
test::jtx::Env env{
*this, network::makeNetworkVLConfig(21337, keys), amend};
env.fund(XRP(1000), alice, issuer);
env.close();
// burn 10'000 xrp
auto const master = Account("masterpassphrase");
env(noop(master), fee(10'000'000'000), ter(tesSUCCESS));
env.close();
env(import::import(
alice, import::loadXpop(ImportTCAccountSet::w_seed)),
import::issuer(bob),
fee(100'000'000),
withFixImportIssuer ? ter(tecNO_ISSUER) : ter(tesSUCCESS));
env.close();
}
for (bool const withFixImportIssuer : {true, false})
{
auto const amend =
withFixImportIssuer ? features : features - fixImportIssuer;
test::jtx::Env env{
*this, network::makeNetworkVLConfig(21337, keys), amend};
env.fund(XRP(1000), alice, issuer);
env.close();
// burn 10'000 xrp
auto const master = Account("masterpassphrase");
env(noop(master), fee(10'000'000'000), ter(tesSUCCESS));
env.close();
AMM amm(env, issuer, XRP(100), USD(100));
BEAST_EXPECT(amm.ammExists());
env(import::import(
alice, import::loadXpop(ImportTCAccountSet::w_seed)),
import::issuer(amm.ammAccount()),
fee(100'000'000),
withFixImportIssuer ? ter(tecNO_PERMISSION)
: ter(tesSUCCESS));
env.close();
}
// env.enableFeature(fixImportIssuer);
// env.close();
// env(import::import(
// carol, import::loadXpop(ImportTCAccountSet::w_seed)),
// import::issuer(bob),
// ter(tecNO_ISSUER));
// env.close();
// env(import::import(
// dave, import::loadXpop(ImportTCAccountSet::w_seed)),
// import::issuer(amm.ammAccount()),
// // fee(100'000'000),
// ter(tecNO_PERMISSION));
// env.close();
}
// tefINTERNAL // tefINTERNAL
// during preclaim could not parse xpop, bailing. // during preclaim could not parse xpop, bailing.
{ {

View File

@@ -18,6 +18,7 @@
//============================================================================== //==============================================================================
#include <test/jtx.h> #include <test/jtx.h>
#include <test/jtx/AMM.h>
#include <xrpl/protocol/jss.h> #include <xrpl/protocol/jss.h>
namespace ripple { namespace ripple {
@@ -168,6 +169,27 @@ class Invoke_test : public beast::unit_test::suite
fee(feeDrops), fee(feeDrops),
ter(tecNO_TARGET)); ter(tecNO_TARGET));
} }
// tecNO_PERMISSION
// issuer is an AMM account
{
test::jtx::Env env{*this, network::makeNetworkConfig(21337)};
auto const alice = Account("alice");
auto const issuer = Account("issuer");
auto const USD = issuer["USD"];
env.fund(XRP(1000), alice, issuer);
env.close();
AMM amm(env, issuer, XRP(100), USD(100));
BEAST_EXPECT(amm.ammExists());
env(invoke::invoke(alice),
invoke::dest(amm.ammAccount()),
ter(tecNO_PERMISSION));
env.close();
}
} }
void void

View File

@@ -18,6 +18,7 @@
//============================================================================== //==============================================================================
#include <test/jtx.h> #include <test/jtx.h>
#include <test/jtx/AMM.h>
#include <xrpld/core/ConfigSections.h> #include <xrpld/core/ConfigSections.h>
#include <xrpld/ledger/Dir.h> #include <xrpld/ledger/Dir.h>
#include <xrpl/basics/chrono.h> #include <xrpl/basics/chrono.h>
@@ -410,6 +411,21 @@ struct Remit_test : public beast::unit_test::suite
env.close(); env.close();
} }
// tecNO_PERMISSION - inform account is an AMM
{
Env env{*this, features};
env.fund(XRP(1000), gw, alice, bob);
env.close();
AMM amm(env, gw, XRP(100), USD(100));
BEAST_EXPECT(amm.ammExists());
auto tx = remit::remit(alice, bob);
tx[sfInform.jsonName] = to_string(amm.ammAccount());
env(tx, alice, ter(tecNO_PERMISSION));
env.close();
}
// tecNO_PERMISSION - lsfDisallowIncomingRemit // tecNO_PERMISSION - lsfDisallowIncomingRemit
// DA: see testAllowIncoming // DA: see testAllowIncoming

View File

@@ -44,7 +44,7 @@ import(jtx::Account const& account, Json::Value const& xpop)
void void
issuer::operator()(Env& env, JTx& jt) const issuer::operator()(Env& env, JTx& jt) const
{ {
jt.jv[sfIssuer.jsonName] = issuer_.human(); jt.jv[sfIssuer.jsonName] = to_string(issuer_);
} }
Json::Value Json::Value

View File

@@ -66,7 +66,7 @@ blob::operator()(Env& env, JTx& jt) const
void void
dest::operator()(Env& env, JTx& jt) const dest::operator()(Env& env, JTx& jt) const
{ {
jt.jv[sfDestination.jsonName] = dest_.human(); jt.jv[sfDestination.jsonName] = to_string(dest_);
} }
} // namespace invoke } // namespace invoke

View File

@@ -29,18 +29,24 @@ namespace reward {
// Claim a reward. // Claim a reward.
Json::Value Json::Value
claim(jtx::Account const& account) claim(jtx::Account const& account)
{
return claim(account.id());
}
Json::Value
claim(AccountID const& account)
{ {
using namespace jtx; using namespace jtx;
Json::Value jv; Json::Value jv;
jv[jss::TransactionType] = jss::ClaimReward; jv[jss::TransactionType] = jss::ClaimReward;
jv[jss::Account] = account.human(); jv[jss::Account] = to_string(account);
return jv; return jv;
} }
void void
issuer::operator()(Env& env, JTx& jt) const issuer::operator()(Env& env, JTx& jt) const
{ {
jt.jv[sfIssuer.jsonName] = issuer_.human(); jt.jv[sfIssuer.jsonName] = to_string(issuer_);
} }
} // namespace reward } // namespace reward

View File

@@ -37,10 +37,10 @@ import(jtx::Account const& account, Json::Value const& xpop);
class issuer class issuer
{ {
private: private:
jtx::Account issuer_; AccountID issuer_;
public: public:
explicit issuer(jtx::Account const& issuer) : issuer_(issuer) explicit issuer(AccountID const& issuer) : issuer_(issuer)
{ {
} }

View File

@@ -58,10 +58,10 @@ public:
class dest class dest
{ {
private: private:
jtx::Account dest_; AccountID dest_;
public: public:
explicit dest(jtx::Account const& dest) : dest_(dest) explicit dest(AccountID const& dest) : dest_(dest)
{ {
} }

View File

@@ -34,17 +34,24 @@ namespace reward {
Json::Value Json::Value
claim(jtx::Account const& account); claim(jtx::Account const& account);
Json::Value
claim(AccountID const& account);
/** Sets the optional Issuer on a JTx. */ /** Sets the optional Issuer on a JTx. */
class issuer class issuer
{ {
private: private:
jtx::Account issuer_; AccountID issuer_;
public: public:
explicit issuer(jtx::Account const& issuer) : issuer_(issuer) explicit issuer(jtx::Account const& issuer) : issuer_(issuer)
{ {
} }
explicit issuer(AccountID const& issuer) : issuer_(issuer)
{
}
void void
operator()(Env&, JTx& jtx) const; operator()(Env&, JTx& jtx) const;
}; };

View File

@@ -82,8 +82,15 @@ ClaimReward::preclaim(PreclaimContext const& ctx)
if ((issuer && isOptOut) || (!issuer && !isOptOut)) if ((issuer && isOptOut) || (!issuer && !isOptOut))
return temMALFORMED; return temMALFORMED;
if (issuer && !ctx.view.exists(keylet::account(*issuer))) if (issuer)
return tecNO_ISSUER; {
auto const sleIssuer = ctx.view.read(keylet::account(*issuer));
if (!sleIssuer)
return tecNO_ISSUER;
if (sleIssuer->isFieldPresent(sfAMMID))
return tecNO_PERMISSION;
}
return tesSUCCESS; return tesSUCCESS;
} }

View File

@@ -870,6 +870,17 @@ Import::preclaim(PreclaimContext const& ctx)
if (!ctx.tx.isFieldPresent(sfBlob)) if (!ctx.tx.isFieldPresent(sfBlob))
return tefINTERNAL; return tefINTERNAL;
if (ctx.tx.isFieldPresent(sfIssuer) &&
ctx.view.rules().enabled(fixImportIssuer))
{
auto const sleIssuer = ctx.view.read(keylet::account(ctx.tx[sfIssuer]));
if (!sleIssuer)
return tecNO_ISSUER;
if (sleIssuer->isFieldPresent(sfAMMID))
return tecNO_PERMISSION;
}
// parse blob as json // parse blob as json
auto const xpop = syntaxCheckXPOP(ctx.tx.getFieldVL(sfBlob), ctx.j); auto const xpop = syntaxCheckXPOP(ctx.tx.getFieldVL(sfBlob), ctx.j);

View File

@@ -64,8 +64,13 @@ Invoke::preclaim(PreclaimContext const& ctx)
if (ctx.tx.isFieldPresent(sfDestination)) if (ctx.tx.isFieldPresent(sfDestination))
{ {
if (!ctx.view.exists(keylet::account(ctx.tx[sfDestination]))) auto const sleDest =
ctx.view.read(keylet::account(ctx.tx[sfDestination]));
if (!sleDest)
return tecNO_TARGET; return tecNO_TARGET;
if (sleDest->isFieldPresent(sfAMMID))
return tecNO_PERMISSION;
} }
return tesSUCCESS; return tesSUCCESS;

View File

@@ -256,11 +256,18 @@ Remit::doApply()
if (ctx_.tx.isFieldPresent(sfInform)) if (ctx_.tx.isFieldPresent(sfInform))
{ {
auto const informAcc = ctx_.tx.getAccountID(sfInform); auto const informAcc = ctx_.tx.getAccountID(sfInform);
if (!sb.exists(keylet::account(informAcc))) auto const sleInformAcc = sb.read(keylet::account(informAcc));
if (!sleInformAcc)
{ {
JLOG(j.warn()) << "Remit: sfInform account does not exist."; JLOG(j.warn()) << "Remit: sfInform account does not exist.";
return tecNO_TARGET; return tecNO_TARGET;
} }
if (sleInformAcc->isFieldPresent(sfAMMID))
{
JLOG(j.warn()) << "Remit: sfInform account is an AMM.";
return tecNO_PERMISSION;
}
} }
XRPAmount const accountReserve{sb.fees().accountReserve(0)}; XRPAmount const accountReserve{sb.fees().accountReserve(0)};