Merge remote-tracking branch 'upstream/ripple/se/fees' into ripple/smart-escrow

This commit is contained in:
Mayukha Vadari
2026-01-12 14:12:04 -05:00
34 changed files with 1684 additions and 358 deletions

View File

@@ -148,15 +148,21 @@ class Batch_test : public beast::unit_test::suite
void
testEnable(FeatureBitset features)
{
testcase("enabled");
using namespace test::jtx;
using namespace std::literals;
bool const withInnerSigFix = features[fixBatchInnerSigs];
for (bool const withBatch : {true, false})
{
testcase << "enabled: Batch "
<< (withBatch ? "enabled" : "disabled")
<< ", Inner Sig Fix: "
<< (withInnerSigFix ? "enabled" : "disabled");
auto const amend = withBatch ? features : features - featureBatch;
test::jtx::Env env{*this, envconfig(), amend};
test::jtx::Env env{*this, amend};
auto const alice = Account("alice");
auto const bob = Account("bob");
@@ -179,7 +185,7 @@ class Batch_test : public beast::unit_test::suite
// tfInnerBatchTxn
// If the feature is disabled, the transaction fails with
// temINVALID_FLAG If the feature is enabled, the transaction fails
// temINVALID_FLAG. If the feature is enabled, the transaction fails
// early in checkValidity()
{
auto const txResult =
@@ -205,7 +211,7 @@ class Batch_test : public beast::unit_test::suite
//----------------------------------------------------------------------
// preflight
test::jtx::Env env{*this, envconfig()};
test::jtx::Env env{*this, features};
auto const alice = Account("alice");
auto const bob = Account("bob");
@@ -617,7 +623,7 @@ class Batch_test : public beast::unit_test::suite
//----------------------------------------------------------------------
// preclaim
test::jtx::Env env{*this, envconfig()};
test::jtx::Env env{*this, features};
auto const alice = Account("alice");
auto const bob = Account("bob");
@@ -858,7 +864,7 @@ class Batch_test : public beast::unit_test::suite
using namespace test::jtx;
using namespace std::literals;
test::jtx::Env env{*this, envconfig()};
test::jtx::Env env{*this, features};
auto const alice = Account("alice");
auto const bob = Account("bob");
@@ -949,7 +955,7 @@ class Batch_test : public beast::unit_test::suite
using namespace test::jtx;
using namespace std::literals;
test::jtx::Env env{*this, envconfig()};
test::jtx::Env env{*this, features};
auto const alice = Account("alice");
auto const bob = Account("bob");
@@ -1187,7 +1193,7 @@ class Batch_test : public beast::unit_test::suite
// Bad Fee Without Signer
{
test::jtx::Env env{*this, envconfig()};
test::jtx::Env env{*this, features};
auto const alice = Account("alice");
auto const bob = Account("bob");
@@ -1209,7 +1215,7 @@ class Batch_test : public beast::unit_test::suite
// Bad Fee With MultiSign
{
test::jtx::Env env{*this, envconfig()};
test::jtx::Env env{*this, features};
auto const alice = Account("alice");
auto const bob = Account("bob");
@@ -1236,7 +1242,7 @@ class Batch_test : public beast::unit_test::suite
// Bad Fee With MultiSign + BatchSigners
{
test::jtx::Env env{*this, envconfig()};
test::jtx::Env env{*this, features};
auto const alice = Account("alice");
auto const bob = Account("bob");
@@ -1265,7 +1271,7 @@ class Batch_test : public beast::unit_test::suite
// Bad Fee With MultiSign + BatchSigners.Signers
{
test::jtx::Env env{*this, envconfig()};
test::jtx::Env env{*this, features};
auto const alice = Account("alice");
auto const bob = Account("bob");
@@ -1297,7 +1303,7 @@ class Batch_test : public beast::unit_test::suite
// Bad Fee With BatchSigners
{
test::jtx::Env env{*this, envconfig()};
test::jtx::Env env{*this, features};
auto const alice = Account("alice");
auto const bob = Account("bob");
@@ -1321,7 +1327,7 @@ class Batch_test : public beast::unit_test::suite
// Bad Fee Dynamic Fee Calculation
{
test::jtx::Env env{*this, envconfig()};
test::jtx::Env env{*this, features};
auto const alice = Account("alice");
auto const bob = Account("bob");
@@ -1361,7 +1367,7 @@ class Batch_test : public beast::unit_test::suite
// telENV_RPC_FAILED: Batch: txns array exceeds 8 entries.
{
test::jtx::Env env{*this, envconfig()};
test::jtx::Env env{*this, features};
auto const alice = Account("alice");
auto const bob = Account("bob");
@@ -1386,7 +1392,7 @@ class Batch_test : public beast::unit_test::suite
// temARRAY_TOO_LARGE: Batch: txns array exceeds 8 entries.
{
test::jtx::Env env{*this, envconfig()};
test::jtx::Env env{*this, features};
auto const alice = Account("alice");
auto const bob = Account("bob");
@@ -1419,7 +1425,7 @@ class Batch_test : public beast::unit_test::suite
// telENV_RPC_FAILED: Batch: signers array exceeds 8 entries.
{
test::jtx::Env env{*this, envconfig()};
test::jtx::Env env{*this, features};
auto const alice = Account("alice");
auto const bob = Account("bob");
@@ -1438,7 +1444,7 @@ class Batch_test : public beast::unit_test::suite
// temARRAY_TOO_LARGE: Batch: signers array exceeds 8 entries.
{
test::jtx::Env env{*this, envconfig()};
test::jtx::Env env{*this, features};
auto const alice = Account("alice");
auto const bob = Account("bob");
@@ -1472,7 +1478,7 @@ class Batch_test : public beast::unit_test::suite
using namespace test::jtx;
using namespace std::literals;
test::jtx::Env env{*this, envconfig()};
test::jtx::Env env{*this, features};
auto const alice = Account("alice");
auto const bob = Account("bob");
@@ -1608,7 +1614,7 @@ class Batch_test : public beast::unit_test::suite
using namespace test::jtx;
using namespace std::literals;
test::jtx::Env env{*this, envconfig()};
test::jtx::Env env{*this, features};
auto const alice = Account("alice");
auto const bob = Account("bob");
@@ -1840,7 +1846,7 @@ class Batch_test : public beast::unit_test::suite
using namespace test::jtx;
using namespace std::literals;
test::jtx::Env env{*this, envconfig()};
test::jtx::Env env{*this, features};
auto const alice = Account("alice");
auto const bob = Account("bob");
@@ -2062,7 +2068,7 @@ class Batch_test : public beast::unit_test::suite
using namespace test::jtx;
using namespace std::literals;
test::jtx::Env env{*this, envconfig()};
test::jtx::Env env{*this, features};
auto const alice = Account("alice");
auto const bob = Account("bob");
@@ -2248,14 +2254,26 @@ class Batch_test : public beast::unit_test::suite
}
void
testInnerSubmitRPC(FeatureBitset features)
doTestInnerSubmitRPC(FeatureBitset features, bool withBatch)
{
testcase("inner submit rpc");
bool const withInnerSigFix = features[fixBatchInnerSigs];
std::string const testName = [&]() {
std::stringstream ss;
ss << "inner submit rpc: batch "
<< (withBatch ? "enabled" : "disabled") << ", inner sig fix: "
<< (withInnerSigFix ? "enabled" : "disabled") << ": ";
return ss.str();
}();
auto const amend = withBatch ? features : features - featureBatch;
using namespace test::jtx;
using namespace std::literals;
test::jtx::Env env{*this, envconfig()};
test::jtx::Env env{*this, amend};
if (!BEAST_EXPECT(amend[featureBatch] == withBatch))
return;
auto const alice = Account("alice");
auto const bob = Account("bob");
@@ -2263,75 +2281,170 @@ class Batch_test : public beast::unit_test::suite
env.fund(XRP(10000), alice, bob);
env.close();
auto submitAndValidate = [&](Slice const& slice) {
auto const jrr = env.rpc("submit", strHex(slice))[jss::result];
BEAST_EXPECT(
jrr[jss::status] == "error" &&
jrr[jss::error] == "invalidTransaction" &&
jrr[jss::error_exception] ==
"fails local checks: Malformed: Invalid inner batch "
"transaction.");
env.close();
};
auto submitAndValidate =
[&](std::string caseName,
Slice const& slice,
int line,
std::optional<std::string> expectedEnabled = std::nullopt,
std::optional<std::string> expectedDisabled = std::nullopt,
bool expectInvalidFlag = false) {
testcase << testName << caseName
<< (expectInvalidFlag
? " - Expected to reach tx engine!"
: "");
auto const jrr = env.rpc("submit", strHex(slice))[jss::result];
auto const expected = withBatch
? expectedEnabled.value_or(
"fails local checks: Malformed: Invalid inner batch "
"transaction.")
: expectedDisabled.value_or(
"fails local checks: Empty SigningPubKey.");
if (expectInvalidFlag)
{
expect(
jrr[jss::status] == "success" &&
jrr[jss::engine_result] == "temINVALID_FLAG",
pretty(jrr),
__FILE__,
line);
}
else
{
expect(
jrr[jss::status] == "error" &&
jrr[jss::error] == "invalidTransaction" &&
jrr[jss::error_exception] == expected,
pretty(jrr),
__FILE__,
line);
}
env.close();
};
// Invalid RPC Submission: TxnSignature
// - has `TxnSignature` field
// + has `TxnSignature` field
// - has no `SigningPubKey` field
// - has no `Signers` field
// - has `tfInnerBatchTxn` flag
// + has `tfInnerBatchTxn` flag
{
auto txn = batch::inner(pay(alice, bob, XRP(1)), env.seq(alice));
txn[sfTxnSignature] = "DEADBEEF";
STParsedJSONObject parsed("test", txn.getTxn());
Serializer s;
parsed.object->add(s);
submitAndValidate(s.slice());
submitAndValidate("TxnSignature set", s.slice(), __LINE__);
}
// Invalid RPC Submission: SigningPubKey
// - has no `TxnSignature` field
// - has `SigningPubKey` field
// + has `SigningPubKey` field
// - has no `Signers` field
// - has `tfInnerBatchTxn` flag
// + has `tfInnerBatchTxn` flag
{
auto txn = batch::inner(pay(alice, bob, XRP(1)), env.seq(alice));
txn[sfSigningPubKey] = strHex(alice.pk());
STParsedJSONObject parsed("test", txn.getTxn());
Serializer s;
parsed.object->add(s);
submitAndValidate(s.slice());
submitAndValidate(
"SigningPubKey set",
s.slice(),
__LINE__,
std::nullopt,
"fails local checks: Invalid signature.");
}
// Invalid RPC Submission: Signers
// - has no `TxnSignature` field
// - has empty `SigningPubKey` field
// - has `Signers` field
// - has `tfInnerBatchTxn` flag
// + has empty `SigningPubKey` field
// + has `Signers` field
// + has `tfInnerBatchTxn` flag
{
auto txn = batch::inner(pay(alice, bob, XRP(1)), env.seq(alice));
txn[sfSigners] = Json::arrayValue;
STParsedJSONObject parsed("test", txn.getTxn());
Serializer s;
parsed.object->add(s);
submitAndValidate(s.slice());
submitAndValidate(
"Signers set",
s.slice(),
__LINE__,
std::nullopt,
"fails local checks: Invalid Signers array size.");
}
{
// Fully signed inner batch transaction
auto const txn =
batch::inner(pay(alice, bob, XRP(1)), env.seq(alice));
auto const jt = env.jt(txn.getTxn());
STParsedJSONObject parsed("test", jt.jv);
Serializer s;
parsed.object->add(s);
submitAndValidate(
"Fully signed",
s.slice(),
__LINE__,
std::nullopt,
std::nullopt,
!withBatch);
}
// Invalid RPC Submission: tfInnerBatchTxn
// - has no `TxnSignature` field
// - has empty `SigningPubKey` field
// + has empty `SigningPubKey` field
// - has no `Signers` field
// - has `tfInnerBatchTxn` flag
// + has `tfInnerBatchTxn` flag
{
auto txn = batch::inner(pay(alice, bob, XRP(1)), env.seq(alice));
STParsedJSONObject parsed("test", txn.getTxn());
Serializer s;
parsed.object->add(s);
auto const jrr = env.rpc("submit", strHex(s.slice()))[jss::result];
BEAST_EXPECT(
jrr[jss::status] == "success" &&
jrr[jss::engine_result] == "temINVALID_FLAG");
submitAndValidate(
"No signing fields set",
s.slice(),
__LINE__,
"fails local checks: Empty SigningPubKey.",
"fails local checks: Empty SigningPubKey.",
withBatch && !withInnerSigFix);
}
env.close();
// Invalid RPC Submission: tfInnerBatchTxn pseudo-transaction
// - has no `TxnSignature` field
// + has empty `SigningPubKey` field
// - has no `Signers` field
// + has `tfInnerBatchTxn` flag
{
STTx amendTx(
ttAMENDMENT, [seq = env.closed()->header().seq + 1](auto& obj) {
obj.setAccountID(sfAccount, AccountID());
obj.setFieldH256(sfAmendment, fixBatchInnerSigs);
obj.setFieldU32(sfLedgerSequence, seq);
obj.setFieldU32(sfFlags, tfInnerBatchTxn);
});
auto txn = batch::inner(
amendTx.getJson(JsonOptions::none), env.seq(alice));
STParsedJSONObject parsed("test", txn.getTxn());
Serializer s;
parsed.object->add(s);
submitAndValidate(
"Pseudo-transaction",
s.slice(),
__LINE__,
withInnerSigFix
? "fails local checks: Empty SigningPubKey."
: "fails local checks: Cannot submit pseudo transactions.",
"fails local checks: Empty SigningPubKey.");
}
}
void
testInnerSubmitRPC(FeatureBitset features)
{
for (bool const withBatch : {true, false})
{
doTestInnerSubmitRPC(features, withBatch);
}
}
@@ -2343,7 +2456,7 @@ class Batch_test : public beast::unit_test::suite
using namespace test::jtx;
using namespace std::literals;
test::jtx::Env env{*this, envconfig()};
test::jtx::Env env{*this, features};
auto const alice = Account("alice");
auto const bob = Account("bob");
@@ -2390,7 +2503,7 @@ class Batch_test : public beast::unit_test::suite
using namespace test::jtx;
using namespace std::literals;
test::jtx::Env env{*this, envconfig()};
test::jtx::Env env{*this, features};
auto const alice = Account("alice");
auto const bob = Account("bob");
@@ -2443,7 +2556,7 @@ class Batch_test : public beast::unit_test::suite
// tfIndependent: account delete success
{
test::jtx::Env env{*this, envconfig()};
test::jtx::Env env{*this, features};
auto const alice = Account("alice");
auto const bob = Account("bob");
@@ -2484,7 +2597,7 @@ class Batch_test : public beast::unit_test::suite
// tfIndependent: account delete fails
{
test::jtx::Env env{*this, envconfig()};
test::jtx::Env env{*this, features};
auto const alice = Account("alice");
auto const bob = Account("bob");
@@ -2529,7 +2642,7 @@ class Batch_test : public beast::unit_test::suite
// tfAllOrNothing: account delete fails
{
test::jtx::Env env{*this, envconfig()};
test::jtx::Env env{*this, features};
auto const alice = Account("alice");
auto const bob = Account("bob");
@@ -2581,7 +2694,6 @@ class Batch_test : public beast::unit_test::suite
test::jtx::Env env{
*this,
envconfig(),
features | featureSingleAssetVault | featureLendingProtocol |
featureMPTokensV1};
@@ -2776,7 +2888,7 @@ class Batch_test : public beast::unit_test::suite
using namespace test::jtx;
using namespace std::literals;
test::jtx::Env env{*this, envconfig()};
test::jtx::Env env{*this, features};
auto const alice = Account("alice");
auto const bob = Account("bob");
@@ -2889,7 +3001,7 @@ class Batch_test : public beast::unit_test::suite
using namespace test::jtx;
using namespace std::literals;
test::jtx::Env env{*this, envconfig()};
test::jtx::Env env{*this, features};
auto const alice = Account("alice");
auto const bob = Account("bob");
@@ -2947,7 +3059,7 @@ class Batch_test : public beast::unit_test::suite
using namespace test::jtx;
using namespace std::literals;
test::jtx::Env env{*this, envconfig()};
test::jtx::Env env{*this, features};
auto const alice = Account("alice");
auto const bob = Account("bob");
@@ -3009,7 +3121,7 @@ class Batch_test : public beast::unit_test::suite
using namespace test::jtx;
using namespace std::literals;
test::jtx::Env env{*this, envconfig()};
test::jtx::Env env{*this, features};
auto const alice = Account("alice");
auto const bob = Account("bob");
@@ -3058,7 +3170,7 @@ class Batch_test : public beast::unit_test::suite
using namespace test::jtx;
using namespace std::literals;
test::jtx::Env env{*this, envconfig()};
test::jtx::Env env{*this, features};
auto const alice = Account("alice");
auto const bob = Account("bob");
@@ -3106,7 +3218,7 @@ class Batch_test : public beast::unit_test::suite
using namespace test::jtx;
using namespace std::literals;
test::jtx::Env env{*this, envconfig()};
test::jtx::Env env{*this, features};
auto const alice = Account("alice");
auto const bob = Account("bob");
@@ -3169,7 +3281,7 @@ class Batch_test : public beast::unit_test::suite
// overwritten by the payment in the batch transaction. Because the
// terPRE_SEQ is outside of the batch this noop transaction will ge
// reapplied in the following ledger
test::jtx::Env env{*this, envconfig()};
test::jtx::Env env{*this, features};
env.fund(XRP(10000), alice, bob, carol);
env.close();
@@ -3216,7 +3328,7 @@ class Batch_test : public beast::unit_test::suite
// IMPORTANT: The batch txn is applied first, then the noop txn.
// Because of this ordering, the noop txn is not applied and is
// overwritten by the payment in the batch transaction.
test::jtx::Env env{*this, envconfig()};
test::jtx::Env env{*this, features};
env.fund(XRP(10000), alice, bob);
env.close();
@@ -3258,7 +3370,7 @@ class Batch_test : public beast::unit_test::suite
// IMPORTANT: The batch txn is applied first, then the noop txn.
// Because of this ordering, the noop txn is not applied and is
// overwritten by the payment in the batch transaction.
test::jtx::Env env{*this, envconfig()};
test::jtx::Env env{*this, features};
env.fund(XRP(10000), alice, bob);
env.close();
@@ -3295,7 +3407,7 @@ class Batch_test : public beast::unit_test::suite
// Outer Batch terPRE_SEQ
{
test::jtx::Env env{*this, envconfig()};
test::jtx::Env env{*this, features};
env.fund(XRP(10000), alice, bob, carol);
env.close();
@@ -3353,7 +3465,7 @@ class Batch_test : public beast::unit_test::suite
// IMPORTANT: The batch txn is applied first, then the noop txn.
// Because of this ordering, the noop txn is not applied and is
// overwritten by the payment in the batch transaction.
test::jtx::Env env{*this, envconfig()};
test::jtx::Env env{*this, features};
env.fund(XRP(10000), alice, bob);
env.close();
@@ -3402,7 +3514,7 @@ class Batch_test : public beast::unit_test::suite
// IMPORTANT: The batch txn is applied first, then the noop txn.
// Because of this ordering, the noop txn is not applied and is
// overwritten by the payment in the batch transaction.
test::jtx::Env env{*this, envconfig()};
test::jtx::Env env{*this, features};
env.fund(XRP(10000), alice, bob);
env.close();
@@ -3464,7 +3576,7 @@ class Batch_test : public beast::unit_test::suite
// batch will run in the close ledger process. The batch will be
// allied and then retry this transaction in the current ledger.
test::jtx::Env env{*this, envconfig()};
test::jtx::Env env{*this, features};
env.fund(XRP(10000), alice, bob);
env.close();
@@ -3511,7 +3623,7 @@ class Batch_test : public beast::unit_test::suite
// Create Object Before Batch Txn
{
test::jtx::Env env{*this, envconfig()};
test::jtx::Env env{*this, features};
env.fund(XRP(10000), alice, bob);
env.close();
@@ -3558,7 +3670,7 @@ class Batch_test : public beast::unit_test::suite
// batch will run in the close ledger process. The batch will be
// applied and then retry this transaction in the current ledger.
test::jtx::Env env{*this, envconfig()};
test::jtx::Env env{*this, features};
env.fund(XRP(10000), alice, bob);
env.close();
@@ -3605,7 +3717,7 @@ class Batch_test : public beast::unit_test::suite
using namespace test::jtx;
using namespace std::literals;
test::jtx::Env env{*this, envconfig()};
test::jtx::Env env{*this, features};
auto const alice = Account("alice");
auto const bob = Account("bob");
@@ -3644,7 +3756,7 @@ class Batch_test : public beast::unit_test::suite
using namespace test::jtx;
using namespace std::literals;
test::jtx::Env env{*this, envconfig()};
test::jtx::Env env{*this, features};
XRPAmount const baseFee = env.current()->fees().base;
auto const alice = Account("alice");
@@ -3725,6 +3837,7 @@ class Batch_test : public beast::unit_test::suite
*this,
makeSmallQueueConfig(
{{"minimum_txn_in_ledger_standalone", "2"}}),
features,
nullptr,
beast::severities::kError};
@@ -3785,6 +3898,7 @@ class Batch_test : public beast::unit_test::suite
*this,
makeSmallQueueConfig(
{{"minimum_txn_in_ledger_standalone", "2"}}),
features,
nullptr,
beast::severities::kError};
@@ -3892,7 +4006,7 @@ class Batch_test : public beast::unit_test::suite
// delegated non atomic inner
{
test::jtx::Env env{*this, envconfig()};
test::jtx::Env env{*this, features};
auto const alice = Account("alice");
auto const bob = Account("bob");
@@ -3937,7 +4051,7 @@ class Batch_test : public beast::unit_test::suite
// delegated atomic inner
{
test::jtx::Env env{*this, envconfig()};
test::jtx::Env env{*this, features};
auto const alice = Account("alice");
auto const bob = Account("bob");
@@ -3989,7 +4103,7 @@ class Batch_test : public beast::unit_test::suite
// this also makes sure tfInnerBatchTxn won't block delegated AccountSet
// with granular permission
{
test::jtx::Env env{*this, envconfig()};
test::jtx::Env env{*this, features};
auto const alice = Account("alice");
auto const bob = Account("bob");
@@ -4038,7 +4152,7 @@ class Batch_test : public beast::unit_test::suite
// this also makes sure tfInnerBatchTxn won't block delegated
// MPTokenIssuanceSet with granular permission
{
test::jtx::Env env{*this, envconfig()};
test::jtx::Env env{*this, features};
Account alice{"alice"};
Account bob{"bob"};
env.fund(XRP(100000), alice, bob);
@@ -4094,7 +4208,7 @@ class Batch_test : public beast::unit_test::suite
// this also makes sure tfInnerBatchTxn won't block delegated TrustSet
// with granular permission
{
test::jtx::Env env{*this, envconfig()};
test::jtx::Env env{*this, features};
Account gw{"gw"};
Account alice{"alice"};
Account bob{"bob"};
@@ -4134,7 +4248,7 @@ class Batch_test : public beast::unit_test::suite
// inner transaction not authorized by the delegating account.
{
test::jtx::Env env{*this, envconfig()};
test::jtx::Env env{*this, features};
Account gw{"gw"};
Account alice{"alice"};
Account bob{"bob"};
@@ -4182,7 +4296,7 @@ class Batch_test : public beast::unit_test::suite
testcase("Validate RPC response");
using namespace jtx;
Env env(*this);
Env env(*this, features);
Account const alice("alice");
Account const bob("bob");
env.fund(XRP(10000), alice, bob);
@@ -4259,7 +4373,7 @@ class Batch_test : public beast::unit_test::suite
testBatchCalculateBaseFee(FeatureBitset features)
{
using namespace jtx;
Env env(*this);
Env env(*this, features);
Account const alice("alice");
Account const bob("bob");
Account const carol("carol");
@@ -4384,6 +4498,7 @@ public:
{
using namespace test::jtx;
auto const sa = testable_amendments();
testWithFeats(sa - fixBatchInnerSigs);
testWithFeats(sa);
}
};

View File

@@ -349,7 +349,7 @@ class HashRouter_test : public beast::unit_test::suite
h.set("hold_time", "alice");
h.set("relay_time", "bob");
auto const setup = setup_HashRouter(cfg);
// The set function ignores values that don't covert, so the
// The set function ignores values that don't convert, so the
// defaults are left unchanged
BEAST_EXPECT(setup.holdTime == 300s);
BEAST_EXPECT(setup.relayTime == 30s);

View File

@@ -2651,7 +2651,7 @@ class MPToken_test : public beast::unit_test::suite
STAmount const amt3{asset3, 10'000};
{
testcase("Test STAmount MPT arithmetics");
testcase("Test STAmount MPT arithmetic");
using namespace std::string_literals;
STAmount res = multiply(amt1, amt2, asset3);
BEAST_EXPECT(res == amt3);
@@ -2688,7 +2688,7 @@ class MPToken_test : public beast::unit_test::suite
}
{
testcase("Test MPTAmount arithmetics");
testcase("Test MPTAmount arithmetic");
MPTAmount mptAmt1{100};
MPTAmount const mptAmt2{100};
BEAST_EXPECT((mptAmt1 += mptAmt2) == MPTAmount{200});

View File

@@ -708,7 +708,7 @@ public:
void
testHeterogeneousSigners(FeatureBitset features)
{
testcase("Heterogenous Signers");
testcase("Heterogeneous Signers");
using namespace jtx;
Env env{*this, features};

View File

@@ -21,7 +21,6 @@
#include <xrpl/protocol/Protocol.h>
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/STAmount.h>
#include <xrpl/protocol/STNumber.h>
#include <xrpl/protocol/TER.h>
#include <xrpl/protocol/TxFlags.h>
#include <xrpl/protocol/XRPAmount.h>
@@ -940,25 +939,6 @@ class Vault_test : public beast::unit_test::suite
}
});
testCase([&](Env& env,
Account const& issuer,
Account const& owner,
Asset const& asset,
Vault& vault) {
testcase("clawback from self");
auto [tx, keylet] = vault.create({.owner = owner, .asset = asset});
{
auto tx = vault.clawback(
{.issuer = issuer,
.id = keylet.key,
.holder = issuer,
.amount = asset(10)});
env(tx, ter{temMALFORMED});
}
});
testCase([&](Env& env,
Account const&,
Account const& owner,
@@ -1197,11 +1177,13 @@ class Vault_test : public beast::unit_test::suite
auto [tx, keylet] = vault.create({.owner = owner, .asset = asset});
// Preclaim only checks for native assets.
if (asset.native())
{
auto tx = vault.clawback(
{.issuer = owner,
{.issuer = issuer,
.id = keylet.key,
.holder = issuer,
.holder = owner,
.amount = asset(50)});
env(tx, ter(temMALFORMED));
}
@@ -1924,8 +1906,20 @@ class Vault_test : public beast::unit_test::suite
env.close();
{
auto tx = vault.clawback(
{.issuer = owner, .id = keylet.key, .holder = depositor});
auto tx = vault.clawback({
.issuer = depositor,
.id = keylet.key,
.holder = depositor,
});
env(tx, ter(tecNO_PERMISSION));
}
{
auto tx = vault.clawback({
.issuer = owner,
.id = keylet.key,
.holder = depositor,
});
env(tx, ter(tecNO_PERMISSION));
}
});
@@ -2377,6 +2371,15 @@ class Vault_test : public beast::unit_test::suite
env(tx, ter(tecNO_AUTH));
}
{
// Cannot clawback if issuer is the holder
tx = vault.clawback(
{.issuer = issuer,
.id = keylet.key,
.holder = issuer,
.amount = asset(800)});
env(tx, ter(tecNO_PERMISSION));
}
// Clawback works
tx = vault.clawback(
{.issuer = issuer,
@@ -5243,6 +5246,542 @@ class Vault_test : public beast::unit_test::suite
});
}
void
testVaultClawbackBurnShares()
{
using namespace test::jtx;
using namespace loanBroker;
using namespace loan;
Env env(*this, beast::severities::kWarning);
auto const vaultAssetBalance = [&](Keylet const& vaultKeylet) {
auto const sleVault = env.le(vaultKeylet);
BEAST_EXPECT(sleVault != nullptr);
return std::make_pair(
sleVault->at(sfAssetsAvailable), sleVault->at(sfAssetsTotal));
};
auto const vaultShareBalance = [&](Keylet const& vaultKeylet) {
auto const sleVault = env.le(vaultKeylet);
BEAST_EXPECT(sleVault != nullptr);
auto const sleIssuance =
env.le(keylet::mptIssuance(sleVault->at(sfShareMPTID)));
BEAST_EXPECT(sleIssuance != nullptr);
return sleIssuance->at(sfOutstandingAmount);
};
auto const setupVault =
[&](PrettyAsset const& asset,
Account const& owner,
Account const& depositor) -> std::pair<Vault, Keylet> {
Vault vault{env};
auto const& [tx, vaultKeylet] =
vault.create({.owner = owner, .asset = asset});
env(tx, ter(tesSUCCESS), THISLINE);
env.close();
auto const& vaultSle = env.le(vaultKeylet);
BEAST_EXPECT(vaultSle != nullptr);
Asset share = vaultSle->at(sfShareMPTID);
env(vault.deposit(
{.depositor = depositor,
.id = vaultKeylet.key,
.amount = asset(100)}),
ter(tesSUCCESS),
THISLINE);
env.close();
auto const& [availablePreDefault, totalPreDefault] =
vaultAssetBalance(vaultKeylet);
BEAST_EXPECT(availablePreDefault == totalPreDefault);
BEAST_EXPECT(availablePreDefault == asset(100).value());
// attempt to clawback shares while there are assets fails
env(vault.clawback(
{.issuer = owner,
.id = vaultKeylet.key,
.holder = depositor,
.amount = share(0).value()}),
ter(tecNO_PERMISSION),
THISLINE);
env.close();
auto const& sharesAvailable = vaultShareBalance(vaultKeylet);
auto const& brokerKeylet =
keylet::loanbroker(owner.id(), env.seq(owner));
env(set(owner, vaultKeylet.key), THISLINE);
env.close();
auto const& loanKeylet = keylet::loan(brokerKeylet.key, 1);
// Create a simple Loan for the full amount of Vault assets
env(set(depositor, brokerKeylet.key, asset(100).value()),
loan::interestRate(TenthBips32(0)),
gracePeriod(10),
paymentInterval(120),
paymentTotal(10),
sig(sfCounterpartySignature, owner),
fee(env.current()->fees().base * 2),
ter(tesSUCCESS),
THISLINE);
env.close();
// attempt to clawback shares while there assetsAvailable == 0 and
// assetsTotal > 0 fails
env(vault.clawback(
{.issuer = owner,
.id = vaultKeylet.key,
.holder = depositor,
.amount = share(0).value()}),
ter(tecNO_PERMISSION),
THISLINE);
env.close();
env.close(std::chrono::seconds{120 + 10});
env(manage(owner, loanKeylet.key, tfLoanDefault),
ter(tesSUCCESS),
THISLINE);
auto const& [availablePostDefault, totalPostDefault] =
vaultAssetBalance(vaultKeylet);
BEAST_EXPECT(availablePostDefault == totalPostDefault);
BEAST_EXPECT(availablePostDefault == asset(0).value());
BEAST_EXPECT(vaultShareBalance(vaultKeylet) == sharesAvailable);
return std::make_pair(vault, vaultKeylet);
};
auto const testCase = [&](PrettyAsset const& asset,
std::string const& prefix,
Account const& owner,
Account const& depositor) {
{
testcase(
"VaultClawback (share) - " + prefix +
" owner asset clawback fails");
auto [vault, vaultKeylet] = setupVault(asset, owner, depositor);
env(vault.clawback({
.issuer = owner,
.id = vaultKeylet.key,
.holder = depositor,
.amount = asset(100).value(),
}),
// when asset is XRP or owner is not issuer clawback fail
// when owner is issuer precision loss occurs as vault is
// empty
asset.native() ? ter(temMALFORMED)
: asset.raw().getIssuer() != owner.id()
? ter(tecNO_PERMISSION)
: ter(tecPRECISION_LOSS),
THISLINE);
env.close();
}
{
testcase(
"VaultClawback (share) - " + prefix +
" owner incomplete share clawback fails");
auto [vault, vaultKeylet] = setupVault(asset, owner, depositor);
auto const& vaultSle = env.le(vaultKeylet);
BEAST_EXPECT(vaultSle != nullptr);
if (!vaultSle)
return;
Asset share = vaultSle->at(sfShareMPTID);
env(vault.clawback({
.issuer = owner,
.id = vaultKeylet.key,
.holder = depositor,
.amount = share(1).value(),
}),
ter(tecLIMIT_EXCEEDED),
THISLINE);
env.close();
}
{
testcase(
"VaultClawback (share) - " + prefix +
" owner implicit complete share clawback");
auto [vault, vaultKeylet] = setupVault(asset, owner, depositor);
env(vault.clawback({
.issuer = owner,
.id = vaultKeylet.key,
.holder = depositor,
}),
// when owner is issuer implicit clawback fails
asset.native() || asset.raw().getIssuer() != owner.id()
? ter(tesSUCCESS)
: ter(tecWRONG_ASSET),
THISLINE);
env.close();
}
{
testcase(
"VaultClawback (share) - " + prefix +
" owner explicit complete share clawback succeeds");
auto [vault, vaultKeylet] = setupVault(asset, owner, depositor);
auto const& vaultSle = env.le(vaultKeylet);
BEAST_EXPECT(vaultSle != nullptr);
if (!vaultSle)
return;
Asset share = vaultSle->at(sfShareMPTID);
env(vault.clawback({
.issuer = owner,
.id = vaultKeylet.key,
.holder = depositor,
.amount = share(vaultShareBalance(vaultKeylet)).value(),
}),
ter(tesSUCCESS),
THISLINE);
env.close();
}
{
testcase(
"VaultClawback (share) - " + prefix +
" owner can clawback own shares");
auto [vault, vaultKeylet] = setupVault(asset, owner, owner);
auto const& vaultSle = env.le(vaultKeylet);
BEAST_EXPECT(vaultSle != nullptr);
if (!vaultSle)
return;
Asset share = vaultSle->at(sfShareMPTID);
env(vault.clawback({
.issuer = owner,
.id = vaultKeylet.key,
.holder = owner,
.amount = share(vaultShareBalance(vaultKeylet)).value(),
}),
ter(tesSUCCESS),
THISLINE);
env.close();
}
{
testcase(
"VaultClawback (share) - " + prefix +
" empty vault share clawback fails");
auto [vault, vaultKeylet] = setupVault(asset, owner, owner);
auto const& vaultSle = env.le(vaultKeylet);
if (BEAST_EXPECT(vaultSle != nullptr))
return;
Asset share = vaultSle->at(sfShareMPTID);
env(vault.clawback({
.issuer = owner,
.id = vaultKeylet.key,
.holder = owner,
.amount = share(vaultShareBalance(vaultKeylet)).value(),
}),
ter(tesSUCCESS),
THISLINE);
// Now the vault is empty, clawback again fails
env(vault.clawback({
.issuer = owner,
.id = vaultKeylet.key,
.holder = owner,
}),
ter(tecNO_PERMISSION),
THISLINE);
env.close();
}
};
Account owner{"alice"};
Account depositor{"bob"};
Account issuer{"issuer"};
env.fund(XRP(10000), issuer, owner, depositor);
env.close();
// Test XRP
PrettyAsset xrp = xrpIssue();
testCase(xrp, "XRP", owner, depositor);
testCase(xrp, "XRP (depositor is owner)", owner, owner);
// Test IOU
PrettyAsset IOU = issuer["IOU"];
env(fset(issuer, asfAllowTrustLineClawback));
env.close();
env.trust(IOU(1000), owner);
env.trust(IOU(1000), depositor);
env(pay(issuer, owner, IOU(100)));
env(pay(issuer, depositor, IOU(100)));
env.close();
testCase(IOU, "IOU", owner, depositor);
testCase(IOU, "IOU (owner is issuer)", issuer, depositor);
// Test MPT
MPTTester mptt{env, issuer, mptInitNoFund};
mptt.create(
{.flags = tfMPTCanClawback | tfMPTCanTransfer | tfMPTCanLock});
PrettyAsset MPT = mptt.issuanceID();
mptt.authorize({.account = owner});
mptt.authorize({.account = depositor});
env(pay(issuer, owner, MPT(1000)));
env(pay(issuer, depositor, MPT(1000)));
env.close();
testCase(MPT, "MPT", owner, depositor);
testCase(MPT, "MPT (owner is issuer)", issuer, depositor);
}
void
testVaultClawbackAssets()
{
using namespace test::jtx;
using namespace loanBroker;
using namespace loan;
Env env(*this);
auto const setupVault =
[&](PrettyAsset const& asset,
Account const& owner,
Account const& depositor,
Account const& issuer) -> std::pair<Vault, Keylet> {
Vault vault{env};
auto const& [tx, vaultKeylet] =
vault.create({.owner = owner, .asset = asset});
env(tx, ter(tesSUCCESS), THISLINE);
env.close();
auto const& vaultSle = env.le(vaultKeylet);
BEAST_EXPECT(vaultSle != nullptr);
env(vault.deposit(
{.depositor = depositor,
.id = vaultKeylet.key,
.amount = asset(100)}),
ter(tesSUCCESS),
THISLINE);
env.close();
return std::make_pair(vault, vaultKeylet);
};
auto const testCase = [&](PrettyAsset const& asset,
std::string const& prefix,
Account const& owner,
Account const& depositor,
Account const& issuer) {
if (asset.native())
{
testcase(
"VaultClawback (asset) - " + prefix +
" issuer XRP clawback fails");
auto [vault, vaultKeylet] =
setupVault(asset, owner, depositor, issuer);
// If the asset is XRP, clawback with amount fails as malfored
// when asset is specified.
env(vault.clawback({
.issuer = issuer,
.id = vaultKeylet.key,
.holder = issuer,
.amount = asset(1).value(),
}),
ter(temMALFORMED),
THISLINE);
// When asset is implicit, clawback fails as no permission.
env(vault.clawback({
.issuer = issuer,
.id = vaultKeylet.key,
.holder = issuer,
}),
ter(tecNO_PERMISSION),
THISLINE);
return;
}
{
testcase(
"VaultClawback (asset) - " + prefix +
" clawback for different asset fails");
auto [vault, vaultKeylet] =
setupVault(asset, owner, depositor, issuer);
Account issuer2{"issuer2"};
PrettyAsset asset2 = issuer2["FOO"];
env(vault.clawback({
.issuer = issuer,
.id = vaultKeylet.key,
.holder = depositor,
.amount = asset2(1).value(),
}),
ter(tecWRONG_ASSET),
THISLINE);
}
{
testcase(
"VaultClawback (asset) - " + prefix +
" ambiguous owner/issuer asset clawback fails");
auto [vault, vaultKeylet] =
setupVault(asset, issuer, depositor, issuer);
env(vault.clawback({
.issuer = issuer,
.id = vaultKeylet.key,
.holder = issuer,
}),
ter(tecWRONG_ASSET),
THISLINE);
}
{
testcase(
"VaultClawback (asset) - " + prefix +
" non-issuer asset clawback fails");
auto [vault, vaultKeylet] =
setupVault(asset, owner, depositor, issuer);
env(vault.clawback({
.issuer = owner,
.id = vaultKeylet.key,
.holder = depositor,
}),
ter(tecNO_PERMISSION),
THISLINE);
env(vault.clawback({
.issuer = owner,
.id = vaultKeylet.key,
.holder = depositor,
.amount = asset(1).value(),
}),
ter(tecNO_PERMISSION),
THISLINE);
}
{
testcase(
"VaultClawback (asset) - " + prefix +
" issuer clawback from self fails");
auto [vault, vaultKeylet] =
setupVault(asset, owner, issuer, issuer);
env(vault.clawback({
.issuer = issuer,
.id = vaultKeylet.key,
.holder = issuer,
}),
ter(tecNO_PERMISSION),
THISLINE);
}
{
testcase(
"VaultClawback (asset) - " + prefix +
" issuer share clawback fails");
auto [vault, vaultKeylet] =
setupVault(asset, owner, depositor, issuer);
auto const& vaultSle = env.le(vaultKeylet);
BEAST_EXPECT(vaultSle != nullptr);
if (!vaultSle)
return;
Asset share = vaultSle->at(sfShareMPTID);
env(vault.clawback({
.issuer = issuer,
.id = vaultKeylet.key,
.holder = depositor,
.amount = share(1).value(),
}),
ter(tecNO_PERMISSION),
THISLINE);
}
{
testcase(
"VaultClawback (asset) - " + prefix +
" partial issuer asset clawback succeeds");
auto [vault, vaultKeylet] =
setupVault(asset, owner, depositor, issuer);
env(vault.clawback({
.issuer = issuer,
.id = vaultKeylet.key,
.holder = depositor,
.amount = asset(1).value(),
}),
ter(tesSUCCESS),
THISLINE);
}
{
testcase(
"VaultClawback (asset) - " + prefix +
" full issuer asset clawback succeeds");
auto [vault, vaultKeylet] =
setupVault(asset, owner, depositor, issuer);
env(vault.clawback({
.issuer = issuer,
.id = vaultKeylet.key,
.holder = depositor,
.amount = asset(100).value(),
}),
ter(tesSUCCESS),
THISLINE);
}
{
testcase(
"VaultClawback (asset) - " + prefix +
" implicit full issuer asset clawback succeeds");
auto [vault, vaultKeylet] =
setupVault(asset, owner, depositor, issuer);
env(vault.clawback({
.issuer = issuer,
.id = vaultKeylet.key,
.holder = depositor,
}),
ter(tesSUCCESS),
THISLINE);
}
};
Account owner{"alice"};
Account depositor{"bob"};
Account issuer{"issuer"};
env.fund(XRP(10000), issuer, owner, depositor);
env.close();
// Test XRP
PrettyAsset xrp = xrpIssue();
testCase(xrp, "XRP", owner, depositor, issuer);
// Test IOU
PrettyAsset IOU = issuer["IOU"];
env(fset(issuer, asfAllowTrustLineClawback));
env.close();
env.trust(IOU(1000), owner);
env.trust(IOU(1000), depositor);
env(pay(issuer, owner, IOU(1000)));
env(pay(issuer, depositor, IOU(1000)));
env.close();
testCase(IOU, "IOU", owner, depositor, issuer);
// Test MPT
MPTTester mptt{env, issuer, mptInitNoFund};
mptt.create(
{.flags = tfMPTCanClawback | tfMPTCanTransfer | tfMPTCanLock});
PrettyAsset MPT = mptt.issuanceID();
mptt.authorize({.account = owner});
mptt.authorize({.account = depositor});
env(pay(issuer, depositor, MPT(1000)));
env.close();
testCase(MPT, "MPT", owner, depositor, issuer);
}
public:
void
run() override
@@ -5261,6 +5800,8 @@ public:
testScaleIOU();
testRPC();
testDelegate();
testVaultClawbackBurnShares();
testVaultClawbackAssets();
}
};

View File

@@ -828,6 +828,135 @@ struct Wasm_test : public beast::unit_test::suite
BEAST_EXPECT(runFinishFunction(wasiPrintHex).has_value() == false);
}
void
testWasmSectionCorruption()
{
testcase("Wasm Section Corruption tests");
BEAST_EXPECT(runFinishFunction(badMagicNumberHex).has_value() == false);
BEAST_EXPECT(
runFinishFunction(badVersionNumberHex).has_value() == false);
BEAST_EXPECT(runFinishFunction(lyingHeaderHex).has_value() == false);
BEAST_EXPECT(
runFinishFunction(neverEndingNumberHex).has_value() == false);
BEAST_EXPECT(runFinishFunction(vectorLieHex).has_value() == false);
BEAST_EXPECT(
runFinishFunction(sectionOrderingHex).has_value() == false);
BEAST_EXPECT(runFinishFunction(ghostPayloadHex).has_value() == false);
BEAST_EXPECT(
runFinishFunction(junkAfterSectionHex).has_value() == false);
BEAST_EXPECT(
runFinishFunction(invalidSectionIdHex).has_value() == false);
BEAST_EXPECT(
runFinishFunction(localVariableBombHex).has_value() == false);
}
void
testStartFunctionLoop()
{
testcase("infinite loop in start function");
using namespace test::jtx;
Env env(*this);
auto wasmStr = boost::algorithm::unhex(startLoopHex);
Bytes wasm(wasmStr.begin(), wasmStr.end());
TestLedgerDataProvider ledgerDataProvider(env);
ImportVec imports;
auto& engine = WasmEngine::instance();
auto checkRes = engine.check(
wasm, "finish", {}, imports, &ledgerDataProvider, env.journal);
BEAST_EXPECTS(
checkRes == tesSUCCESS, std::to_string(TERtoInt(checkRes)));
auto re = engine.run(
wasm,
ESCROW_FUNCTION_NAME,
{},
imports,
&ledgerDataProvider,
1'000'000,
env.journal);
BEAST_EXPECTS(
re.error() == tecFAILED_PROCESSING,
std::to_string(TERtoInt(re.error())));
}
void
testBadAlloc()
{
testcase("Wasm Bad Alloc");
// bad_alloc.c
auto wasmStr = boost::algorithm::unhex(badAllocHex);
Bytes wasm(wasmStr.begin(), wasmStr.end());
using namespace test::jtx;
Env env{*this};
TestLedgerDataProvider hf(env);
// ImportVec imports;
uint8_t buf1[8] = {7, 8, 9, 10, 11, 12, 13, 14};
{ // forged "allocate" return valid address
std::vector<WasmParam> params = {
{.type = WT_U8V,
.of = {.u8v = {.d = buf1, .sz = sizeof(buf1)}}}};
auto& engine = WasmEngine::instance();
auto re = engine.run(
wasm, "test", params, {}, &hf, 1'000'000, env.journal);
if (BEAST_EXPECT(re))
{
BEAST_EXPECTS(re->result == 7, std::to_string(re->result));
BEAST_EXPECTS(re->cost == 10, std::to_string(re->result));
}
}
{ // return 0 whithout calling wasm
std::vector<WasmParam> params = {
{.type = WT_U8V, .of = {.u8v = {.d = buf1, .sz = 0}}}};
auto& engine = WasmEngine::instance();
auto re = engine.run(
wasm, "test", params, {}, &hf, 1'000'000, env.journal);
BEAST_EXPECT(!re) &&
BEAST_EXPECT(re.error() == tecFAILED_PROCESSING);
}
{ // forged "allocate" return 8Mb (which is more than memory limit)
std::vector<WasmParam> params = {
{.type = WT_U8V, .of = {.u8v = {.d = buf1, .sz = 1}}}};
auto& engine = WasmEngine::instance();
auto re = engine.run(
wasm, "test", params, {}, &hf, 1'000'000, env.journal);
BEAST_EXPECT(!re) &&
BEAST_EXPECT(re.error() == tecFAILED_PROCESSING);
}
{ // forged "allocate" return 0
std::vector<WasmParam> params = {
{.type = WT_U8V, .of = {.u8v = {.d = buf1, .sz = 2}}}};
auto& engine = WasmEngine::instance();
auto re = engine.run(
wasm, "test", params, {}, &hf, 1'000'000, env.journal);
BEAST_EXPECT(!re) &&
BEAST_EXPECT(re.error() == tecFAILED_PROCESSING);
}
{ // forged "allocate" return -1
std::vector<WasmParam> params = {
{.type = WT_U8V, .of = {.u8v = {.d = buf1, .sz = 3}}}};
auto& engine = WasmEngine::instance();
auto re = engine.run(
wasm, "test", params, {}, &hf, 1'000'000, env.journal);
BEAST_EXPECT(!re) &&
BEAST_EXPECT(re.error() == tecFAILED_PROCESSING);
}
env.close();
}
void
run() override
{
@@ -854,6 +983,11 @@ struct Wasm_test : public beast::unit_test::suite
testWasmProposal();
testWasmTrap();
testWasmWasi();
testWasmSectionCorruption();
testStartFunctionLoop();
testBadAlloc();
// perfTest();
}
};

View File

@@ -0,0 +1,27 @@
#include <stdint.h>
char buf[1024];
void*
allocate(int sz)
{
if (!sz)
return 0;
if (sz == 1)
return ((void*)(8 * 1024 * 1024));
if (sz == 2)
return 0;
if (sz == 3)
return ((void*)(0xFFFFFFFF));
return &buf[sz];
}
int32_t
test(char* p, int32_t sz)
{
if (!sz)
return 0;
return p[0];
}

View File

@@ -1209,6 +1209,122 @@ extern std::string const wasiPrintHex =
"45047f410105417f0b0b0b1e030041100b0648656c6c6f0a0041000b04100000000041040b"
"0406000000";
// The following several wasm hex strings are for testing wasm section
// corruption cases. They are illegal hence do not have corresponding
// rust or wat sources.
// Wasm code magic number is "0061736d", and the only valid version is 1.
extern std::string const badMagicNumberHex = "1061736d01000000";
extern std::string const badVersionNumberHex = "0061736d02000000";
// Corruption Test: lyingHeader
// Scenario: A section declares it is 2GB long, but the file ends immediately.
// Attack: Buffer pre-allocation DoS (OOM).
// # Magic (00 61 73 6d) + Version (01 00 00 00)
// data = b'\x00\x61\x73\x6d\x01\x00\x00\x00'
// # Type Section (ID 1)
// # Size: LEB128 encoded 2GB (0x80 0x80 0x80 0x80 0x08)
// data += b'\x01\x80\x80\x80\x80\x08'
extern std::string const lyingHeaderHex = "0061736d01000000018080808008";
// Corruption Test: neverEndingNumber
// Scenario: An LEB128 integer that never has a stop bit (byte < 0x80).
// Attack: Infinite loop in parser or read out of bounds.
// data = b'\x00\x61\x73\x6d\x01\x00\x00\x00'
// # Type Section (ID 1), Size 5
// data += b'\x01\x05'
// # Vector count: Infinite stream of 0x80 (100 bytes)
// data += b'\x80' * 100
extern std::string const neverEndingNumberHex =
"0061736d010000000105808080808080808080808080808080808080808080808080808080"
"80808080808080808080808080808080808080808080808080808080808080808080808080"
"808080808080808080808080808080808080808080808080808080808080808080808080";
// Corruption Test: vectorLie
// Scenario: A vector declares it has 4 billion items, but provides none.
// Attack: Vector pre-allocation DoS (OOM).
// data = b'\x00\x61\x73\x6d\x01\x00\x00\x00'
// # Type Section (ID 1)
// # Size 5 (just enough for the count bytes)
// data += b'\x01\x05'
// # Vector Count: 0xFF 0xFF 0xFF 0xFF 0x0F (4,294,967,295 items)
// data += b'\xff\xff\xff\xff\x0f'
// # No actual items follow...
extern std::string const vectorLieHex = "0061736d010000000105ffffffff0f";
// Corruption Test: sectionOrdering
// Scenario: Sections appear out of order
// (Code section before Function section).
// Attack: Parser state confusion / potential null pointer deref.
// data = b'\x00\x61\x73\x6d\x01\x00\x00\x00'
// # Code Section (ID 10) - usually last
// # Size 2, Count 0
// data += b'\x0a\x02\x00\x0b'
// # Function Section (ID 3) - usually 3rd
// data += b'\x03\x02\x00\x00'
extern std::string const sectionOrderingHex =
"0061736d010000000a02000b03020000";
// Corruption Test: ghostPayload
// Scenario: Valid headers, but file is truncated in the middle of a payload.
// Attack: Read out of bounds panic.
// data = b'\x00\x61\x73\x6d\x01\x00\x00\x00'
// # Type Section (ID 1), Size 10
// data += b'\x01\x0a'
// # Content: Count 1
// data += b'\x01'
// # Start of a type definition (0x60 = func)
// data += b'\x60'
// # File ends abruptly here (missing params/results)
extern std::string const ghostPayloadHex = "0061736d01000000010a0160";
// Corruption Test: junkAfterSection
// Scenario: Section declares size X, but logical content finishes at X-5.
// Attack: Validation bypass if parser stops early,
// or panic if strict check missing.
// data = b'\x00\x61\x73\x6d\x01\x00\x00\x00'
// # Type Section (ID 1), Size 10 bytes
// data += b'\x01\x0a'
// # Real content: Count 1, (func -> void) = 4 bytes
// # \x01 (count) \x60 (func) \x00 (0 params) \x00 (0 results)
// data += b'\x01\x60\x00\x00'
// # Remaining 6 bytes are junk padding within the section size
// data += b'\x00' * 6
extern std::string const junkAfterSectionHex =
"0061736d01000000010a01600000000000000000";
// Corruption Test: invalidSectionId
// Scenario: A section ID that doesn't exist (0xFF).
// Attack: Default case handling / unhandled enum variant.
// data = b'\x00\x61\x73\x6d\x01\x00\x00\x00'
// # Section ID 0xFF, Size 1
// data += b'\xff\x01\x00'
extern std::string const invalidSectionIdHex = "0061736d01000000ff0100";
// Corruption Test: localVariableBomb
// Scenario: A function declares 4 billion local variables.
// Attack: Stack Overflow / OOM during function init (memset).
// data = b'\x00\x61\x73\x6d\x01\x00\x00\x00'
// # 1. Type Section: (func) -> ()
// data += b'\x01\x04\x01\x60\x00\x00'
// # 3. Function Section: 1 function of type 0
// data += b'\x03\x02\x01\x00'
// # 10. Code Section
// # ID 10, Size 15 (estimated), Count 1
// data += b'\x0a\x0f\x01'
// # Function Body Size: 13 bytes
// data += b'\x0d'
// # Local Declarations Count: 1 entry
// data += b'\x01'
// # The Bomb: 4,294,967,295 locals of type i32
// # Count: 0xFF 0xFF 0xFF 0xFF 0x0F
// # Type: 0x7F (i32)
// data += b'\xff\xff\xff\xff\x0f\x7f'
// # Instruction: end (0x0b)
// data += b'\x0b'
extern std::string const localVariableBombHex =
"0061736d01000000010401600000030201000a0f010d01ffffffff0f7f0b";
extern std::string const infiniteLoopWasmHex =
"0061736d010000000108026000006000017f030302000105030100020638097f004180080b"
"7f004180080b7f004180080b7f00418088040b7f004180080b7f00418088040b7f00418080"
@@ -1224,6 +1340,27 @@ extern std::string const infiniteLoopWasmHex =
"303861373930636664623432626432343732302900490f7461726765745f66656174757265"
"73042b0f6d757461626c652d676c6f62616c732b087369676e2d6578742b0f726566657265"
"6e63652d74797065732b0a6d756c746976616c7565";
extern std::string const startLoopHex =
"0061736d010000000108026000006000017f03030200010712020573746172740000066669"
"6e69736800010801000a0e02070003400c000b0b040041010b";
extern std::string const badAllocHex =
"0061736d01000000010f0360000060017f017f60027f7f017f030403000102050301000206"
"3e0a7f004180080b7f004180080b7f004180100b7f004180100b7f00418090040b7f004180"
"080b7f00418090040b7f00418080080b7f0041000b7f0041010b07b9010e066d656d6f7279"
"0200115f5f7761736d5f63616c6c5f63746f7273000008616c6c6f63617465000103627566"
"0300047465737400020c5f5f64736f5f68616e646c6503010a5f5f646174615f656e640302"
"0b5f5f737461636b5f6c6f7703030c5f5f737461636b5f6869676803040d5f5f676c6f6261"
"6c5f6261736503050b5f5f686561705f6261736503060a5f5f686561705f656e6403070d5f"
"5f6d656d6f72795f6261736503080c5f5f7461626c655f6261736503090a420302000b2c01"
"017f024002400240024020000e0403000301020b41808080040f0b417f0f0b20004180086a"
"21010b20010b1000200145044041000f0b20002c00000b007f0970726f647563657273010c"
"70726f6365737365642d62790105636c616e675f31392e312e352d776173692d73646b2028"
"68747470733a2f2f6769746875622e636f6d2f6c6c766d2f6c6c766d2d70726f6a65637420"
"61623462356132646235383239353861663165653330386137393063666462343262643234"
"3732302900490f7461726765745f6665617475726573042b0f6d757461626c652d676c6f62"
"616c732b087369676e2d6578742b0f7265666572656e63652d74797065732b0a6d756c7469"
"76616c7565";
extern std::string const updateDataWasmHex =
"0061736d01000000010e0360027f7f017f6000006000017f02130103656e760b7570646174"

View File

@@ -65,6 +65,20 @@ extern std::string const trapFuncSigMismatchHex;
extern std::string const wasiGetTimeHex;
extern std::string const wasiPrintHex;
extern std::string const badMagicNumberHex;
extern std::string const badVersionNumberHex;
extern std::string const lyingHeaderHex;
extern std::string const neverEndingNumberHex;
extern std::string const vectorLieHex;
extern std::string const sectionOrderingHex;
extern std::string const ghostPayloadHex;
extern std::string const junkAfterSectionHex;
extern std::string const invalidSectionIdHex;
extern std::string const localVariableBombHex;
extern std::string const infiniteLoopWasmHex;
extern std::string const startLoopHex;
extern std::string const badAllocHex;
extern std::string const updateDataWasmHex;

View File

@@ -0,0 +1,22 @@
(module
;; Function 1: The Infinite Loop
(func $run_forever
(loop $infinite
br $infinite
)
)
;; Function 2: Finish
(func $finish (result i32)
i32.const 1
)
;; 1. EXPORT the functions (optional, if you want to call them later)
(export "start" (func $run_forever))
(export "finish" (func $finish))
;; 2. The special start section
;; This tells the VM: "Run function $run_forever immediately
;; when this module is instantiated."
(start $run_forever)
)

View File

@@ -396,7 +396,7 @@ public:
// This checks that partialDelete has run to completion
// before the destructor is called. A sleep is inserted
// inside the partial delete to make sure the destructor is
// given an opportunity to run durring partial delete.
// given an opportunity to run during partial delete.
BEAST_EXPECT(cur == partiallyDeleted);
}
if (next == partiallyDeletedStarted)

View File

@@ -1,6 +1,8 @@
#include <xrpl/beast/core/CurrentThreadName.h>
#include <xrpl/beast/unit_test.h>
#include <boost/predef/os.h>
#include <thread>
namespace xrpl {
@@ -37,33 +39,71 @@ private:
if (beast::getCurrentThreadName() == myName)
*state = 2;
}
#if BOOST_OS_LINUX
// Helper function to test a specific name.
// It creates a thread, sets the name, and checks if the OS-level
// name matches the expected (potentially truncated) name.
void
testName(std::string const& nameToSet, std::string const& expectedName)
{
std::thread t([&] {
beast::setCurrentThreadName(nameToSet);
// Initialize buffer to be safe.
char actualName[beast::maxThreadNameLength + 1] = {};
pthread_getname_np(pthread_self(), actualName, sizeof(actualName));
BEAST_EXPECT(std::string(actualName) == expectedName);
});
t.join();
}
#endif
public:
void
run() override
{
// Make two different threads with two different names. Make sure
// that the expected thread names are still there when the thread
// exits.
std::atomic<bool> stop{false};
// Make two different threads with two different names.
// Make sure that the expected thread names are still there
// when the thread exits.
{
std::atomic<bool> stop{false};
std::atomic<int> stateA{0};
std::thread tA(exerciseName, "tA", &stop, &stateA);
std::atomic<int> stateA{0};
std::thread tA(exerciseName, "tA", &stop, &stateA);
std::atomic<int> stateB{0};
std::thread tB(exerciseName, "tB", &stop, &stateB);
std::atomic<int> stateB{0};
std::thread tB(exerciseName, "tB", &stop, &stateB);
// Wait until both threads have set their names.
while (stateA == 0 || stateB == 0)
;
// Wait until both threads have set their names.
while (stateA == 0 || stateB == 0)
;
stop = true;
tA.join();
tB.join();
stop = true;
tA.join();
tB.join();
// Both threads should still have the expected name when they exit.
BEAST_EXPECT(stateA == 2);
BEAST_EXPECT(stateB == 2);
// Both threads should still have the expected name when they exit.
BEAST_EXPECT(stateA == 2);
BEAST_EXPECT(stateB == 2);
}
#if BOOST_OS_LINUX
// On Linux, verify that thread names longer than 15 characters
// are truncated to 15 characters (the 16th character is reserved for
// the null terminator).
{
testName(
"123456789012345",
"123456789012345"); // 15 chars, no truncation
testName(
"1234567890123456", "123456789012345"); // 16 chars, truncated
testName(
"ThisIsAVeryLongThreadNameExceedingLimit",
"ThisIsAVeryLong"); // 39 chars, truncated
testName("", ""); // empty name
testName("short", "short"); // short name, no truncation
}
#endif
}
};