Compare commits

..

19 Commits

Author SHA1 Message Date
Richard Holland
f4ff7bd4de Merge branch 'dev' into fixImportIssuer 2026-04-29 10:43:25 +10:00
tequ
7bf5bcaf20 Add new TSH tests (#704) 2026-04-29 10:40:50 +10:00
Niq Dudfield
9f2160f42e fix: resolve switch fall-through in util_keylet unimplemented cases (#701) 2026-04-29 10:37:17 +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
18 changed files with 1772 additions and 71 deletions

View File

@@ -80,7 +80,7 @@ namespace detail {
// 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
// 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.
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`
// in include/xrpl/protocol/Feature.h.
XRPL_FIX (ImportIssuer, Supported::yes, VoteBehavior::DefaultYes)
XRPL_FEATURE(HookAPISerializedType240, Supported::yes, VoteBehavior::DefaultNo)
XRPL_FEATURE(PermissionedDomains, Supported::no, VoteBehavior::DefaultNo)
XRPL_FEATURE(DynamicNFT, Supported::no, VoteBehavior::DefaultNo)

View File

@@ -18,6 +18,7 @@
//==============================================================================
#include <test/jtx.h>
#include <test/jtx/AMM.h>
#include <xrpld/app/ledger/LedgerMaster.h>
#include <xrpl/protocol/Feature.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.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

View File

@@ -19,6 +19,7 @@
#include <test/app/Import_json.h>
#include <test/jtx.h>
#include <test/jtx/AMM.h>
#include <xrpld/app/ledger/LedgerMaster.h>
#include <xrpld/app/misc/AmendmentTable.h>
#include <xrpld/app/misc/HashRouter.h>
@@ -2635,6 +2636,77 @@ class Import_test : public beast::unit_test::suite
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
// during preclaim could not parse xpop, bailing.
{

View File

@@ -18,6 +18,7 @@
//==============================================================================
#include <test/jtx.h>
#include <test/jtx/AMM.h>
#include <xrpl/protocol/jss.h>
namespace ripple {
@@ -168,6 +169,27 @@ class Invoke_test : public beast::unit_test::suite
fee(feeDrops),
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

View File

@@ -18,6 +18,7 @@
//==============================================================================
#include <test/jtx.h>
#include <test/jtx/AMM.h>
#include <xrpld/core/ConfigSections.h>
#include <xrpld/ledger/Dir.h>
#include <xrpl/basics/chrono.h>
@@ -410,6 +411,21 @@ struct Remit_test : public beast::unit_test::suite
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
// DA: see testAllowIncoming

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

@@ -37,10 +37,10 @@ import(jtx::Account const& account, Json::Value const& xpop);
class issuer
{
private:
jtx::Account issuer_;
AccountID issuer_;
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
{
private:
jtx::Account dest_;
AccountID dest_;
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
claim(jtx::Account const& account);
Json::Value
claim(AccountID const& account);
/** Sets the optional Issuer on a JTx. */
class issuer
{
private:
jtx::Account issuer_;
AccountID issuer_;
public:
explicit issuer(jtx::Account const& issuer) : issuer_(issuer)
{
}
explicit issuer(AccountID const& issuer) : issuer_(issuer)
{
}
void
operator()(Env&, JTx& jtx) const;
};

View File

@@ -301,18 +301,14 @@ getTransactionalStakeHolders(STTx const& tx, ReadView const& rv)
bool issuerCanRollback = nft::getFlags(nid) & tfStrongTSH;
ADD_TSH(issuer, issuerCanRollback);
if (bo)
for (auto const& offer : {bo, so})
{
ADD_TSH(bo->getAccountID(sfOwner), tshSTRONG);
if (bo->isFieldPresent(sfDestination))
ADD_TSH(bo->getAccountID(sfDestination), tshSTRONG);
}
if (so)
{
ADD_TSH(so->getAccountID(sfOwner), tshSTRONG);
if (so->isFieldPresent(sfDestination))
ADD_TSH(so->getAccountID(sfDestination), tshSTRONG);
if (offer)
{
ADD_TSH(offer->getAccountID(sfOwner), tshSTRONG);
if (offer->isFieldPresent(sfDestination))
ADD_TSH(offer->getAccountID(sfDestination), tshSTRONG);
}
}
break;
@@ -551,7 +547,8 @@ getTransactionalStakeHolders(STTx const& tx, ReadView const& rv)
break;
}
case ttLEDGER_STATE_FIX: {
// TODO: Implement if needed
if (tx.isFieldPresent(sfOwner))
ADD_TSH(tx.getAccountID(sfOwner), tshWEAK);
break;
}
case ttMPTOKEN_ISSUANCE_CREATE:

View File

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

View File

@@ -870,6 +870,17 @@ Import::preclaim(PreclaimContext const& ctx)
if (!ctx.tx.isFieldPresent(sfBlob))
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
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.view.exists(keylet::account(ctx.tx[sfDestination])))
auto const sleDest =
ctx.view.read(keylet::account(ctx.tx[sfDestination]));
if (!sleDest)
return tecNO_TARGET;
if (sleDest->isFieldPresent(sfAMMID))
return tecNO_PERMISSION;
}
return tesSUCCESS;

View File

@@ -256,11 +256,18 @@ Remit::doApply()
if (ctx_.tx.isFieldPresent(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.";
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)};