mirror of
https://github.com/XRPLF/rippled.git
synced 2026-06-08 19:26:45 +00:00
Compare commits
34 Commits
bthomee/pr
...
tapanito/l
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
189f2d60bd | ||
|
|
beb8a1872d | ||
|
|
24db40e56c | ||
|
|
da4513d096 | ||
|
|
2e2fddefe9 | ||
|
|
db997ecad9 | ||
|
|
68e4fbdf2b | ||
|
|
bb0a09ae21 | ||
|
|
d94232007f | ||
|
|
df8bfbe5af | ||
|
|
347d1a19ef | ||
|
|
d65fab27a1 | ||
|
|
b5d25c5ab1 | ||
|
|
7222150095 | ||
|
|
a67da5c2ed | ||
|
|
d2f23b2f5b | ||
|
|
4067e5025f | ||
|
|
ed4330a7d6 | ||
|
|
feba605998 | ||
|
|
b322097529 | ||
|
|
e159d27373 | ||
|
|
ba53026006 | ||
|
|
34773080df | ||
|
|
3c3bd75991 | ||
|
|
7459fe454d | ||
|
|
106bf48725 | ||
|
|
74c968d4e3 | ||
|
|
167147281c | ||
|
|
ba60306610 | ||
|
|
6674500896 | ||
|
|
c5d7ebe93d | ||
|
|
d0b5ca9dab | ||
|
|
5e51893e9b | ||
|
|
3422c11d02 |
@@ -15,6 +15,7 @@
|
||||
// Add new amendments to the top of this list.
|
||||
// Keep it sorted in reverse chronological order.
|
||||
|
||||
XRPL_FEATURE(LendingProtocolV1_1, Supported::No, VoteBehavior::DefaultNo)
|
||||
XRPL_FIX (Cleanup3_2_0, Supported::Yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FEATURE(MPTokensV2, Supported::No, VoteBehavior::DefaultNo)
|
||||
XRPL_FIX (Cleanup3_1_3, Supported::Yes, VoteBehavior::DefaultYes)
|
||||
|
||||
@@ -887,6 +887,7 @@ TRANSACTION(ttVAULT_DELETE, 67, VaultDelete,
|
||||
MustDeleteAcct | DestroyMptIssuance | MustModifyVault,
|
||||
({
|
||||
{sfVaultID, SoeRequired},
|
||||
{sfMemoData, SoeOptional},
|
||||
}))
|
||||
|
||||
/** This transaction trades assets for shares with a vault. */
|
||||
|
||||
@@ -57,6 +57,32 @@ public:
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* @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.
|
||||
* @param publicKey The public key for signing.
|
||||
|
||||
@@ -7,8 +7,10 @@
|
||||
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
|
||||
#include <xrpl/ledger/helpers/TokenHelpers.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/MPTIssue.h>
|
||||
#include <xrpl/protocol/Protocol.h>
|
||||
#include <xrpl/protocol/SField.h>
|
||||
#include <xrpl/protocol/STLedgerEntry.h>
|
||||
#include <xrpl/protocol/STNumber.h> // IWYU pragma: keep
|
||||
@@ -28,6 +30,14 @@ VaultDelete::preflight(PreflightContext const& ctx)
|
||||
return temMALFORMED;
|
||||
}
|
||||
|
||||
if (ctx.tx.isFieldPresent(sfMemoData) && !ctx.rules.enabled(featureLendingProtocolV1_1))
|
||||
return temDISABLED;
|
||||
|
||||
// The sfMemoData field is an optional field used to record the deletion reason.
|
||||
if (auto const data = ctx.tx[~sfMemoData];
|
||||
data && !validDataLength(data, kMaxDataPayloadLength))
|
||||
return temMALFORMED;
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
|
||||
@@ -8027,6 +8027,63 @@ class Vault_test : public beast::unit_test::Suite
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testVaultDeleteData()
|
||||
{
|
||||
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 data featureLendingProtocolV1_1 disabled");
|
||||
env.disableFeature(featureLendingProtocolV1_1);
|
||||
delTx[sfMemoData] = strHex(std::string(kMaxDataPayloadLength, 'A'));
|
||||
env(delTx, Ter(temDISABLED));
|
||||
env.close();
|
||||
env.enableFeature(featureLendingProtocolV1_1);
|
||||
}
|
||||
|
||||
// Transaction fails if the data field is too large
|
||||
{
|
||||
testcase("VaultDelete 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 data featureLendingProtocolV1_1 enabled data empty");
|
||||
delTx[sfMemoData] = strHex(std::string(0, 'A'));
|
||||
env(delTx, Ter(temMALFORMED));
|
||||
env.close();
|
||||
}
|
||||
|
||||
{
|
||||
testcase("VaultDelete data featureLendingProtocolV1_1 enabled data valid");
|
||||
PrettyAsset const xrpAsset = xrpIssue();
|
||||
auto [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();
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
void
|
||||
run() override
|
||||
@@ -8055,6 +8112,7 @@ public:
|
||||
testVaultClawbackAssets();
|
||||
testVaultEscrowedMPT();
|
||||
testAssetsMaximum();
|
||||
testVaultDeleteData();
|
||||
testBug6LimitBypassWithShares();
|
||||
testRemoveEmptyHoldingLockedAmount();
|
||||
|
||||
|
||||
@@ -30,6 +30,7 @@ TEST(TransactionsVaultDeleteTests, BuilderSettersRoundTrip)
|
||||
|
||||
// Transaction-specific field values
|
||||
auto const vaultIDValue = canonical_UINT256();
|
||||
auto const memoDataValue = canonical_VL();
|
||||
|
||||
VaultDeleteBuilder builder{
|
||||
accountValue,
|
||||
@@ -39,6 +40,7 @@ TEST(TransactionsVaultDeleteTests, BuilderSettersRoundTrip)
|
||||
};
|
||||
|
||||
// Set optional fields
|
||||
builder.setMemoData(memoDataValue);
|
||||
|
||||
auto tx = builder.build(publicKey, secretKey);
|
||||
|
||||
@@ -62,6 +64,14 @@ TEST(TransactionsVaultDeleteTests, BuilderSettersRoundTrip)
|
||||
}
|
||||
|
||||
// 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,
|
||||
@@ -79,6 +89,7 @@ TEST(TransactionsVaultDeleteTests, BuilderFromStTxRoundTrip)
|
||||
|
||||
// Transaction-specific field values
|
||||
auto const vaultIDValue = canonical_UINT256();
|
||||
auto const memoDataValue = canonical_VL();
|
||||
|
||||
// Build an initial transaction
|
||||
VaultDeleteBuilder initialBuilder{
|
||||
@@ -88,6 +99,7 @@ TEST(TransactionsVaultDeleteTests, BuilderFromStTxRoundTrip)
|
||||
feeValue
|
||||
};
|
||||
|
||||
initialBuilder.setMemoData(memoDataValue);
|
||||
|
||||
auto initialTx = initialBuilder.build(publicKey, secretKey);
|
||||
|
||||
@@ -112,6 +124,13 @@ TEST(TransactionsVaultDeleteTests, BuilderFromStTxRoundTrip)
|
||||
}
|
||||
|
||||
// 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.
|
||||
@@ -142,5 +161,35 @@ TEST(TransactionsVaultDeleteTests, BuilderThrowsOnWrongTxType)
|
||||
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