mirror of
https://github.com/XRPLF/rippled.git
synced 2026-06-04 17:27:00 +00:00
Compare commits
5 Commits
ripple/se/
...
dangell7/f
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
298b95d1f1 | ||
|
|
93ac1aa7aa | ||
|
|
d9a3af8207 | ||
|
|
8d1083e5ea | ||
|
|
1e45d363c5 |
4
.github/workflows/reusable-package.yml
vendored
4
.github/workflows/reusable-package.yml
vendored
@@ -58,6 +58,7 @@ jobs:
|
||||
|
||||
package:
|
||||
needs: [generate-matrix, generate-version]
|
||||
if: ${{ github.event.repository.visibility == 'public' }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix: ${{ fromJson(needs.generate-matrix.outputs.matrix) }}
|
||||
@@ -88,8 +89,7 @@ jobs:
|
||||
run: ./package/build_pkg.sh
|
||||
|
||||
- name: Upload package artifact
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||
if: ${{ github.event.repository.visibility == 'public' }}
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: ${{ matrix.artifact_name }}-pkg-${{ needs.generate-version.outputs.version }}
|
||||
path: |
|
||||
|
||||
@@ -1466,10 +1466,7 @@ admin = 127.0.0.1
|
||||
protocol = http
|
||||
|
||||
[port_peer]
|
||||
# Many servers still use the legacy port of 51235, so for backward-compatibility
|
||||
# we maintain that port number here. However, for new servers we recommend
|
||||
# changing this to the default port of 2459.
|
||||
port = 51235
|
||||
port = 2459
|
||||
ip = 0.0.0.0
|
||||
# alternatively, to accept connections on IPv4 + IPv6, use:
|
||||
#ip = ::
|
||||
|
||||
@@ -99,7 +99,14 @@ isPseudoAccount(
|
||||
* createPseudoAccount.
|
||||
*/
|
||||
[[nodiscard]] Expected<std::shared_ptr<SLE>, TER>
|
||||
createPseudoAccount(ApplyView& view, uint256 const& pseudoOwnerKey, SField const& ownerField);
|
||||
createPseudoAccount(
|
||||
ApplyView& view,
|
||||
uint256 const& pseudoOwnerKey,
|
||||
SField const& ownerField,
|
||||
std::uint32_t additionalFlags = 0);
|
||||
|
||||
[[nodiscard]] bool
|
||||
isBlackholed(ReadView const& view, std::shared_ptr<SLE const> const& sle);
|
||||
|
||||
/** Checks the destination and tag.
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
// Add new amendments to the top of this list.
|
||||
// Keep it sorted in reverse chronological order.
|
||||
|
||||
XRPL_FIX (TokenEscrowV1_1, Supported::No, VoteBehavior::DefaultNo)
|
||||
XRPL_FIX (Cleanup3_2_0, Supported::No, VoteBehavior::DefaultNo)
|
||||
XRPL_FEATURE(MPTokensV2, Supported::No, VoteBehavior::DefaultNo)
|
||||
XRPL_FIX (Cleanup3_1_3, Supported::Yes, VoteBehavior::DefaultYes)
|
||||
|
||||
@@ -208,8 +208,36 @@ isPseudoAccount(
|
||||
}) > 0;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool
|
||||
isBlackholed(ReadView const& view, std::shared_ptr<SLE const> const& sle)
|
||||
{
|
||||
if (!sle || sle->getType() != ltACCOUNT_ROOT)
|
||||
return false;
|
||||
|
||||
if (!sle->isFlag(lsfDisableMaster))
|
||||
return false;
|
||||
|
||||
if (sle->isFieldPresent(sfRegularKey))
|
||||
{
|
||||
AccountID const rk = sle->getAccountID(sfRegularKey);
|
||||
static AccountID const kAccountZero(0);
|
||||
static AccountID const kAccountOne(1);
|
||||
static AccountID const kAccountTwo(2);
|
||||
|
||||
if (rk != kAccountZero && rk != kAccountOne && rk != kAccountTwo)
|
||||
return false;
|
||||
}
|
||||
|
||||
AccountID const account = sle->getAccountID(sfAccount);
|
||||
return !view.exists(keylet::signers(account));
|
||||
}
|
||||
|
||||
Expected<std::shared_ptr<SLE>, TER>
|
||||
createPseudoAccount(ApplyView& view, uint256 const& pseudoOwnerKey, SField const& ownerField)
|
||||
createPseudoAccount(
|
||||
ApplyView& view,
|
||||
uint256 const& pseudoOwnerKey,
|
||||
SField const& ownerField,
|
||||
std::uint32_t additionalFlags)
|
||||
{
|
||||
[[maybe_unused]]
|
||||
auto const& fields = getPseudoAccountFields();
|
||||
@@ -241,7 +269,8 @@ createPseudoAccount(ApplyView& view, uint256 const& pseudoOwnerKey, SField const
|
||||
// Ignore reserves requirement, disable the master key, allow default
|
||||
// rippling, and enable deposit authorization to prevent payments into
|
||||
// pseudo-account.
|
||||
account->setFieldU32(sfFlags, lsfDisableMaster | lsfDefaultRipple | lsfDepositAuth);
|
||||
account->setFieldU32(
|
||||
sfFlags, lsfDisableMaster | lsfDefaultRipple | lsfDepositAuth | additionalFlags);
|
||||
// Link the pseudo-account with its owner object.
|
||||
account->setFieldH256(ownerField, pseudoOwnerKey);
|
||||
|
||||
|
||||
@@ -770,8 +770,11 @@ ValidNewAccountRoot::finalize(
|
||||
|
||||
if (pseudoAccount)
|
||||
{
|
||||
std::uint32_t const expected = (lsfDisableMaster | lsfDefaultRipple | lsfDepositAuth);
|
||||
if (flags_ != expected)
|
||||
std::uint32_t const base = (lsfDisableMaster | lsfDefaultRipple | lsfDepositAuth);
|
||||
bool valid = (flags_ == base);
|
||||
if (!valid && view.rules().enabled(fixTokenEscrowV1_1))
|
||||
valid = (flags_ == (base | lsfAllowTrustLineLocking));
|
||||
if (!valid)
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: pseudo-account created with "
|
||||
"wrong flags";
|
||||
|
||||
@@ -244,7 +244,9 @@ applyCreate(ApplyContext& ctx, Sandbox& sb, AccountID const& account, beast::Jou
|
||||
auto const ammKeylet = keylet::amm(amount.asset(), amount2.asset());
|
||||
|
||||
// Mitigate same account exists possibility
|
||||
auto const maybeAccount = createPseudoAccount(sb, ammKeylet.key, sfAMMID);
|
||||
std::uint32_t const additionalFlags =
|
||||
sb.rules().enabled(fixTokenEscrowV1_1) ? lsfAllowTrustLineLocking : 0u;
|
||||
auto const maybeAccount = createPseudoAccount(sb, ammKeylet.key, sfAMMID, additionalFlags);
|
||||
// AMM account already exists (should not happen)
|
||||
if (!maybeAccount)
|
||||
{
|
||||
|
||||
@@ -195,7 +195,19 @@ escrowCreatePreclaimHelper<Issue>(
|
||||
if (!sleIssuer)
|
||||
return tecNO_ISSUER;
|
||||
if (!sleIssuer->isFlag(lsfAllowTrustLineLocking))
|
||||
return tecNO_PERMISSION;
|
||||
{
|
||||
if (ctx.view.rules().enabled(fixTokenEscrowV1_1))
|
||||
{
|
||||
bool const isAMM = isPseudoAccount(sleIssuer, {&sfAMMID});
|
||||
bool const isBlackholedIssuer = isBlackholed(ctx.view, sleIssuer);
|
||||
if (!isAMM && !isBlackholedIssuer)
|
||||
return tecNO_PERMISSION;
|
||||
}
|
||||
else
|
||||
{
|
||||
return tecNO_PERMISSION;
|
||||
}
|
||||
}
|
||||
|
||||
// If the account does not have a trustline to the issuer, return tecNO_LINE
|
||||
auto const sleRippleState = ctx.view.read(keylet::line(account, issuer, issue.currency));
|
||||
@@ -469,6 +481,23 @@ EscrowCreate::doApply()
|
||||
auto const xferRate = transferRate(ctx_.view(), amount);
|
||||
if (xferRate != kParityRate)
|
||||
(*slep)[sfTransferRate] = xferRate.value;
|
||||
|
||||
if (ctx_.view().rules().enabled(fixTokenEscrowV1_1))
|
||||
{
|
||||
AccountID const issuer = amount.getIssuer();
|
||||
auto sleIssuer = ctx_.view().peek(keylet::account(issuer));
|
||||
if (sleIssuer && !sleIssuer->isFlag(lsfAllowTrustLineLocking))
|
||||
{
|
||||
bool const isAMM = isPseudoAccount(sleIssuer, {&sfAMMID});
|
||||
bool const isBlackholedIssuer = isBlackholed(ctx_.view(), sleIssuer);
|
||||
if (isAMM || isBlackholedIssuer)
|
||||
{
|
||||
sleIssuer->setFieldU32(
|
||||
sfFlags, sleIssuer->getFlags() | lsfAllowTrustLineLocking);
|
||||
ctx_.view().update(sleIssuer);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ctx_.view().insert(slep);
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
|
||||
#include <xrpl/ledger/helpers/TokenHelpers.h>
|
||||
#include <xrpl/ledger/helpers/VaultHelpers.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/Issue.h>
|
||||
#include <xrpl/protocol/LedgerFormats.h>
|
||||
@@ -252,19 +253,26 @@ VaultDeposit::doApply()
|
||||
!isTesSuccess(ter))
|
||||
return ter;
|
||||
|
||||
// Sanity check
|
||||
if (accountHolds(
|
||||
view(),
|
||||
accountID_,
|
||||
assetsDeposited.asset(),
|
||||
FreezeHandling::IgnoreFreeze,
|
||||
AuthHandling::IgnoreAuth,
|
||||
j_) < beast::kZero)
|
||||
// This check is wrong. Disable it with fixCleanup3_2_0.
|
||||
// For XRP and MPT the predicate is structurally unsatisfiable: xrpLiquid clamps at zero, and
|
||||
// MPT balances are unsigned. For IOUs it only fires when the deposit drove the depositor's
|
||||
// trust line into debt the exact case preclaim authorizes via SpendableHandling::FullBalance.
|
||||
// The check thus converts a preclaim- authorized deposit into tefINTERNAL after the asset
|
||||
// transfer.
|
||||
if (!view().rules().enabled(fixCleanup3_2_0))
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
JLOG(j_.error()) << "VaultDeposit: negative balance of account assets.";
|
||||
return tefINTERNAL;
|
||||
// LCOV_EXCL_STOP
|
||||
// Sanity check
|
||||
if (accountHolds(
|
||||
view(),
|
||||
accountID_,
|
||||
assetsDeposited.asset(),
|
||||
FreezeHandling::IgnoreFreeze,
|
||||
AuthHandling::IgnoreAuth,
|
||||
j_) < beast::kZero)
|
||||
{
|
||||
JLOG(j_.error()) << "VaultDeposit: negative balance of account assets.";
|
||||
return tefINTERNAL;
|
||||
}
|
||||
}
|
||||
|
||||
// Transfer shares from vault to depositor.
|
||||
|
||||
@@ -4365,19 +4365,25 @@ private:
|
||||
}
|
||||
|
||||
void
|
||||
testFlags()
|
||||
testFlags(FeatureBitset features)
|
||||
{
|
||||
testcase("Flags");
|
||||
using namespace jtx;
|
||||
|
||||
testAMM([&](AMM& ammAlice, Env& env) {
|
||||
auto const info = env.rpc(
|
||||
"json",
|
||||
"account_info",
|
||||
std::string("{\"account\": \"" + to_string(ammAlice.ammAccount()) + "\"}"));
|
||||
auto const flags = info[jss::result][jss::account_data][jss::Flags].asUInt();
|
||||
BEAST_EXPECT(flags == (lsfDisableMaster | lsfDefaultRipple | lsfDepositAuth));
|
||||
});
|
||||
Env env{*this, features};
|
||||
fund(env, gw_, {alice_}, {USD(20'000)}, Fund::All);
|
||||
AMM const ammAlice(env, alice_, XRP(10'000), USD(10'000));
|
||||
auto const ammAccount = ammAlice.ammAccount();
|
||||
|
||||
auto const sleAMM = env.le(keylet::account(ammAccount));
|
||||
if (BEAST_EXPECT(sleAMM))
|
||||
{
|
||||
BEAST_EXPECT(sleAMM->isFlag(lsfDisableMaster));
|
||||
BEAST_EXPECT(sleAMM->isFlag(lsfDefaultRipple));
|
||||
BEAST_EXPECT(sleAMM->isFlag(lsfDepositAuth));
|
||||
bool const flag = sleAMM->isFlag(lsfAllowTrustLineLocking);
|
||||
BEAST_EXPECT(features[fixTokenEscrowV1_1] ? flag : !flag);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
@@ -7194,7 +7200,8 @@ private:
|
||||
testBasicPaymentEngine(all - fixAMMv1_1 - fixAMMv1_3 - fixReducedOffersV2);
|
||||
testAMMTokens();
|
||||
testAmendment();
|
||||
testFlags();
|
||||
testFlags(all);
|
||||
testFlags(all - fixTokenEscrowV1_1);
|
||||
testRippling();
|
||||
testAMMAndCLOB(all);
|
||||
testAMMAndCLOB(all - fixAMMv1_1 - fixAMMv1_3);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
|
||||
#include <test/jtx/AMM.h>
|
||||
#include <test/jtx/Account.h>
|
||||
#include <test/jtx/Env.h>
|
||||
#include <test/jtx/amount.h>
|
||||
@@ -9,6 +10,8 @@
|
||||
#include <test/jtx/mpt.h>
|
||||
#include <test/jtx/pay.h>
|
||||
#include <test/jtx/rate.h>
|
||||
#include <test/jtx/regkey.h>
|
||||
#include <test/jtx/sig.h>
|
||||
#include <test/jtx/ter.h>
|
||||
#include <test/jtx/trust.h>
|
||||
#include <test/jtx/txflags.h>
|
||||
@@ -26,6 +29,7 @@
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/Issue.h>
|
||||
#include <xrpl/protocol/LedgerFormats.h>
|
||||
#include <xrpl/protocol/MPTIssue.h>
|
||||
#include <xrpl/protocol/Protocol.h>
|
||||
#include <xrpl/protocol/SField.h>
|
||||
@@ -372,6 +376,86 @@ struct EscrowToken_test : public beast::unit_test::Suite
|
||||
env.close();
|
||||
}
|
||||
|
||||
// AMM issuer without asfAllowTrustLineLocking
|
||||
// (succeeds under fixTokenEscrowV1_1, fails otherwise)
|
||||
{
|
||||
bool const withFix = features[fixTokenEscrowV1_1];
|
||||
Env env{*this, features};
|
||||
auto const baseFee = env.current()->fees().base;
|
||||
auto const alice = Account("alice");
|
||||
auto const bob = Account("bob");
|
||||
auto const carol = Account("carol");
|
||||
auto const gw = Account{"gateway"};
|
||||
|
||||
env.fund(XRP(30'000), alice, bob, carol, gw);
|
||||
env.close();
|
||||
|
||||
auto const usd = gw["USD"];
|
||||
env.trust(usd(30'000), alice);
|
||||
env.close();
|
||||
env(pay(gw, alice, usd(10'000)));
|
||||
env.close();
|
||||
|
||||
AMM const ammAlice(env, alice, XRP(10'000), usd(10'000));
|
||||
auto const ammAccount = ammAlice.ammAccount();
|
||||
auto const lpIssue = ammAlice.lptIssue();
|
||||
|
||||
env.trust(STAmount{lpIssue, 10'000}, carol);
|
||||
env.close();
|
||||
env(pay(alice, carol, STAmount{lpIssue, 100}));
|
||||
env.close();
|
||||
|
||||
env(escrow::create(carol, bob, STAmount{lpIssue, 50}),
|
||||
escrow::kFinishTime(env.now() + 1s),
|
||||
Fee(baseFee * 150),
|
||||
Ter(withFix ? TER(tesSUCCESS) : TER(tecNO_PERMISSION)));
|
||||
env.close();
|
||||
|
||||
if (withFix)
|
||||
{
|
||||
auto const sleAMM = env.le(keylet::account(ammAccount));
|
||||
BEAST_EXPECT(sleAMM && sleAMM->isFlag(lsfAllowTrustLineLocking));
|
||||
}
|
||||
}
|
||||
|
||||
// Blackholed issuer without asfAllowTrustLineLocking
|
||||
// (succeeds under fixTokenEscrowV1_1, fails otherwise)
|
||||
{
|
||||
bool const withFix = features[fixTokenEscrowV1_1];
|
||||
Env env{*this, features};
|
||||
auto const baseFee = env.current()->fees().base;
|
||||
auto const alice = Account("alice");
|
||||
auto const bob = Account("bob");
|
||||
auto const gw = Account{"gateway"};
|
||||
auto const usd = gw["USD"];
|
||||
|
||||
env.fund(XRP(5000), alice, bob, gw);
|
||||
env.close();
|
||||
env.trust(usd(10'000), alice, bob);
|
||||
env.close();
|
||||
env(pay(gw, alice, usd(5000)));
|
||||
env(pay(gw, bob, usd(5000)));
|
||||
env.close();
|
||||
|
||||
Account const blackhole("blackhole", AccountID(1));
|
||||
env(regkey(gw, blackhole));
|
||||
env.close();
|
||||
env(fset(gw, asfDisableMaster), Sig(gw));
|
||||
env.close();
|
||||
|
||||
env(escrow::create(alice, bob, usd(100)),
|
||||
escrow::kFinishTime(env.now() + 1s),
|
||||
Fee(baseFee * 150),
|
||||
Ter(withFix ? TER(tesSUCCESS) : TER(tecNO_PERMISSION)));
|
||||
env.close();
|
||||
|
||||
if (withFix)
|
||||
{
|
||||
auto const sleGW = env.le(keylet::account(gw));
|
||||
BEAST_EXPECT(sleGW && sleGW->isFlag(lsfAllowTrustLineLocking));
|
||||
}
|
||||
}
|
||||
|
||||
// tecNO_LINE: account does not have a trustline to the issuer
|
||||
{
|
||||
Env env{*this, features};
|
||||
@@ -3993,9 +4077,11 @@ public:
|
||||
{all - featureSingleAssetVault - featureLendingProtocol, all})
|
||||
{
|
||||
testIOUWithFeats(feats);
|
||||
testIOUWithFeats(feats - fixCleanup3_2_0);
|
||||
testIOUWithFeats(feats - fixTokenEscrowV1_1);
|
||||
testMPTWithFeats(feats);
|
||||
testMPTWithFeats(feats - fixTokenEscrowV1);
|
||||
testMPTWithFeats(feats - fixTokenEscrowV1 - fixTokenEscrowV1_1);
|
||||
testIOUWithFeats(feats - fixCleanup3_2_0);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -6140,10 +6140,90 @@ class Vault_test : public beast::unit_test::Suite
|
||||
runTest(amendments);
|
||||
}
|
||||
|
||||
// VaultDeposit::preclaim uses accountHolds(..., SpendableHandling::
|
||||
// shFULL_BALANCE), which for an IOU asset adds the counterparty's
|
||||
// LowLimit/HighLimit to the depositor's raw balance (TokenHelpers.cpp:
|
||||
// getTrustLineBalance with includeOppositeLimit=true). When the
|
||||
// depositor's raw balance < deposit amount but raw + opposite limit >=
|
||||
// amount, preclaim is satisfied. doApply then calls
|
||||
// directSendNoFeeIOU, which unconditionally subtracts saAmount from
|
||||
// saBalance — driving the trust line negative — and returns tesSUCCESS.
|
||||
// The post-send sanity check uses the default shSIMPLE_BALANCE (no
|
||||
// opposite-limit add), sees a negative balance, and returns tefINTERNAL.
|
||||
void
|
||||
testVaultDepositNegativeBalanceFromOppositeLimit()
|
||||
{
|
||||
auto runTest = [&](FeatureBitset f, TER expected) {
|
||||
using namespace test::jtx;
|
||||
using namespace std::literals;
|
||||
|
||||
Env env{*this, f};
|
||||
Account const gw{"gateway"};
|
||||
Account const owner{"owner"};
|
||||
Account const depositor{"depositor"};
|
||||
|
||||
env.fund(XRP(10000), gw, owner, depositor);
|
||||
env.close();
|
||||
|
||||
// Gateway with DefaultRipple so vault creation on its IOU works.
|
||||
env(fset(gw, asfDefaultRipple));
|
||||
env.close();
|
||||
|
||||
// Depositor opens a trust line to gateway and receives a small
|
||||
// balance.
|
||||
PrettyAsset const usd = gw["USD"];
|
||||
env.trust(usd(1000), depositor);
|
||||
env(pay(gw, depositor, usd(100))); // raw trust-line balance: 100
|
||||
env.close();
|
||||
|
||||
// Key precondition: gateway sets a non-zero limit on the same
|
||||
// RippleState — the "opposite field" from depositor's perspective.
|
||||
// This is what inflates shFULL_BALANCE in preclaim above the raw
|
||||
// balance.
|
||||
env(trust(gw, depositor["USD"](1000)));
|
||||
env.close();
|
||||
|
||||
// Create the IOU vault.
|
||||
Vault const vault{env};
|
||||
auto [vaultTx, keylet] = vault.create({.owner = owner, .asset = usd});
|
||||
env(vaultTx);
|
||||
env.close();
|
||||
|
||||
// Submit a deposit of 500 USD:
|
||||
// - raw balance: 100 USD
|
||||
// - opposite limit (gw's side): 1000 USD
|
||||
// - preclaim sees 100 + 1000 = 1100, passes (>= 500)
|
||||
// - doApply transfers 500, depositor's trust-line balance
|
||||
// becomes -400
|
||||
// - sanity check at VaultDeposit.cpp:256 fires
|
||||
// - tx returns tefINTERNAL (BUG — should be tesSUCCESS.
|
||||
auto depositTx =
|
||||
vault.deposit({.depositor = depositor, .id = keylet.key, .amount = usd(500)});
|
||||
env(depositTx, Ter(expected));
|
||||
env.close();
|
||||
};
|
||||
|
||||
{
|
||||
testcase(
|
||||
"IOU vault deposit exceeding depositor's balance but "
|
||||
"within counterparty's trust limit, pre-fixCleanup3_2_0 "
|
||||
"(tefINTERNAL)");
|
||||
runTest(test::jtx::testableAmendments() - fixCleanup3_2_0, tefINTERNAL);
|
||||
}
|
||||
{
|
||||
testcase(
|
||||
"IOU vault deposit exceeding depositor's balance but "
|
||||
"within counterparty's trust limit, post-fixCleanup3_2_0 "
|
||||
"(tesSUCCESS)");
|
||||
runTest(test::jtx::testableAmendments(), tesSUCCESS);
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
void
|
||||
run() override
|
||||
{
|
||||
testVaultDepositNegativeBalanceFromOppositeLimit();
|
||||
testSequences();
|
||||
testPreflight();
|
||||
testCreateFailXRP();
|
||||
|
||||
Reference in New Issue
Block a user