mirror of
https://github.com/XRPLF/rippled.git
synced 2026-07-01 03:22:19 +00:00
Compare commits
1 Commits
ximinez/fi
...
develop
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ecf7f805c9 |
@@ -2,9 +2,6 @@
|
|||||||
|
|
||||||
#include <xrpl/basics/IntrusivePointer.ipp>
|
#include <xrpl/basics/IntrusivePointer.ipp>
|
||||||
#include <xrpl/basics/TaggedCache.h>
|
#include <xrpl/basics/TaggedCache.h>
|
||||||
#include <xrpl/basics/scope.h>
|
|
||||||
|
|
||||||
#include <algorithm>
|
|
||||||
|
|
||||||
namespace xrpl {
|
namespace xrpl {
|
||||||
|
|
||||||
@@ -598,37 +595,8 @@ TaggedCache<Key, T, IsKeyCache, SharedWeakUnionPointer, SharedPointerType, Hash,
|
|||||||
std::vector<key_type> v;
|
std::vector<key_type> v;
|
||||||
|
|
||||||
{
|
{
|
||||||
// Keep track of how many iterations are needed. Exit the loop if the number of retries gets
|
std::scoped_lock const lock(mutex_);
|
||||||
// absurd. (Note that if this somehow ever happens, one more allocation will be done under
|
v.reserve(cache_.size());
|
||||||
// lock, which is undesirable, but really should be almost impossible. Also, assert that
|
|
||||||
// there were fewer than 3 needed after the loop, because in a normal operating environment,
|
|
||||||
// even 2 is going to be unusual, and 3 shouldn't be needed.
|
|
||||||
std::size_t allocationIterations = 0;
|
|
||||||
std::unique_lock lock(mutex_);
|
|
||||||
for (auto size = cache_.size(); v.capacity() < size && allocationIterations < 20;
|
|
||||||
size = cache_.size())
|
|
||||||
{
|
|
||||||
ScopeUnlock const unlock(lock);
|
|
||||||
// Allocate the current size plus a little extra, in case the cache grows while
|
|
||||||
// allocating. Each time another allocation is needed, the extra also gets bigger until
|
|
||||||
// it ultimately doubles the size + 1.
|
|
||||||
size += (size >> (4 - std::min(allocationIterations, std::size_t{4}))) + 1;
|
|
||||||
v.reserve(size);
|
|
||||||
++allocationIterations;
|
|
||||||
}
|
|
||||||
XRPL_ASSERT(
|
|
||||||
allocationIterations < 3,
|
|
||||||
"xrpl::TaggedCache::getKeys(): limited allocation iterations");
|
|
||||||
if (v.capacity() < cache_.size())
|
|
||||||
{
|
|
||||||
// LCOV_EXCL_START
|
|
||||||
UNREACHABLE("xrpl::TaggedCache::getKeys(): failed to allocate sufficient capacity");
|
|
||||||
v.reserve(cache_.size());
|
|
||||||
// LCOV_EXCL_STOP
|
|
||||||
}
|
|
||||||
XRPL_ASSERT(lock.owns_lock(), "xrpl::TaggedCache::getKeys(): owns lock");
|
|
||||||
XRPL_ASSERT(
|
|
||||||
v.capacity() >= cache_.size(), "xrpl::TaggedCache::getKeys(): sufficient capacity");
|
|
||||||
for (auto const& _ : cache_)
|
for (auto const& _ : cache_)
|
||||||
v.push_back(_.first);
|
v.push_back(_.first);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
|
|
||||||
// Add new amendments to the top of this list.
|
// Add new amendments to the top of this list.
|
||||||
// Keep it sorted in reverse chronological order.
|
// Keep it sorted in reverse chronological order.
|
||||||
|
XRPL_FEATURE(LendingProtocolV1_1, Supported::No, VoteBehavior::DefaultNo)
|
||||||
XRPL_FEATURE(ConfidentialTransfer, Supported::No, VoteBehavior::DefaultNo)
|
XRPL_FEATURE(ConfidentialTransfer, Supported::No, VoteBehavior::DefaultNo)
|
||||||
XRPL_FIX (Cleanup3_3_0, Supported::Yes, VoteBehavior::DefaultNo)
|
XRPL_FIX (Cleanup3_3_0, Supported::Yes, VoteBehavior::DefaultNo)
|
||||||
XRPL_FIX (Cleanup3_2_0, Supported::Yes, VoteBehavior::DefaultNo)
|
XRPL_FIX (Cleanup3_2_0, Supported::Yes, VoteBehavior::DefaultNo)
|
||||||
|
|||||||
@@ -889,6 +889,7 @@ TRANSACTION(ttVAULT_DELETE, 67, VaultDelete,
|
|||||||
MustDeleteAcct | DestroyMptIssuance | MustModifyVault,
|
MustDeleteAcct | DestroyMptIssuance | MustModifyVault,
|
||||||
({
|
({
|
||||||
{sfVaultID, SoeRequired},
|
{sfVaultID, SoeRequired},
|
||||||
|
{sfMemoData, SoeOptional},
|
||||||
}))
|
}))
|
||||||
|
|
||||||
/** This transaction trades assets for shares with a vault. */
|
/** This transaction trades assets for shares with a vault. */
|
||||||
|
|||||||
@@ -57,6 +57,32 @@ public:
|
|||||||
{
|
{
|
||||||
return this->tx_->at(sfVaultID);
|
return this->tx_->at(sfVaultID);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get sfMemoData (SoeOptional)
|
||||||
|
* @return The field value, or std::nullopt if not present.
|
||||||
|
*/
|
||||||
|
[[nodiscard]]
|
||||||
|
protocol_autogen::Optional<SF_VL::type::value_type>
|
||||||
|
getMemoData() const
|
||||||
|
{
|
||||||
|
if (hasMemoData())
|
||||||
|
{
|
||||||
|
return this->tx_->at(sfMemoData);
|
||||||
|
}
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Check if sfMemoData is present.
|
||||||
|
* @return True if the field is present, false otherwise.
|
||||||
|
*/
|
||||||
|
[[nodiscard]]
|
||||||
|
bool
|
||||||
|
hasMemoData() const
|
||||||
|
{
|
||||||
|
return this->tx_->isFieldPresent(sfMemoData);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -112,6 +138,17 @@ public:
|
|||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Set sfMemoData (SoeOptional)
|
||||||
|
* @return Reference to this builder for method chaining.
|
||||||
|
*/
|
||||||
|
VaultDeleteBuilder&
|
||||||
|
setMemoData(std::decay_t<typename SF_VL::type::value_type> const& value)
|
||||||
|
{
|
||||||
|
object_[sfMemoData] = value;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Build and return the VaultDelete wrapper.
|
* @brief Build and return the VaultDelete wrapper.
|
||||||
* @param publicKey The public key for signing.
|
* @param publicKey The public key for signing.
|
||||||
|
|||||||
@@ -7,8 +7,10 @@
|
|||||||
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
|
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
|
||||||
#include <xrpl/ledger/helpers/TokenHelpers.h>
|
#include <xrpl/ledger/helpers/TokenHelpers.h>
|
||||||
#include <xrpl/protocol/AccountID.h>
|
#include <xrpl/protocol/AccountID.h>
|
||||||
|
#include <xrpl/protocol/Feature.h>
|
||||||
#include <xrpl/protocol/Indexes.h>
|
#include <xrpl/protocol/Indexes.h>
|
||||||
#include <xrpl/protocol/MPTIssue.h>
|
#include <xrpl/protocol/MPTIssue.h>
|
||||||
|
#include <xrpl/protocol/Protocol.h>
|
||||||
#include <xrpl/protocol/SField.h>
|
#include <xrpl/protocol/SField.h>
|
||||||
#include <xrpl/protocol/STLedgerEntry.h>
|
#include <xrpl/protocol/STLedgerEntry.h>
|
||||||
#include <xrpl/protocol/STNumber.h> // IWYU pragma: keep
|
#include <xrpl/protocol/STNumber.h> // IWYU pragma: keep
|
||||||
@@ -28,6 +30,12 @@ VaultDelete::preflight(PreflightContext const& ctx)
|
|||||||
return temMALFORMED;
|
return temMALFORMED;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (ctx.tx.isFieldPresent(sfMemoData) && !ctx.rules.enabled(featureLendingProtocolV1_1))
|
||||||
|
return temDISABLED;
|
||||||
|
|
||||||
|
if (!validDataLength(ctx.tx[~sfMemoData], kMaxDataPayloadLength))
|
||||||
|
return temMALFORMED;
|
||||||
|
|
||||||
return tesSUCCESS;
|
return tesSUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7511,6 +7511,74 @@ class Vault_test : public beast::unit_test::Suite
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
testVaultDeleteMemoData()
|
||||||
|
{
|
||||||
|
using namespace test::jtx;
|
||||||
|
|
||||||
|
Env env{*this};
|
||||||
|
|
||||||
|
Account const owner{"owner"};
|
||||||
|
env.fund(XRP(1'000'000), owner);
|
||||||
|
env.close();
|
||||||
|
|
||||||
|
Vault const vault{env};
|
||||||
|
|
||||||
|
auto const keylet = keylet::vault(owner.id(), 1);
|
||||||
|
auto delTx = vault.del({.owner = owner, .id = keylet.key});
|
||||||
|
|
||||||
|
// Test VaultDelete with featureLendingProtocolV1_1 disabled
|
||||||
|
// Transaction fails if the data field is provided
|
||||||
|
{
|
||||||
|
testcase("VaultDelete memo data featureLendingProtocolV1_1 disabled");
|
||||||
|
env.disableFeature(featureLendingProtocolV1_1);
|
||||||
|
delTx[sfMemoData] = strHex(std::string(kMaxDataPayloadLength, 'A'));
|
||||||
|
env(delTx, Ter(temDISABLED));
|
||||||
|
env.enableFeature(featureLendingProtocolV1_1);
|
||||||
|
env.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transaction fails if the data field is too large
|
||||||
|
{
|
||||||
|
testcase("VaultDelete memo data featureLendingProtocolV1_1 enabled data too large");
|
||||||
|
delTx[sfMemoData] = strHex(std::string(kMaxDataPayloadLength + 1, 'A'));
|
||||||
|
env(delTx, Ter(temMALFORMED));
|
||||||
|
env.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transaction fails if the data field is set, but is empty
|
||||||
|
{
|
||||||
|
testcase("VaultDelete memo data featureLendingProtocolV1_1 enabled data empty");
|
||||||
|
delTx[sfMemoData] = strHex(std::string(0, 'A'));
|
||||||
|
env(delTx, Ter(temMALFORMED));
|
||||||
|
env.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
testcase("VaultDelete memo data featureLendingProtocolV1_1 enabled no vault");
|
||||||
|
auto const keylet = keylet::vault(owner.id(), env.seq(owner));
|
||||||
|
|
||||||
|
// Recreate the transaction as the vault keylet changed
|
||||||
|
auto delTx = vault.del({.owner = owner, .id = keylet.key});
|
||||||
|
delTx[sfMemoData] = strHex(std::string(kMaxDataPayloadLength, 'A'));
|
||||||
|
env(delTx, Ter(tecNO_ENTRY));
|
||||||
|
env.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
testcase("VaultDelete memo data featureLendingProtocolV1_1 enabled data valid");
|
||||||
|
PrettyAsset const xrpAsset = xrpIssue();
|
||||||
|
auto const [tx, keylet] = vault.create({.owner = owner, .asset = xrpAsset});
|
||||||
|
env(tx, Ter(tesSUCCESS));
|
||||||
|
env.close();
|
||||||
|
// Recreate the transaction as the vault keylet changed
|
||||||
|
auto delTx = vault.del({.owner = owner, .id = keylet.key});
|
||||||
|
delTx[sfMemoData] = strHex(std::string(kMaxDataPayloadLength, 'A'));
|
||||||
|
env(delTx, Ter(tesSUCCESS));
|
||||||
|
env.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
testVaultDepositFreeze()
|
testVaultDepositFreeze()
|
||||||
{
|
{
|
||||||
@@ -8082,6 +8150,7 @@ class Vault_test : public beast::unit_test::Suite
|
|||||||
|
|
||||||
runTests();
|
runTests();
|
||||||
env.disableFeature(fixCleanup3_3_0);
|
env.disableFeature(fixCleanup3_3_0);
|
||||||
|
|
||||||
runTests();
|
runTests();
|
||||||
env.enableFeature(fixCleanup3_3_0);
|
env.enableFeature(fixCleanup3_3_0);
|
||||||
}
|
}
|
||||||
@@ -8115,6 +8184,7 @@ public:
|
|||||||
testVaultClawbackAssets();
|
testVaultClawbackAssets();
|
||||||
testVaultEscrowedMPT();
|
testVaultEscrowedMPT();
|
||||||
testAssetsMaximum();
|
testAssetsMaximum();
|
||||||
|
testVaultDeleteMemoData();
|
||||||
testBug6LimitBypassWithShares();
|
testBug6LimitBypassWithShares();
|
||||||
testRemoveEmptyHoldingLockedAmount();
|
testRemoveEmptyHoldingLockedAmount();
|
||||||
testRemoveEmptyHoldingConfidentialBalances();
|
testRemoveEmptyHoldingConfidentialBalances();
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ TEST(TransactionsVaultDeleteTests, BuilderSettersRoundTrip)
|
|||||||
|
|
||||||
// Transaction-specific field values
|
// Transaction-specific field values
|
||||||
auto const vaultIDValue = canonical_UINT256();
|
auto const vaultIDValue = canonical_UINT256();
|
||||||
|
auto const memoDataValue = canonical_VL();
|
||||||
|
|
||||||
VaultDeleteBuilder builder{
|
VaultDeleteBuilder builder{
|
||||||
accountValue,
|
accountValue,
|
||||||
@@ -39,6 +40,7 @@ TEST(TransactionsVaultDeleteTests, BuilderSettersRoundTrip)
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Set optional fields
|
// Set optional fields
|
||||||
|
builder.setMemoData(memoDataValue);
|
||||||
|
|
||||||
auto tx = builder.build(publicKey, secretKey);
|
auto tx = builder.build(publicKey, secretKey);
|
||||||
|
|
||||||
@@ -62,6 +64,14 @@ TEST(TransactionsVaultDeleteTests, BuilderSettersRoundTrip)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Verify optional fields
|
// Verify optional fields
|
||||||
|
{
|
||||||
|
auto const& expected = memoDataValue;
|
||||||
|
auto const actualOpt = tx.getMemoData();
|
||||||
|
ASSERT_TRUE(actualOpt.has_value()) << "Optional field sfMemoData should be present";
|
||||||
|
expectEqualField(expected, *actualOpt, "sfMemoData");
|
||||||
|
EXPECT_TRUE(tx.hasMemoData());
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2 & 4) Start from an STTx, construct a builder from it, build a new wrapper,
|
// 2 & 4) Start from an STTx, construct a builder from it, build a new wrapper,
|
||||||
@@ -79,6 +89,7 @@ TEST(TransactionsVaultDeleteTests, BuilderFromStTxRoundTrip)
|
|||||||
|
|
||||||
// Transaction-specific field values
|
// Transaction-specific field values
|
||||||
auto const vaultIDValue = canonical_UINT256();
|
auto const vaultIDValue = canonical_UINT256();
|
||||||
|
auto const memoDataValue = canonical_VL();
|
||||||
|
|
||||||
// Build an initial transaction
|
// Build an initial transaction
|
||||||
VaultDeleteBuilder initialBuilder{
|
VaultDeleteBuilder initialBuilder{
|
||||||
@@ -88,6 +99,7 @@ TEST(TransactionsVaultDeleteTests, BuilderFromStTxRoundTrip)
|
|||||||
feeValue
|
feeValue
|
||||||
};
|
};
|
||||||
|
|
||||||
|
initialBuilder.setMemoData(memoDataValue);
|
||||||
|
|
||||||
auto initialTx = initialBuilder.build(publicKey, secretKey);
|
auto initialTx = initialBuilder.build(publicKey, secretKey);
|
||||||
|
|
||||||
@@ -112,6 +124,13 @@ TEST(TransactionsVaultDeleteTests, BuilderFromStTxRoundTrip)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Verify optional fields
|
// Verify optional fields
|
||||||
|
{
|
||||||
|
auto const& expected = memoDataValue;
|
||||||
|
auto const actualOpt = rebuiltTx.getMemoData();
|
||||||
|
ASSERT_TRUE(actualOpt.has_value()) << "Optional field sfMemoData should be present";
|
||||||
|
expectEqualField(expected, *actualOpt, "sfMemoData");
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3) Verify wrapper throws when constructed from wrong transaction type.
|
// 3) Verify wrapper throws when constructed from wrong transaction type.
|
||||||
@@ -142,5 +161,35 @@ TEST(TransactionsVaultDeleteTests, BuilderThrowsOnWrongTxType)
|
|||||||
EXPECT_THROW(VaultDeleteBuilder{wrongTx.getSTTx()}, std::runtime_error);
|
EXPECT_THROW(VaultDeleteBuilder{wrongTx.getSTTx()}, std::runtime_error);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 5) Build with only required fields and verify optional fields return nullopt.
|
||||||
|
TEST(TransactionsVaultDeleteTests, OptionalFieldsReturnNullopt)
|
||||||
|
{
|
||||||
|
// Generate a deterministic keypair for signing
|
||||||
|
auto const [publicKey, secretKey] =
|
||||||
|
generateKeyPair(KeyType::Secp256k1, generateSeed("testVaultDeleteNullopt"));
|
||||||
|
|
||||||
|
// Common transaction fields
|
||||||
|
auto const accountValue = calcAccountID(publicKey);
|
||||||
|
std::uint32_t const sequenceValue = 3;
|
||||||
|
auto const feeValue = canonical_AMOUNT();
|
||||||
|
|
||||||
|
// Transaction-specific required field values
|
||||||
|
auto const vaultIDValue = canonical_UINT256();
|
||||||
|
|
||||||
|
VaultDeleteBuilder builder{
|
||||||
|
accountValue,
|
||||||
|
vaultIDValue,
|
||||||
|
sequenceValue,
|
||||||
|
feeValue
|
||||||
|
};
|
||||||
|
|
||||||
|
// Do NOT set optional fields
|
||||||
|
|
||||||
|
auto tx = builder.build(publicKey, secretKey);
|
||||||
|
|
||||||
|
// Verify optional fields are not present
|
||||||
|
EXPECT_FALSE(tx.hasMemoData());
|
||||||
|
EXPECT_FALSE(tx.getMemoData().has_value());
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user