mirror of
https://github.com/XRPLF/rippled.git
synced 2026-07-01 11:32:10 +00:00
Compare commits
8 Commits
ximinez/31
...
mathbunnyr
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2432e46114 | ||
|
|
ecf7f805c9 | ||
|
|
909836ffc2 | ||
|
|
2af67d8f6f | ||
|
|
9171fef02c | ||
|
|
273c1862ac | ||
|
|
49e9c2acb0 | ||
|
|
cabafec58d |
@@ -160,7 +160,6 @@ test.peerfinder > xrpl.protocol
|
||||
test.protocol > test.jtx
|
||||
test.protocol > test.unit_test
|
||||
test.protocol > xrpl.basics
|
||||
test.protocol > xrpld.core
|
||||
test.protocol > xrpl.json
|
||||
test.protocol > xrpl.protocol
|
||||
test.resource > test.unit_test
|
||||
|
||||
@@ -10,16 +10,16 @@
|
||||
os={{ os }}
|
||||
arch={{ arch }}
|
||||
build_type=Debug
|
||||
compiler={{compiler}}
|
||||
compiler={{ compiler }}
|
||||
compiler.version={{ compiler_version }}
|
||||
compiler.cppstd=23
|
||||
{% if os == "Windows" %}
|
||||
compiler.runtime=static
|
||||
{% else %}
|
||||
compiler.libcxx={{detect_api.detect_libcxx(compiler, version, compiler_exe)}}
|
||||
compiler.libcxx={{ detect_api.detect_libcxx(compiler, version, compiler_exe) }}
|
||||
{% endif %}
|
||||
|
||||
[conf]
|
||||
{% if compiler == "gcc" and compiler_version < 13 %}
|
||||
tools.build:cxxflags+=['-Wno-restrict']
|
||||
{% endif %}
|
||||
{# By default, conan tries compatibility mode to reuse binaries built with different cppstd versions #}
|
||||
user.package:cppstd_version=23
|
||||
tools.info.package_id:confs+=["user.package:cppstd_version"]
|
||||
|
||||
@@ -87,15 +87,15 @@ include(default)
|
||||
{% endif %}
|
||||
|
||||
[conf]
|
||||
tools.build:defines+={{defines}}
|
||||
tools.build:cxxflags+={{sanitizer_compiler_flags}}
|
||||
tools.build:sharedlinkflags+={{sanitizer_linker_flags}}
|
||||
tools.build:exelinkflags+={{sanitizer_linker_flags}}
|
||||
tools.build:defines+={{ defines }}
|
||||
tools.build:cxxflags+={{ sanitizer_compiler_flags }}
|
||||
tools.build:sharedlinkflags+={{ sanitizer_linker_flags }}
|
||||
tools.build:exelinkflags+={{ sanitizer_linker_flags }}
|
||||
|
||||
tools.info.package_id:confs+=["tools.build:cxxflags", "tools.build:exelinkflags", "tools.build:sharedlinkflags", "tools.build:defines"]
|
||||
|
||||
# &: means "apply only to the consumer/root package"
|
||||
&:tools.cmake.cmaketoolchain:extra_variables={"SANITIZERS": "{{sanitizers}}", "SANITIZERS_COMPILER_FLAGS": "{{sanitizer_compiler_flags | join(' ')}}", "SANITIZERS_LINKER_FLAGS": "{{sanitizer_linker_flags | join(' ')}}"}
|
||||
&:tools.cmake.cmaketoolchain:extra_variables={"SANITIZERS": "{{ sanitizers }}", "SANITIZERS_COMPILER_FLAGS": "{{ sanitizer_compiler_flags | join(' ') }}", "SANITIZERS_LINKER_FLAGS": "{{ sanitizer_linker_flags | join(' ') }}"}
|
||||
|
||||
[options]
|
||||
{% if enable_asan %}
|
||||
|
||||
@@ -14,6 +14,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_FEATURE(ConfidentialTransfer, Supported::No, VoteBehavior::DefaultNo)
|
||||
XRPL_FIX (Cleanup3_3_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,
|
||||
({
|
||||
{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,12 @@ VaultDelete::preflight(PreflightContext const& ctx)
|
||||
return temMALFORMED;
|
||||
}
|
||||
|
||||
if (ctx.tx.isFieldPresent(sfMemoData) && !ctx.rules.enabled(featureLendingProtocolV1_1))
|
||||
return temDISABLED;
|
||||
|
||||
if (!validDataLength(ctx.tx[~sfMemoData], kMaxDataPayloadLength))
|
||||
return temMALFORMED;
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
|
||||
@@ -939,46 +939,6 @@ struct LedgerReplayer_test : public beast::unit_test::Suite
|
||||
BEAST_EXPECT(!reply->has_error());
|
||||
BEAST_EXPECT(server.msgHandler.processProofPathResponse(reply));
|
||||
|
||||
{
|
||||
// bad reply: invalid hash/key sizes
|
||||
{
|
||||
// reply with undersized ledgerhash (31 bytes)
|
||||
auto bad = std::make_shared<protocol::TMProofPathResponse>(*reply);
|
||||
bad->set_ledgerhash(std::string(31, '\x01'));
|
||||
BEAST_EXPECT(!server.msgHandler.processProofPathResponse(bad));
|
||||
}
|
||||
{
|
||||
// reply with oversized ledgerhash (33 bytes)
|
||||
auto bad = std::make_shared<protocol::TMProofPathResponse>(*reply);
|
||||
bad->set_ledgerhash(std::string(33, '\x01'));
|
||||
BEAST_EXPECT(!server.msgHandler.processProofPathResponse(bad));
|
||||
}
|
||||
{
|
||||
// reply with empty ledgerhash
|
||||
auto bad = std::make_shared<protocol::TMProofPathResponse>(*reply);
|
||||
bad->set_ledgerhash(std::string());
|
||||
BEAST_EXPECT(!server.msgHandler.processProofPathResponse(bad));
|
||||
}
|
||||
{
|
||||
// reply with undersized key (31 bytes)
|
||||
auto bad = std::make_shared<protocol::TMProofPathResponse>(*reply);
|
||||
bad->set_key(std::string(31, '\x01'));
|
||||
BEAST_EXPECT(!server.msgHandler.processProofPathResponse(bad));
|
||||
}
|
||||
{
|
||||
// reply with oversized key (33 bytes)
|
||||
auto bad = std::make_shared<protocol::TMProofPathResponse>(*reply);
|
||||
bad->set_key(std::string(33, '\x01'));
|
||||
BEAST_EXPECT(!server.msgHandler.processProofPathResponse(bad));
|
||||
}
|
||||
{
|
||||
// reply with empty key
|
||||
auto bad = std::make_shared<protocol::TMProofPathResponse>(*reply);
|
||||
bad->set_key(std::string());
|
||||
BEAST_EXPECT(!server.msgHandler.processProofPathResponse(bad));
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
// bad reply
|
||||
// bad header
|
||||
@@ -1028,28 +988,6 @@ struct LedgerReplayer_test : public beast::unit_test::Suite
|
||||
BEAST_EXPECT(!reply->has_error());
|
||||
BEAST_EXPECT(server.msgHandler.processReplayDeltaResponse(reply));
|
||||
|
||||
{
|
||||
// bad reply: invalid hash sizes
|
||||
{
|
||||
// reply with undersized ledgerhash (31 bytes)
|
||||
auto bad = std::make_shared<protocol::TMReplayDeltaResponse>(*reply);
|
||||
bad->set_ledgerhash(std::string(31, '\x01'));
|
||||
BEAST_EXPECT(!server.msgHandler.processReplayDeltaResponse(bad));
|
||||
}
|
||||
{
|
||||
// reply with oversized ledgerhash (33 bytes)
|
||||
auto bad = std::make_shared<protocol::TMReplayDeltaResponse>(*reply);
|
||||
bad->set_ledgerhash(std::string(33, '\x01'));
|
||||
BEAST_EXPECT(!server.msgHandler.processReplayDeltaResponse(bad));
|
||||
}
|
||||
{
|
||||
// reply with empty ledgerhash
|
||||
auto bad = std::make_shared<protocol::TMReplayDeltaResponse>(*reply);
|
||||
bad->set_ledgerhash(std::string());
|
||||
BEAST_EXPECT(!server.msgHandler.processReplayDeltaResponse(bad));
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
// bad reply
|
||||
// bad header
|
||||
|
||||
@@ -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
|
||||
testVaultDepositFreeze()
|
||||
{
|
||||
@@ -8082,6 +8150,7 @@ class Vault_test : public beast::unit_test::Suite
|
||||
|
||||
runTests();
|
||||
env.disableFeature(fixCleanup3_3_0);
|
||||
|
||||
runTests();
|
||||
env.enableFeature(fixCleanup3_3_0);
|
||||
}
|
||||
@@ -8115,6 +8184,7 @@ public:
|
||||
testVaultClawbackAssets();
|
||||
testVaultEscrowedMPT();
|
||||
testAssetsMaximum();
|
||||
testVaultDeleteMemoData();
|
||||
testBug6LimitBypassWithShares();
|
||||
testRemoveEmptyHoldingLockedAmount();
|
||||
testRemoveEmptyHoldingConfidentialBalances();
|
||||
|
||||
@@ -117,24 +117,6 @@ struct base_uint_test : beast::unit_test::Suite
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef NDEBUG
|
||||
void
|
||||
testFromRawSizeMismatch()
|
||||
{
|
||||
testcase("base_uint: fromRaw size mismatch");
|
||||
|
||||
// Container larger than the base_uint (16 bytes vs 12 bytes for
|
||||
// test96). Only the first 12 bytes are copied; the extra bytes are
|
||||
// ignored.
|
||||
{
|
||||
Blob const tooBig{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16};
|
||||
test96 const result = test96::fromRaw(tooBig);
|
||||
auto const resultText = to_string(result);
|
||||
BEAST_EXPECTS(resultText == "0102030405060708090A0B0C", resultText);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
void
|
||||
run() override
|
||||
{
|
||||
@@ -143,10 +125,6 @@ struct base_uint_test : beast::unit_test::Suite
|
||||
static_assert(!std::is_constructible_v<test96, std::complex<double>>);
|
||||
static_assert(!std::is_assignable_v<test96&, std::complex<double>>);
|
||||
|
||||
#ifdef NDEBUG
|
||||
testFromRawSizeMismatch();
|
||||
#endif
|
||||
|
||||
testComparisons();
|
||||
|
||||
// used to verify set insertion (hashing required)
|
||||
@@ -216,19 +194,6 @@ struct base_uint_test : beast::unit_test::Suite
|
||||
BEAST_EXPECT(d == 0);
|
||||
}
|
||||
|
||||
{
|
||||
// There are several ways to create a zero. beast::kZero is tested above. Test some
|
||||
// others.
|
||||
test96 const z1;
|
||||
BEAST_EXPECTS(z1 == z, to_string(z1));
|
||||
|
||||
test96 const z2{};
|
||||
BEAST_EXPECTS(z2 == z, to_string(z2));
|
||||
|
||||
test96 const z3{0u};
|
||||
BEAST_EXPECTS(z3 == z, to_string(z3));
|
||||
}
|
||||
|
||||
test96 n{z};
|
||||
n++;
|
||||
BEAST_EXPECT(n == test96(1));
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
#include <xrpl/basics/UnorderedContainers.h>
|
||||
#include <xrpl/basics/base_uint.h>
|
||||
#include <xrpl/beast/unit_test/suite.h>
|
||||
#include <xrpl/json/json_value.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/Book.h>
|
||||
#include <xrpl/protocol/Issue.h>
|
||||
#include <xrpl/protocol/UintTypes.h>
|
||||
#include <xrpl/protocol/jss.h>
|
||||
|
||||
#include <functional>
|
||||
#include <map>
|
||||
@@ -865,101 +863,6 @@ public:
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
void
|
||||
testIssueFromJson()
|
||||
{
|
||||
testcase("issueFromJson");
|
||||
|
||||
// Valid XRP — no issuer field
|
||||
{
|
||||
json::Value jv;
|
||||
jv[jss::currency] = "XRP";
|
||||
auto const issue = issueFromJson(jv);
|
||||
BEAST_EXPECT(isXRP(issue));
|
||||
}
|
||||
|
||||
// Valid IOU — legitimate issuer
|
||||
{
|
||||
json::Value jv;
|
||||
jv[jss::currency] = "USD";
|
||||
jv[jss::issuer] = "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh";
|
||||
auto const issue = issueFromJson(jv);
|
||||
BEAST_EXPECT(!isXRP(issue));
|
||||
BEAST_EXPECT(issue.account != noAccount());
|
||||
}
|
||||
|
||||
// noAccount() is the MPT sentinel in binary serialization - must be
|
||||
// rejected
|
||||
try
|
||||
{
|
||||
json::Value jv;
|
||||
jv[jss::currency] = "USD";
|
||||
jv[jss::issuer] = to_string(noAccount());
|
||||
issueFromJson(jv);
|
||||
fail("noAccount() accepted as IOU issuer");
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
pass();
|
||||
}
|
||||
|
||||
// xrpAccount() is the XRP sentinel (all zeros) - must be rejected
|
||||
// as IOU issuer
|
||||
try
|
||||
{
|
||||
json::Value jv;
|
||||
jv[jss::currency] = "USD";
|
||||
jv[jss::issuer] = to_string(xrpAccount());
|
||||
issueFromJson(jv);
|
||||
fail("xrpAccount() accepted as IOU issuer");
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
pass();
|
||||
}
|
||||
|
||||
// Invalid base58 — must be rejected
|
||||
try
|
||||
{
|
||||
json::Value jv;
|
||||
jv[jss::currency] = "USD";
|
||||
jv[jss::issuer] = "not_a_valid_address";
|
||||
issueFromJson(jv);
|
||||
fail("invalid base58 accepted as IOU issuer");
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
pass();
|
||||
}
|
||||
|
||||
// Non-XRP currency with no issuer field — must be rejected
|
||||
try
|
||||
{
|
||||
json::Value jv;
|
||||
jv[jss::currency] = "USD";
|
||||
issueFromJson(jv);
|
||||
fail("missing issuer accepted");
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
pass();
|
||||
}
|
||||
|
||||
// XRP with an issuer field — must be rejected
|
||||
try
|
||||
{
|
||||
json::Value jv;
|
||||
jv[jss::currency] = "XRP";
|
||||
jv[jss::issuer] = "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh";
|
||||
issueFromJson(jv);
|
||||
fail("XRP with issuer accepted");
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
pass();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
run() override
|
||||
{
|
||||
@@ -994,9 +897,6 @@ public:
|
||||
// ---
|
||||
testIssueDomainSets();
|
||||
testIssueDomainMaps();
|
||||
|
||||
// ---
|
||||
testIssueFromJson();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -1,24 +1,15 @@
|
||||
|
||||
#include <test/jtx/Account.h>
|
||||
#include <test/jtx/Env.h>
|
||||
#include <test/jtx/amount.h> // IWYU pragma: keep
|
||||
#include <test/jtx/envconfig.h>
|
||||
|
||||
#include <xrpld/core/Config.h>
|
||||
|
||||
#include <xrpl/basics/base_uint.h>
|
||||
#include <xrpl/beast/unit_test/suite.h>
|
||||
#include <xrpl/json/json_value.h>
|
||||
#include <xrpl/json/to_string.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/Issue.h>
|
||||
#include <xrpl/protocol/SField.h>
|
||||
#include <xrpl/protocol/STIssue.h>
|
||||
#include <xrpl/protocol/Serializer.h>
|
||||
#include <xrpl/protocol/UintTypes.h>
|
||||
#include <xrpl/protocol/jss.h>
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace xrpl::test {
|
||||
|
||||
@@ -146,143 +137,12 @@ public:
|
||||
"000000000000000000000000000000000000000000000002");
|
||||
}
|
||||
|
||||
void
|
||||
testNoAccountIssuerRpc()
|
||||
{
|
||||
testcase("noAccount issuer rejected via RPC sign");
|
||||
|
||||
using namespace jtx;
|
||||
Env env{*this, envconfig([](std::unique_ptr<Config> cfg) {
|
||||
cfg->loadFromString("[signing_support]\ntrue");
|
||||
return cfg;
|
||||
})};
|
||||
|
||||
Account const alice{"alice"};
|
||||
env.fund(XRP(10000), alice);
|
||||
env.close();
|
||||
|
||||
json::Value txJson;
|
||||
txJson[jss::TransactionType] = "AMMDelete";
|
||||
txJson[jss::Account] = alice.human();
|
||||
txJson[jss::Asset][jss::currency] = "USD";
|
||||
txJson[jss::Asset][jss::issuer] = to_string(noAccount());
|
||||
txJson[jss::Asset2][jss::currency] = "XRP";
|
||||
|
||||
json::Value req;
|
||||
req[jss::tx_json] = txJson;
|
||||
req[jss::secret] = alice.name();
|
||||
|
||||
auto const result = env.rpc("json", "sign", to_string(req))[jss::result];
|
||||
|
||||
BEAST_EXPECT(result[jss::status] == "error");
|
||||
BEAST_EXPECT(result.isMember(jss::error));
|
||||
}
|
||||
|
||||
void
|
||||
testNoAccountIssuer()
|
||||
{
|
||||
testcase("noAccount issuer rejection");
|
||||
|
||||
{
|
||||
json::Value jv;
|
||||
jv[jss::currency] = "USD";
|
||||
jv[jss::issuer] = to_string(noAccount());
|
||||
|
||||
try
|
||||
{
|
||||
issueFromJson(sfAsset, jv);
|
||||
fail("issueFromJson accepted noAccount() as IOU issuer");
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
pass();
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
Serializer s;
|
||||
s.addBitString(toCurrency("USD"));
|
||||
s.addBitString(noAccount());
|
||||
SerialIter iter(s.slice());
|
||||
|
||||
try
|
||||
{
|
||||
STIssue const stissue(iter, sfAsset);
|
||||
fail(
|
||||
"STIssue deserialization of [USD][noAccount()] should "
|
||||
"throw");
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
pass();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testXrpAccountIssuerRpc()
|
||||
{
|
||||
testcase("xrpAccount issuer rejected via RPC sign");
|
||||
|
||||
using namespace jtx;
|
||||
Env env{*this, envconfig([](std::unique_ptr<Config> cfg) {
|
||||
cfg->loadFromString("[signing_support]\ntrue");
|
||||
return cfg;
|
||||
})};
|
||||
|
||||
Account const alice{"alice"};
|
||||
env.fund(XRP(10000), alice);
|
||||
env.close();
|
||||
|
||||
json::Value txJson;
|
||||
txJson[jss::TransactionType] = "AMMDelete";
|
||||
txJson[jss::Account] = alice.human();
|
||||
txJson[jss::Asset][jss::currency] = "USD";
|
||||
txJson[jss::Asset][jss::issuer] = to_string(xrpAccount());
|
||||
txJson[jss::Asset2][jss::currency] = "XRP";
|
||||
|
||||
json::Value req;
|
||||
req[jss::tx_json] = txJson;
|
||||
req[jss::secret] = alice.name();
|
||||
|
||||
auto const result = env.rpc("json", "sign", to_string(req))[jss::result];
|
||||
|
||||
BEAST_EXPECT(result[jss::status] == "error");
|
||||
BEAST_EXPECT(result.isMember(jss::error));
|
||||
}
|
||||
|
||||
void
|
||||
testXrpAccountIssuer()
|
||||
{
|
||||
testcase("xrpAccount issuer rejection");
|
||||
|
||||
{
|
||||
json::Value jv;
|
||||
jv[jss::currency] = "USD";
|
||||
jv[jss::issuer] = to_string(xrpAccount());
|
||||
|
||||
try
|
||||
{
|
||||
issueFromJson(sfAsset, jv);
|
||||
fail("issueFromJson accepted xrpAccount() as IOU issuer");
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
pass();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
run() override
|
||||
{
|
||||
// compliments other unit tests to ensure complete coverage
|
||||
testConstructor();
|
||||
testCompare();
|
||||
testNoAccountIssuerRpc();
|
||||
testNoAccountIssuer();
|
||||
testXrpAccountIssuerRpc();
|
||||
testXrpAccountIssuer();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -23,7 +23,6 @@
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/SField.h>
|
||||
#include <xrpl/protocol/Seed.h>
|
||||
#include <xrpl/protocol/TxFlags.h>
|
||||
#include <xrpl/protocol/jss.h>
|
||||
#include <xrpl/protocol/nft.h>
|
||||
@@ -33,7 +32,6 @@
|
||||
#include <cstdint>
|
||||
#include <iterator>
|
||||
#include <optional>
|
||||
#include <ranges>
|
||||
#include <vector>
|
||||
|
||||
namespace xrpl::test {
|
||||
@@ -1352,86 +1350,6 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testAccountObjectDoesntShowCancelledOffers()
|
||||
{
|
||||
testcase("AccountObjectDoesntShowCancelledOffers");
|
||||
|
||||
using namespace jtx;
|
||||
Env env(*this);
|
||||
|
||||
Account const alice{"alice"};
|
||||
Account const bob{"bob"};
|
||||
auto const eur = bob["EUR"];
|
||||
env.fund(XRP(10000), alice, bob);
|
||||
env.close();
|
||||
|
||||
auto const rpcAccountObjects = [&](std::optional<uint32_t> limit = std::nullopt) {
|
||||
json::Value params;
|
||||
params[jss::account] = alice.human();
|
||||
if (limit.has_value())
|
||||
{
|
||||
params[jss::limit] = *limit;
|
||||
}
|
||||
return env.rpc("json", "account_objects", to_string(params));
|
||||
};
|
||||
|
||||
auto const numEntries = 33;
|
||||
std::vector<uint32_t> seqs;
|
||||
seqs.reserve(numEntries);
|
||||
for ([[maybe_unused]] auto _ : std::ranges::iota_view{0, numEntries})
|
||||
{
|
||||
json::Value params;
|
||||
params[jss::secret] = toBase58(generateSeed("alice"));
|
||||
params[jss::tx_json] = offer(alice, eur(1), XRP(2));
|
||||
auto const res = env.rpc("json", "submit", to_string(params))[jss::result];
|
||||
BEAST_EXPECT(res[jss::engine_result].asString() == "tesSUCCESS");
|
||||
seqs.push_back(env.seq(alice));
|
||||
}
|
||||
|
||||
auto res = rpcAccountObjects();
|
||||
BEAST_EXPECT(res[jss::result][jss::account_objects].size() == numEntries);
|
||||
BEAST_EXPECT(not res[jss::result].isMember(jss::limit));
|
||||
BEAST_EXPECT(not res[jss::result].isMember(jss::marker));
|
||||
|
||||
for (auto const s : std::views::all(seqs) | std::views::take(numEntries - 1))
|
||||
{
|
||||
json::Value params;
|
||||
params[jss::secret] = toBase58(generateSeed("alice"));
|
||||
params[jss::tx_json] = offerCancel(alice, s - 1);
|
||||
auto const res = env.rpc("json", "submit", to_string(params))[jss::result];
|
||||
BEAST_EXPECT(res[jss::engine_result].asString() == "tesSUCCESS");
|
||||
}
|
||||
|
||||
res = rpcAccountObjects();
|
||||
BEAST_EXPECT(res[jss::result][jss::account_objects].size() == 1);
|
||||
BEAST_EXPECT(not res[jss::result].isMember(jss::limit));
|
||||
BEAST_EXPECT(not res[jss::result].isMember(jss::marker));
|
||||
|
||||
{
|
||||
json::Value params;
|
||||
params[jss::secret] = toBase58(generateSeed("alice"));
|
||||
json::Value txJson;
|
||||
txJson[jss::TransactionType] = jss::NFTokenMint;
|
||||
txJson[jss::Account] = to_string(alice.id());
|
||||
txJson["NFTokenTaxon"] = 1;
|
||||
params[jss::tx_json] = txJson;
|
||||
auto const res = env.rpc("json", "submit", to_string(params))[jss::result];
|
||||
BEAST_EXPECT(res[jss::engine_result].asString() == "tesSUCCESS");
|
||||
}
|
||||
env.close();
|
||||
|
||||
res = rpcAccountObjects();
|
||||
BEAST_EXPECT(res[jss::result][jss::account_objects].size() == 2);
|
||||
BEAST_EXPECT(not res[jss::result].isMember(jss::limit));
|
||||
BEAST_EXPECT(not res[jss::result].isMember(jss::marker));
|
||||
|
||||
res = rpcAccountObjects(1);
|
||||
BEAST_EXPECT(res[jss::result][jss::account_objects].size() == 1);
|
||||
BEAST_EXPECT(res[jss::result][jss::limit].asUInt() == 1);
|
||||
BEAST_EXPECT(res[jss::result].isMember(jss::marker));
|
||||
}
|
||||
|
||||
void
|
||||
run() override
|
||||
{
|
||||
@@ -1442,7 +1360,6 @@ public:
|
||||
testNFTsMarker();
|
||||
testAccountNFTs();
|
||||
testAccountObjectMarker();
|
||||
testAccountObjectDoesntShowCancelledOffers();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
#include <test/jtx/Env.h>
|
||||
#include <test/jtx/amount.h>
|
||||
#include <test/jtx/envconfig.h>
|
||||
#include <test/jtx/multisign.h>
|
||||
#include <test/jtx/noop.h>
|
||||
#include <test/jtx/pay.h>
|
||||
#include <test/jtx/seq.h>
|
||||
@@ -882,125 +881,6 @@ class Transaction_test : public beast::unit_test::Suite
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testSignForNetworkIDValidation()
|
||||
{
|
||||
testcase("SignFor NetworkID validation");
|
||||
using namespace test::jtx;
|
||||
|
||||
Account const owner{"owner"};
|
||||
Account const signer{"signer"};
|
||||
|
||||
auto makeConfig = [](std::uint32_t networkID) {
|
||||
return envconfig([networkID](std::unique_ptr<Config> cfg) {
|
||||
cfg->networkId = networkID;
|
||||
return cfg;
|
||||
});
|
||||
};
|
||||
|
||||
auto setupEnv = [&](Env& env) {
|
||||
env.fund(XRP(10'000), owner, signer);
|
||||
env.close();
|
||||
env(signers(owner, 1, {{signer, 1}}));
|
||||
env.close();
|
||||
};
|
||||
|
||||
auto makeTx = [&](Env& env) {
|
||||
json::Value tx;
|
||||
tx[jss::TransactionType] = jss::AccountSet;
|
||||
tx[jss::Account] = owner.human();
|
||||
tx[jss::Sequence] = env.seq(owner);
|
||||
tx[jss::Fee] = "100";
|
||||
tx[jss::SigningPubKey] = "";
|
||||
return tx;
|
||||
};
|
||||
|
||||
auto signFor = [&](Env& env, json::Value const& tx) {
|
||||
json::Value signReq;
|
||||
signReq[jss::tx_json] = tx;
|
||||
signReq[jss::account] = signer.human();
|
||||
signReq[jss::secret] = signer.name();
|
||||
return env.rpc("json", "sign_for", to_string(signReq))[jss::result];
|
||||
};
|
||||
|
||||
// Test case: NetworkID < 1024 - field is not required
|
||||
{
|
||||
Env env{*this, makeConfig(500)};
|
||||
setupEnv(env);
|
||||
|
||||
auto tx = makeTx(env);
|
||||
auto result = signFor(env, tx);
|
||||
|
||||
BEAST_EXPECT(result[jss::status] == "success");
|
||||
BEAST_EXPECT(!result[jss::tx_json].isMember(jss::NetworkID));
|
||||
}
|
||||
|
||||
// Test case: NetworkID > 1024 - missing NetworkID field
|
||||
{
|
||||
Env env{*this, makeConfig(2040)};
|
||||
setupEnv(env);
|
||||
|
||||
auto tx = makeTx(env);
|
||||
auto result = signFor(env, tx);
|
||||
|
||||
BEAST_EXPECT(result[jss::error] == "invalidParams");
|
||||
BEAST_EXPECT(result[jss::error_message] == "Missing field 'tx_json.NetworkID'.");
|
||||
}
|
||||
|
||||
// Test case: NetworkID > 1024 - NetworkID field is not a number
|
||||
{
|
||||
Env env{*this, makeConfig(2040)};
|
||||
setupEnv(env);
|
||||
|
||||
auto tx = makeTx(env);
|
||||
tx[jss::NetworkID] = "not_a_number";
|
||||
auto result = signFor(env, tx);
|
||||
|
||||
BEAST_EXPECT(result[jss::error] == "invalidParams");
|
||||
BEAST_EXPECT(result[jss::error_message] == "Invalid field 'tx_json.NetworkID'.");
|
||||
}
|
||||
|
||||
// Test case: NetworkID > 1024 - NetworkID field is not integral
|
||||
{
|
||||
Env env{*this, makeConfig(2040)};
|
||||
setupEnv(env);
|
||||
|
||||
auto tx = makeTx(env);
|
||||
tx[jss::NetworkID] = 2040.1;
|
||||
auto result = signFor(env, tx);
|
||||
|
||||
BEAST_EXPECT(result[jss::error] == "invalidParams");
|
||||
BEAST_EXPECT(result[jss::error_message] == "Invalid field 'tx_json.NetworkID'.");
|
||||
}
|
||||
|
||||
// Test case: NetworkID > 1024 - NetworkID field is different from
|
||||
// actual NetworkID
|
||||
{
|
||||
Env env{*this, makeConfig(2040)};
|
||||
setupEnv(env);
|
||||
|
||||
auto tx = makeTx(env);
|
||||
tx[jss::NetworkID] = 9999;
|
||||
auto result = signFor(env, tx);
|
||||
|
||||
BEAST_EXPECT(result[jss::error] == "invalidParams");
|
||||
BEAST_EXPECT(result[jss::error_message] == "Invalid field 'tx_json.NetworkID'.");
|
||||
}
|
||||
|
||||
// Test case: NetworkID > 1024 - NetworkID field is correct
|
||||
{
|
||||
Env env{*this, makeConfig(2040)};
|
||||
setupEnv(env);
|
||||
|
||||
auto tx = makeTx(env);
|
||||
tx[jss::NetworkID] = 2040;
|
||||
auto result = signFor(env, tx);
|
||||
|
||||
BEAST_EXPECT(result[jss::status] == "success");
|
||||
BEAST_EXPECT(result[jss::tx_json][jss::NetworkID].asUInt() == 2040);
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
void
|
||||
run() override
|
||||
@@ -1010,8 +890,6 @@ public:
|
||||
|
||||
FeatureBitset const all{testableAmendments()};
|
||||
testWithFeats(all);
|
||||
|
||||
testSignForNetworkIDValidation();
|
||||
}
|
||||
|
||||
void
|
||||
|
||||
@@ -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