mirror of
https://github.com/XRPLF/rippled.git
synced 2026-04-29 15:37:57 +00:00
Merge remote-tracking branch 'upstream/ripple/se/fees' into ripple/smart-escrow
This commit is contained in:
@@ -1,7 +1,5 @@
|
||||
#include <xrpl/beast/core/CurrentThreadName.h>
|
||||
|
||||
#include <boost/predef.h>
|
||||
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
@@ -73,12 +71,32 @@ setCurrentThreadNameImpl(std::string_view name)
|
||||
#if BOOST_OS_LINUX
|
||||
#include <pthread.h>
|
||||
|
||||
#include <iostream>
|
||||
|
||||
namespace beast::detail {
|
||||
|
||||
inline void
|
||||
setCurrentThreadNameImpl(std::string_view name)
|
||||
{
|
||||
pthread_setname_np(pthread_self(), name.data());
|
||||
// truncate and set the thread name.
|
||||
char boundedName[maxThreadNameLength + 1];
|
||||
std::snprintf(
|
||||
boundedName,
|
||||
sizeof(boundedName),
|
||||
"%.*s",
|
||||
static_cast<int>(maxThreadNameLength),
|
||||
name.data());
|
||||
|
||||
pthread_setname_np(pthread_self(), boundedName);
|
||||
|
||||
#ifdef TRUNCATED_THREAD_NAME_LOGS
|
||||
if (name.size() > maxThreadNameLength)
|
||||
{
|
||||
std::cerr << "WARNING: Thread name \"" << name << "\" (length "
|
||||
<< name.size() << ") exceeds maximum of "
|
||||
<< maxThreadNameLength << " characters on Linux.\n";
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace beast::detail
|
||||
|
||||
@@ -77,7 +77,7 @@ deriveDeterministicRootKey(Seed const& seed)
|
||||
std::array<std::uint8_t, 20> buf;
|
||||
std::copy(seed.begin(), seed.end(), buf.begin());
|
||||
|
||||
// The odds that this loop executes more than once are neglible
|
||||
// The odds that this loop executes more than once are negligible
|
||||
// but *just* in case someone managed to generate a key that required
|
||||
// more iterations loop a few times.
|
||||
for (std::uint32_t seq = 0; seq != 128; ++seq)
|
||||
@@ -137,7 +137,7 @@ private:
|
||||
std::copy(generator_.begin(), generator_.end(), buf.begin());
|
||||
copy_uint32(buf.data() + 33, seq);
|
||||
|
||||
// The odds that this loop executes more than once are neglible
|
||||
// The odds that this loop executes more than once are negligible
|
||||
// but we impose a maximum limit just in case.
|
||||
for (std::uint32_t subseq = 0; subseq != 128; ++subseq)
|
||||
{
|
||||
|
||||
@@ -140,7 +140,7 @@ private:
|
||||
void
|
||||
run()
|
||||
{
|
||||
beast::setCurrentThreadName("Resource::Manager");
|
||||
beast::setCurrentThreadName("Resource::Mngr");
|
||||
for (;;)
|
||||
{
|
||||
logic_.periodicActivity();
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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});
|
||||
|
||||
@@ -708,7 +708,7 @@ public:
|
||||
void
|
||||
testHeterogeneousSigners(FeatureBitset features)
|
||||
{
|
||||
testcase("Heterogenous Signers");
|
||||
testcase("Heterogeneous Signers");
|
||||
|
||||
using namespace jtx;
|
||||
Env env{*this, features};
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
};
|
||||
|
||||
27
src/test/app/wasm_fixtures/bad_alloc.c
Normal file
27
src/test/app/wasm_fixtures/bad_alloc.c
Normal 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];
|
||||
}
|
||||
@@ -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"
|
||||
|
||||
@@ -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;
|
||||
|
||||
22
src/test/app/wasm_fixtures/wat/start_loop.wat
Normal file
22
src/test/app/wasm_fixtures/wat/start_loop.wat
Normal 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)
|
||||
)
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -1681,7 +1681,7 @@ NetworkOPsImp::apply(std::unique_lock<std::mutex>& batchLock)
|
||||
// only be set if the Batch feature is enabled. If Batch is
|
||||
// not enabled, the flag is always invalid, so don't relay
|
||||
// it regardless.
|
||||
!sttx.isFlag(tfInnerBatchTxn))
|
||||
!(sttx.isFlag(tfInnerBatchTxn)))
|
||||
{
|
||||
protocol::TMTransaction tx;
|
||||
Serializer s;
|
||||
|
||||
@@ -95,6 +95,7 @@ hasPrivilege(STTx const& tx, Privilege priv)
|
||||
switch (tx.getTxnType())
|
||||
{
|
||||
#include <xrpl/protocol/detail/transactions.macro>
|
||||
|
||||
// Deprecated types
|
||||
default:
|
||||
return false;
|
||||
@@ -2622,6 +2623,7 @@ ValidVault::Vault::make(SLE const& from)
|
||||
self.key = from.key();
|
||||
self.asset = from.at(sfAsset);
|
||||
self.pseudoId = from.getAccountID(sfAccount);
|
||||
self.owner = from.at(sfOwner);
|
||||
self.shareMPTID = from.getFieldH192(sfShareMPTID);
|
||||
self.assetsTotal = from.at(sfAssetsTotal);
|
||||
self.assetsAvailable = from.at(sfAssetsAvailable);
|
||||
@@ -3066,6 +3068,10 @@ ValidVault::finalize(
|
||||
: std::nullopt;
|
||||
};
|
||||
|
||||
auto const vaultHoldsNoAssets = [&](Vault const& vault) {
|
||||
return vault.assetsAvailable == 0 && vault.assetsTotal == 0;
|
||||
};
|
||||
|
||||
// Technically this does not need to be a lambda, but it's more
|
||||
// convenient thanks to early "return false"; the not-so-nice
|
||||
// alternatives are several layers of nested if/else or more complex
|
||||
@@ -3448,29 +3454,56 @@ ValidVault::finalize(
|
||||
if (vaultAsset.native() ||
|
||||
vaultAsset.getIssuer() != tx[sfAccount])
|
||||
{
|
||||
JLOG(j.fatal()) << //
|
||||
"Invariant failed: clawback may only be performed by "
|
||||
"the asset issuer";
|
||||
return false; // That's all we can do
|
||||
// The owner can use clawback to force-burn shares when the
|
||||
// vault is empty but there are outstanding shares
|
||||
if (!(beforeShares && beforeShares->sharesTotal > 0 &&
|
||||
vaultHoldsNoAssets(beforeVault) &&
|
||||
beforeVault.owner == tx[sfAccount]))
|
||||
{
|
||||
JLOG(j.fatal()) << //
|
||||
"Invariant failed: clawback may only be performed "
|
||||
"by the asset issuer, or by the vault owner of an "
|
||||
"empty vault";
|
||||
return false; // That's all we can do
|
||||
}
|
||||
}
|
||||
|
||||
auto const vaultDeltaAssets = deltaAssets(afterVault.pseudoId);
|
||||
if (vaultDeltaAssets)
|
||||
{
|
||||
if (*vaultDeltaAssets >= zero)
|
||||
{
|
||||
JLOG(j.fatal()) << //
|
||||
"Invariant failed: clawback must decrease vault "
|
||||
"balance";
|
||||
result = false;
|
||||
}
|
||||
|
||||
if (!vaultDeltaAssets)
|
||||
if (beforeVault.assetsTotal + *vaultDeltaAssets !=
|
||||
afterVault.assetsTotal)
|
||||
{
|
||||
JLOG(j.fatal()) << //
|
||||
"Invariant failed: clawback and assets outstanding "
|
||||
"must add up";
|
||||
result = false;
|
||||
}
|
||||
|
||||
if (beforeVault.assetsAvailable + *vaultDeltaAssets !=
|
||||
afterVault.assetsAvailable)
|
||||
{
|
||||
JLOG(j.fatal()) << //
|
||||
"Invariant failed: clawback and assets available "
|
||||
"must add up";
|
||||
result = false;
|
||||
}
|
||||
}
|
||||
else if (!vaultHoldsNoAssets(beforeVault))
|
||||
{
|
||||
JLOG(j.fatal()) << //
|
||||
"Invariant failed: clawback must change vault balance";
|
||||
return false; // That's all we can do
|
||||
}
|
||||
|
||||
if (*vaultDeltaAssets >= zero)
|
||||
{
|
||||
JLOG(j.fatal()) << //
|
||||
"Invariant failed: clawback must decrease vault "
|
||||
"balance";
|
||||
result = false;
|
||||
}
|
||||
|
||||
auto const accountDeltaShares = deltaShares(tx[sfHolder]);
|
||||
if (!accountDeltaShares)
|
||||
{
|
||||
@@ -3503,24 +3536,6 @@ ValidVault::finalize(
|
||||
result = false;
|
||||
}
|
||||
|
||||
if (beforeVault.assetsTotal + *vaultDeltaAssets !=
|
||||
afterVault.assetsTotal)
|
||||
{
|
||||
JLOG(j.fatal()) << //
|
||||
"Invariant failed: clawback and assets outstanding "
|
||||
"must add up";
|
||||
result = false;
|
||||
}
|
||||
|
||||
if (beforeVault.assetsAvailable + *vaultDeltaAssets !=
|
||||
afterVault.assetsAvailable)
|
||||
{
|
||||
JLOG(j.fatal()) << //
|
||||
"Invariant failed: clawback and assets available must "
|
||||
"add up";
|
||||
result = false;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
@@ -861,6 +861,7 @@ class ValidVault
|
||||
uint256 key = beast::zero;
|
||||
Asset asset = {};
|
||||
AccountID pseudoId = {};
|
||||
AccountID owner = {};
|
||||
uint192 shareMPTID = beast::zero;
|
||||
Number assetsTotal = 0;
|
||||
Number assetsAvailable = 0;
|
||||
|
||||
@@ -204,8 +204,14 @@ Transactor::preflight2(PreflightContext const& ctx)
|
||||
// regardless of success or failure
|
||||
return *ret;
|
||||
|
||||
// It should be impossible for the InnerBatchTxn flag to be set without
|
||||
// featureBatch being enabled
|
||||
XRPL_ASSERT_PARTS(
|
||||
!ctx.tx.isFlag(tfInnerBatchTxn) || ctx.rules.enabled(featureBatch),
|
||||
"xrpl::Transactor::preflight2",
|
||||
"InnerBatch flag only set if feature enabled");
|
||||
// Skip signature check on batch inner transactions
|
||||
if (ctx.tx.isFlag(tfInnerBatchTxn) && !ctx.rules.enabled(featureBatch))
|
||||
if (ctx.tx.isFlag(tfInnerBatchTxn) && ctx.rules.enabled(featureBatch))
|
||||
return tesSUCCESS;
|
||||
// Do not add any checks after this point that are relevant for
|
||||
// batch inner transactions. They will be skipped.
|
||||
|
||||
@@ -1,18 +1,17 @@
|
||||
#include <xrpld/app/tx/detail/VaultClawback.h>
|
||||
|
||||
//
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
#include <xrpl/ledger/View.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/MPTIssue.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 <optional>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
NotTEC
|
||||
VaultClawback::preflight(PreflightContext const& ctx)
|
||||
{
|
||||
@@ -22,15 +21,6 @@ VaultClawback::preflight(PreflightContext const& ctx)
|
||||
return temMALFORMED;
|
||||
}
|
||||
|
||||
AccountID const issuer = ctx.tx[sfAccount];
|
||||
AccountID const holder = ctx.tx[sfHolder];
|
||||
|
||||
if (issuer == holder)
|
||||
{
|
||||
JLOG(ctx.j.debug()) << "VaultClawback: issuer cannot be holder.";
|
||||
return temMALFORMED;
|
||||
}
|
||||
|
||||
auto const amount = ctx.tx[~sfAmount];
|
||||
if (amount)
|
||||
{
|
||||
@@ -42,17 +32,27 @@ VaultClawback::preflight(PreflightContext const& ctx)
|
||||
JLOG(ctx.j.debug()) << "VaultClawback: cannot clawback XRP.";
|
||||
return temMALFORMED;
|
||||
}
|
||||
else if (amount->asset().getIssuer() != issuer)
|
||||
{
|
||||
JLOG(ctx.j.debug())
|
||||
<< "VaultClawback: only asset issuer can clawback.";
|
||||
return temMALFORMED;
|
||||
}
|
||||
}
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
[[nodiscard]] STAmount
|
||||
clawbackAmount(
|
||||
std::shared_ptr<SLE const> const& vault,
|
||||
std::optional<STAmount> const& maybeAmount,
|
||||
AccountID const& account)
|
||||
{
|
||||
if (maybeAmount)
|
||||
return *maybeAmount;
|
||||
|
||||
Asset const share = MPTIssue{vault->at(sfShareMPTID)};
|
||||
if (account == vault->at(sfOwner))
|
||||
return STAmount{share};
|
||||
|
||||
return STAmount{vault->at(sfAsset)};
|
||||
}
|
||||
|
||||
TER
|
||||
VaultClawback::preclaim(PreclaimContext const& ctx)
|
||||
{
|
||||
@@ -60,61 +60,264 @@ VaultClawback::preclaim(PreclaimContext const& ctx)
|
||||
if (!vault)
|
||||
return tecNO_ENTRY;
|
||||
|
||||
auto account = ctx.tx[sfAccount];
|
||||
auto const issuer = ctx.view.read(keylet::account(account));
|
||||
if (!issuer)
|
||||
Asset const vaultAsset = vault->at(sfAsset);
|
||||
auto const account = ctx.tx[sfAccount];
|
||||
auto const holder = ctx.tx[sfHolder];
|
||||
auto const maybeAmount = ctx.tx[~sfAmount];
|
||||
auto const mptIssuanceID = vault->at(sfShareMPTID);
|
||||
auto const sleShareIssuance =
|
||||
ctx.view.read(keylet::mptIssuance(mptIssuanceID));
|
||||
if (!sleShareIssuance)
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
JLOG(ctx.j.error()) << "VaultClawback: missing issuer account.";
|
||||
JLOG(ctx.j.error())
|
||||
<< "VaultClawback: missing issuance of vault shares.";
|
||||
return tefINTERNAL;
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
Asset const vaultAsset = vault->at(sfAsset);
|
||||
if (auto const amount = ctx.tx[~sfAmount];
|
||||
amount && vaultAsset != amount->asset())
|
||||
Asset const share = MPTIssue{mptIssuanceID};
|
||||
|
||||
// Ambiguous case: If Issuer is Owner they must specify the asset
|
||||
if (!maybeAmount && !vaultAsset.native() &&
|
||||
vaultAsset.getIssuer() == vault->at(sfOwner))
|
||||
{
|
||||
JLOG(ctx.j.debug())
|
||||
<< "VaultClawback: must specify amount when issuer is owner.";
|
||||
return tecWRONG_ASSET;
|
||||
|
||||
if (vaultAsset.native())
|
||||
{
|
||||
JLOG(ctx.j.debug()) << "VaultClawback: cannot clawback XRP.";
|
||||
return tecNO_PERMISSION; // Cannot clawback XRP.
|
||||
}
|
||||
else if (vaultAsset.getIssuer() != account)
|
||||
{
|
||||
JLOG(ctx.j.debug()) << "VaultClawback: only asset issuer can clawback.";
|
||||
return tecNO_PERMISSION; // Only issuers can clawback.
|
||||
}
|
||||
|
||||
if (vaultAsset.holds<MPTIssue>())
|
||||
{
|
||||
auto const mpt = vaultAsset.get<MPTIssue>();
|
||||
auto const mptIssue =
|
||||
ctx.view.read(keylet::mptIssuance(mpt.getMptID()));
|
||||
if (mptIssue == nullptr)
|
||||
return tecOBJECT_NOT_FOUND;
|
||||
auto const amount = clawbackAmount(vault, maybeAmount, account);
|
||||
|
||||
std::uint32_t const issueFlags = mptIssue->getFieldU32(sfFlags);
|
||||
if (!(issueFlags & lsfMPTCanClawback))
|
||||
// There is a special case that allows the VaultOwner to use clawback to
|
||||
// burn shares when Vault assets total and available are zero, but
|
||||
// shares remain. However, that case is handled in doApply() directly,
|
||||
// so here we just enforce checks.
|
||||
if (amount.asset() == share)
|
||||
{
|
||||
// Only the Vault Owner may clawback shares
|
||||
if (account != vault->at(sfOwner))
|
||||
{
|
||||
JLOG(ctx.j.debug())
|
||||
<< "VaultClawback: cannot clawback MPT vault asset.";
|
||||
<< "VaultClawback: only vault owner can clawback shares.";
|
||||
return tecNO_PERMISSION;
|
||||
}
|
||||
}
|
||||
else if (vaultAsset.holds<Issue>())
|
||||
{
|
||||
std::uint32_t const issuerFlags = issuer->getFieldU32(sfFlags);
|
||||
if (!(issuerFlags & lsfAllowTrustLineClawback) ||
|
||||
(issuerFlags & lsfNoFreeze))
|
||||
|
||||
auto const assetsTotal = vault->at(sfAssetsTotal);
|
||||
auto const assetsAvailable = vault->at(sfAssetsAvailable);
|
||||
auto const sharesTotal = sleShareIssuance->at(sfOutstandingAmount);
|
||||
|
||||
// Owner can clawback funds when the vault has shares but no assets
|
||||
if (sharesTotal == 0 || (assetsTotal != 0 || assetsAvailable != 0))
|
||||
{
|
||||
JLOG(ctx.j.debug())
|
||||
<< "VaultClawback: cannot clawback IOU vault asset.";
|
||||
<< "VaultClawback: vault owner can clawback shares only"
|
||||
" when vault has no assets.";
|
||||
return tecNO_PERMISSION;
|
||||
}
|
||||
|
||||
// If amount is non-zero, the VaultOwner must burn all shares
|
||||
if (amount != beast::zero)
|
||||
{
|
||||
Number const& sharesHeld = accountHolds(
|
||||
ctx.view,
|
||||
holder,
|
||||
share,
|
||||
FreezeHandling::fhIGNORE_FREEZE,
|
||||
AuthHandling::ahIGNORE_AUTH,
|
||||
ctx.j);
|
||||
|
||||
// The VaultOwner must burn all shares
|
||||
if (amount != sharesHeld)
|
||||
{
|
||||
JLOG(ctx.j.debug())
|
||||
<< "VaultClawback: vault owner must clawback all "
|
||||
"shares.";
|
||||
return tecLIMIT_EXCEEDED;
|
||||
}
|
||||
}
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
return tesSUCCESS;
|
||||
// The asset that is being clawed back is the vault asset
|
||||
if (amount.asset() == vaultAsset)
|
||||
{
|
||||
// XRP cannot be clawed back
|
||||
if (vaultAsset.native())
|
||||
{
|
||||
JLOG(ctx.j.debug()) << "VaultClawback: cannot clawback XRP.";
|
||||
return tecNO_PERMISSION;
|
||||
}
|
||||
|
||||
// Only the Asset Issuer may clawback the asset
|
||||
if (account != vaultAsset.getIssuer())
|
||||
{
|
||||
JLOG(ctx.j.debug())
|
||||
<< "VaultClawback: only asset issuer can clawback asset.";
|
||||
return tecNO_PERMISSION;
|
||||
}
|
||||
|
||||
// The issuer cannot clawback from itself
|
||||
if (account == holder)
|
||||
{
|
||||
JLOG(ctx.j.debug())
|
||||
<< "VaultClawback: issuer cannot be the holder.";
|
||||
return tecNO_PERMISSION;
|
||||
}
|
||||
|
||||
return std::visit(
|
||||
[&]<ValidIssueType TIss>(TIss const& issue) -> TER {
|
||||
if constexpr (std::is_same_v<TIss, MPTIssue>)
|
||||
{
|
||||
auto const mptIssue =
|
||||
ctx.view.read(keylet::mptIssuance(issue.getMptID()));
|
||||
if (mptIssue == nullptr)
|
||||
return tecOBJECT_NOT_FOUND;
|
||||
|
||||
std::uint32_t const issueFlags =
|
||||
mptIssue->getFieldU32(sfFlags);
|
||||
if (!(issueFlags & lsfMPTCanClawback))
|
||||
{
|
||||
JLOG(ctx.j.debug()) << "VaultClawback: cannot clawback "
|
||||
"MPT vault asset.";
|
||||
return tecNO_PERMISSION;
|
||||
}
|
||||
}
|
||||
else if constexpr (std::is_same_v<TIss, Issue>)
|
||||
{
|
||||
auto const issuerSle =
|
||||
ctx.view.read(keylet::account(account));
|
||||
if (!issuerSle)
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
JLOG(ctx.j.error())
|
||||
<< "VaultClawback: missing submitter account.";
|
||||
return tefINTERNAL;
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
std::uint32_t const issuerFlags =
|
||||
issuerSle->getFieldU32(sfFlags);
|
||||
if (!(issuerFlags & lsfAllowTrustLineClawback) ||
|
||||
(issuerFlags & lsfNoFreeze))
|
||||
{
|
||||
JLOG(ctx.j.debug()) << "VaultClawback: cannot clawback "
|
||||
"IOU vault asset.";
|
||||
return tecNO_PERMISSION;
|
||||
}
|
||||
}
|
||||
return tesSUCCESS;
|
||||
},
|
||||
vaultAsset.value());
|
||||
}
|
||||
|
||||
// Invalid asset
|
||||
return tecWRONG_ASSET;
|
||||
}
|
||||
|
||||
Expected<std::pair<STAmount, STAmount>, TER>
|
||||
VaultClawback::assetsToClawback(
|
||||
std::shared_ptr<SLE> const& vault,
|
||||
std::shared_ptr<SLE const> const& sleShareIssuance,
|
||||
AccountID const& holder,
|
||||
STAmount const& clawbackAmount)
|
||||
{
|
||||
if (clawbackAmount.asset() != vault->at(sfAsset))
|
||||
{
|
||||
// preclaim should have blocked this , now it's an internal error
|
||||
// LCOV_EXCL_START
|
||||
JLOG(j_.error()) << "VaultClawback: asset mismatch in clawback.";
|
||||
return Unexpected(tecINTERNAL);
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
auto const assetsAvailable = vault->at(sfAssetsAvailable);
|
||||
auto const mptIssuanceID = *vault->at(sfShareMPTID);
|
||||
MPTIssue const share{mptIssuanceID};
|
||||
|
||||
if (clawbackAmount == beast::zero)
|
||||
{
|
||||
auto const sharesDestroyed = accountHolds(
|
||||
view(),
|
||||
holder,
|
||||
share,
|
||||
FreezeHandling::fhIGNORE_FREEZE,
|
||||
AuthHandling::ahIGNORE_AUTH,
|
||||
j_);
|
||||
auto const maybeAssets =
|
||||
sharesToAssetsWithdraw(vault, sleShareIssuance, sharesDestroyed);
|
||||
if (!maybeAssets)
|
||||
return Unexpected(tecINTERNAL); // LCOV_EXCL_LINE
|
||||
|
||||
return std::make_pair(*maybeAssets, sharesDestroyed);
|
||||
}
|
||||
|
||||
STAmount sharesDestroyed;
|
||||
STAmount assetsRecovered = clawbackAmount;
|
||||
try
|
||||
{
|
||||
{
|
||||
auto const maybeShares = assetsToSharesWithdraw(
|
||||
vault, sleShareIssuance, assetsRecovered);
|
||||
if (!maybeShares)
|
||||
return Unexpected(tecINTERNAL); // LCOV_EXCL_LINE
|
||||
sharesDestroyed = *maybeShares;
|
||||
}
|
||||
|
||||
auto const maybeAssets =
|
||||
sharesToAssetsWithdraw(vault, sleShareIssuance, sharesDestroyed);
|
||||
if (!maybeAssets)
|
||||
return Unexpected(tecINTERNAL); // LCOV_EXCL_LINE
|
||||
assetsRecovered = *maybeAssets;
|
||||
|
||||
// Clamp to maximum.
|
||||
if (assetsRecovered > *assetsAvailable)
|
||||
{
|
||||
assetsRecovered = *assetsAvailable;
|
||||
// Note, it is important to truncate the number of shares,
|
||||
// otherwise the corresponding assets might breach the
|
||||
// AssetsAvailable
|
||||
{
|
||||
auto const maybeShares = assetsToSharesWithdraw(
|
||||
vault,
|
||||
sleShareIssuance,
|
||||
assetsRecovered,
|
||||
TruncateShares::yes);
|
||||
if (!maybeShares)
|
||||
return Unexpected(tecINTERNAL); // LCOV_EXCL_LINE
|
||||
sharesDestroyed = *maybeShares;
|
||||
}
|
||||
|
||||
auto const maybeAssets = sharesToAssetsWithdraw(
|
||||
vault, sleShareIssuance, sharesDestroyed);
|
||||
if (!maybeAssets)
|
||||
return Unexpected(tecINTERNAL); // LCOV_EXCL_LINE
|
||||
assetsRecovered = *maybeAssets;
|
||||
if (assetsRecovered > *assetsAvailable)
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
JLOG(j_.error())
|
||||
<< "VaultClawback: invalid rounding of shares.";
|
||||
return Unexpected(tecINTERNAL);
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (std::overflow_error const&)
|
||||
{
|
||||
// It's easy to hit this exception from Number with large enough
|
||||
// Scale so we avoid spamming the log and only use debug here.
|
||||
JLOG(j_.debug()) //
|
||||
<< "VaultClawback: overflow error with"
|
||||
<< " scale=" << (int)vault->at(sfScale).value() //
|
||||
<< ", assetsTotal=" << vault->at(sfAssetsTotal).value()
|
||||
<< ", sharesTotal=" << sleShareIssuance->at(sfOutstandingAmount)
|
||||
<< ", amount=" << clawbackAmount.value();
|
||||
return Unexpected(tecPATH_DRY);
|
||||
}
|
||||
|
||||
return std::make_pair(assetsRecovered, sharesDestroyed);
|
||||
}
|
||||
|
||||
TER
|
||||
@@ -125,7 +328,7 @@ VaultClawback::doApply()
|
||||
if (!vault)
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
auto const mptIssuanceID = *((*vault)[sfShareMPTID]);
|
||||
auto const mptIssuanceID = *vault->at(sfShareMPTID);
|
||||
auto const sleIssuance = view().read(keylet::mptIssuance(mptIssuanceID));
|
||||
if (!sleIssuance)
|
||||
{
|
||||
@@ -134,105 +337,47 @@ VaultClawback::doApply()
|
||||
return tefINTERNAL;
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
MPTIssue const share{mptIssuanceID};
|
||||
|
||||
Asset const vaultAsset = vault->at(sfAsset);
|
||||
STAmount const amount = [&]() -> STAmount {
|
||||
auto const maybeAmount = tx[~sfAmount];
|
||||
if (maybeAmount)
|
||||
return *maybeAmount;
|
||||
return {sfAmount, vaultAsset, 0};
|
||||
}();
|
||||
XRPL_ASSERT(
|
||||
amount.asset() == vaultAsset,
|
||||
"xrpl::VaultClawback::doApply : matching asset");
|
||||
STAmount const amount = clawbackAmount(vault, tx[~sfAmount], account_);
|
||||
|
||||
auto assetsAvailable = vault->at(sfAssetsAvailable);
|
||||
auto assetsTotal = vault->at(sfAssetsTotal);
|
||||
|
||||
[[maybe_unused]] auto const lossUnrealized = vault->at(sfLossUnrealized);
|
||||
XRPL_ASSERT(
|
||||
lossUnrealized <= (assetsTotal - assetsAvailable),
|
||||
"xrpl::VaultClawback::doApply : loss and assets do balance");
|
||||
|
||||
AccountID holder = tx[sfHolder];
|
||||
MPTIssue const share{mptIssuanceID};
|
||||
STAmount sharesDestroyed = {share};
|
||||
STAmount assetsRecovered;
|
||||
try
|
||||
STAmount assetsRecovered = {vault->at(sfAsset)};
|
||||
|
||||
// The Owner is burning shares
|
||||
if (account_ == vault->at(sfOwner) && amount.asset() == share)
|
||||
{
|
||||
if (amount == beast::zero)
|
||||
{
|
||||
sharesDestroyed = accountHolds(
|
||||
view(),
|
||||
holder,
|
||||
share,
|
||||
FreezeHandling::fhIGNORE_FREEZE,
|
||||
AuthHandling::ahIGNORE_AUTH,
|
||||
j_);
|
||||
|
||||
auto const maybeAssets =
|
||||
sharesToAssetsWithdraw(vault, sleIssuance, sharesDestroyed);
|
||||
if (!maybeAssets)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
assetsRecovered = *maybeAssets;
|
||||
}
|
||||
else
|
||||
{
|
||||
assetsRecovered = amount;
|
||||
{
|
||||
auto const maybeShares =
|
||||
assetsToSharesWithdraw(vault, sleIssuance, assetsRecovered);
|
||||
if (!maybeShares)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
sharesDestroyed = *maybeShares;
|
||||
}
|
||||
|
||||
auto const maybeAssets =
|
||||
sharesToAssetsWithdraw(vault, sleIssuance, sharesDestroyed);
|
||||
if (!maybeAssets)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
assetsRecovered = *maybeAssets;
|
||||
}
|
||||
|
||||
// Clamp to maximum.
|
||||
if (assetsRecovered > *assetsAvailable)
|
||||
{
|
||||
assetsRecovered = *assetsAvailable;
|
||||
// Note, it is important to truncate the number of shares, otherwise
|
||||
// the corresponding assets might breach the AssetsAvailable
|
||||
{
|
||||
auto const maybeShares = assetsToSharesWithdraw(
|
||||
vault, sleIssuance, assetsRecovered, TruncateShares::yes);
|
||||
if (!maybeShares)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
sharesDestroyed = *maybeShares;
|
||||
}
|
||||
|
||||
auto const maybeAssets =
|
||||
sharesToAssetsWithdraw(vault, sleIssuance, sharesDestroyed);
|
||||
if (!maybeAssets)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
assetsRecovered = *maybeAssets;
|
||||
if (assetsRecovered > *assetsAvailable)
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
JLOG(j_.error())
|
||||
<< "VaultClawback: invalid rounding of shares.";
|
||||
return tecINTERNAL;
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
}
|
||||
sharesDestroyed = accountHolds(
|
||||
view(),
|
||||
holder,
|
||||
share,
|
||||
FreezeHandling::fhIGNORE_FREEZE,
|
||||
AuthHandling::ahIGNORE_AUTH,
|
||||
j_);
|
||||
}
|
||||
catch (std::overflow_error const&)
|
||||
else // The Issuer is clawbacking vault assets
|
||||
{
|
||||
// It's easy to hit this exception from Number with large enough Scale
|
||||
// so we avoid spamming the log and only use debug here.
|
||||
JLOG(j_.debug()) //
|
||||
<< "VaultClawback: overflow error with"
|
||||
<< " scale=" << (int)vault->at(sfScale).value() //
|
||||
<< ", assetsTotal=" << vault->at(sfAssetsTotal).value()
|
||||
<< ", sharesTotal=" << sleIssuance->at(sfOutstandingAmount)
|
||||
<< ", amount=" << amount.value();
|
||||
return tecPATH_DRY;
|
||||
XRPL_ASSERT(
|
||||
amount.asset() == vaultAsset,
|
||||
"xrpl::VaultClawback::doApply : matching asset");
|
||||
|
||||
auto const clawbackParts =
|
||||
assetsToClawback(vault, sleIssuance, holder, amount);
|
||||
if (!clawbackParts)
|
||||
return clawbackParts.error();
|
||||
|
||||
assetsRecovered = clawbackParts->first;
|
||||
sharesDestroyed = clawbackParts->second;
|
||||
}
|
||||
|
||||
if (sharesDestroyed == beast::zero)
|
||||
@@ -282,30 +427,34 @@ VaultClawback::doApply()
|
||||
// else quietly ignore, holder balance is not zero
|
||||
}
|
||||
|
||||
// Transfer assets from vault to issuer.
|
||||
if (auto const ter = accountSend(
|
||||
view(),
|
||||
vaultAccount,
|
||||
account_,
|
||||
assetsRecovered,
|
||||
j_,
|
||||
WaiveTransferFee::Yes);
|
||||
!isTesSuccess(ter))
|
||||
return ter;
|
||||
|
||||
// Sanity check
|
||||
if (accountHolds(
|
||||
view(),
|
||||
vaultAccount,
|
||||
assetsRecovered.asset(),
|
||||
FreezeHandling::fhIGNORE_FREEZE,
|
||||
AuthHandling::ahIGNORE_AUTH,
|
||||
j_) < beast::zero)
|
||||
if (assetsRecovered > beast::zero)
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
JLOG(j_.error()) << "VaultClawback: negative balance of vault assets.";
|
||||
return tefINTERNAL;
|
||||
// LCOV_EXCL_STOP
|
||||
// Transfer assets from vault to issuer.
|
||||
if (auto const ter = accountSend(
|
||||
view(),
|
||||
vaultAccount,
|
||||
account_,
|
||||
assetsRecovered,
|
||||
j_,
|
||||
WaiveTransferFee::Yes);
|
||||
!isTesSuccess(ter))
|
||||
return ter;
|
||||
|
||||
// Sanity check
|
||||
if (accountHolds(
|
||||
view(),
|
||||
vaultAccount,
|
||||
assetsRecovered.asset(),
|
||||
FreezeHandling::fhIGNORE_FREEZE,
|
||||
AuthHandling::ahIGNORE_AUTH,
|
||||
j_) < beast::zero)
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
JLOG(j_.error())
|
||||
<< "VaultClawback: negative balance of vault assets.";
|
||||
return tefINTERNAL;
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
}
|
||||
|
||||
return tesSUCCESS;
|
||||
|
||||
@@ -22,6 +22,14 @@ public:
|
||||
|
||||
TER
|
||||
doApply() override;
|
||||
|
||||
private:
|
||||
Expected<std::pair<STAmount, STAmount>, TER>
|
||||
assetsToClawback(
|
||||
std::shared_ptr<SLE> const& vault,
|
||||
std::shared_ptr<SLE const> const& sleShareIssuance,
|
||||
AccountID const& holder,
|
||||
STAmount const& clawbackAmount);
|
||||
};
|
||||
|
||||
} // namespace xrpl
|
||||
|
||||
@@ -41,15 +41,22 @@ checkValidity(
|
||||
Validity::SigBad,
|
||||
"Malformed: Invalid inner batch transaction."};
|
||||
|
||||
std::string reason;
|
||||
if (!passesLocalChecks(tx, reason))
|
||||
// This block should probably have never been included in the
|
||||
// original `Batch` implementation. An inner transaction never
|
||||
// has a valid signature.
|
||||
bool const neverValid = rules.enabled(fixBatchInnerSigs);
|
||||
if (!neverValid)
|
||||
{
|
||||
router.setFlags(id, SF_LOCALBAD);
|
||||
return {Validity::SigGoodOnly, reason};
|
||||
}
|
||||
std::string reason;
|
||||
if (!passesLocalChecks(tx, reason))
|
||||
{
|
||||
router.setFlags(id, SF_LOCALBAD);
|
||||
return {Validity::SigGoodOnly, reason};
|
||||
}
|
||||
|
||||
router.setFlags(id, SF_SIGGOOD);
|
||||
return {Validity::Valid, ""};
|
||||
router.setFlags(id, SF_SIGGOOD);
|
||||
return {Validity::Valid, ""};
|
||||
}
|
||||
}
|
||||
|
||||
if (any(flags & SF_SIGBAD))
|
||||
|
||||
@@ -179,6 +179,10 @@ public:
|
||||
|
||||
FuncInfo
|
||||
getFunc(std::string_view funcName) const;
|
||||
|
||||
wasm_functype_t*
|
||||
getFuncType(std::string_view funcName) const;
|
||||
|
||||
wmem
|
||||
getMem() const;
|
||||
|
||||
|
||||
@@ -249,9 +249,9 @@ ModuleWrapper::ModuleWrapper(
|
||||
: module_(init(s, wasmBin, j)), j_(j)
|
||||
{
|
||||
wasm_module_exports(module_.get(), &exportTypes_.vec_);
|
||||
auto wimports = buildImports(s, imports);
|
||||
if (instantiate)
|
||||
{
|
||||
auto wimports = buildImports(s, imports);
|
||||
addInstance(s, wimports);
|
||||
}
|
||||
}
|
||||
@@ -415,6 +415,7 @@ ModuleWrapper::buildImports(StorePtr& s, ImportVec const& imports)
|
||||
"/" + std::to_string(importTypes.vec_.size),
|
||||
nullptr,
|
||||
j_);
|
||||
throw std::runtime_error("Missing imports");
|
||||
}
|
||||
|
||||
return wimports;
|
||||
@@ -426,6 +427,26 @@ ModuleWrapper::getFunc(std::string_view funcName) const
|
||||
return instanceWrap_.getFunc(funcName, exportTypes_);
|
||||
}
|
||||
|
||||
wasm_functype_t*
|
||||
ModuleWrapper::getFuncType(std::string_view funcName) const
|
||||
{
|
||||
for (size_t i = 0; i < exportTypes_.vec_.size; i++)
|
||||
{
|
||||
auto const* exp_type(exportTypes_.vec_.data[i]);
|
||||
wasm_name_t const* name = wasm_exporttype_name(exp_type);
|
||||
wasm_externtype_t const* exn_type = wasm_exporttype_type(exp_type);
|
||||
if (wasm_externtype_kind(exn_type) == WASM_EXTERN_FUNC &&
|
||||
funcName == std::string_view(name->data, name->size))
|
||||
{
|
||||
return wasm_externtype_as_functype(
|
||||
const_cast<wasm_externtype_t*>(exn_type));
|
||||
}
|
||||
}
|
||||
|
||||
throw std::runtime_error(
|
||||
"can't find function <" + std::string(funcName) + ">");
|
||||
}
|
||||
|
||||
wmem
|
||||
ModuleWrapper::getMem() const
|
||||
{
|
||||
@@ -888,13 +909,14 @@ WasmiEngine::checkHlp(
|
||||
if (wasmCode.empty())
|
||||
throw std::runtime_error("empty nodule");
|
||||
|
||||
int const m = addModule(wasmCode, true, -1, imports);
|
||||
if ((m < 0) || !moduleWrap_ || !moduleWrap_->instanceWrap_)
|
||||
throw std::runtime_error("no instance"); // LCOV_EXCL_LINE
|
||||
int const m = addModule(wasmCode, false, -1, imports);
|
||||
if ((m < 0) || !moduleWrap_)
|
||||
throw std::runtime_error("no module"); // LCOV_EXCL_LINE
|
||||
|
||||
// Looking for a func and compare parameter types
|
||||
auto const f = getFunc(!funcName.empty() ? funcName : "_start");
|
||||
auto const* ftp = wasm_functype_params(f.second);
|
||||
auto const f =
|
||||
moduleWrap_->getFuncType(!funcName.empty() ? funcName : "_start");
|
||||
auto const* ftp = wasm_functype_params(f);
|
||||
auto const p = convertParams(params);
|
||||
|
||||
if (int const comp = compareParamTypes(ftp, p); comp >= 0)
|
||||
@@ -929,13 +951,24 @@ WasmiEngine::getRT(int m, int i)
|
||||
int32_t
|
||||
WasmiEngine::allocate(int32_t sz)
|
||||
{
|
||||
auto res = call<1>(W_ALLOC, static_cast<int32_t>(sz));
|
||||
|
||||
if (res.f || !res.r.vec_.size || (res.r.vec_.data[0].kind != WASM_I32) ||
|
||||
!res.r.vec_.data[0].of.i32)
|
||||
if (sz <= 0)
|
||||
throw std::runtime_error(
|
||||
"can't allocate memory, " + std::to_string(sz) + " bytes");
|
||||
return res.r.vec_.data[0].of.i32;
|
||||
|
||||
auto res = call<1>(W_ALLOC, static_cast<int32_t>(sz));
|
||||
|
||||
if (res.f || !res.r.vec_.size || (res.r.vec_.data[0].kind != WASM_I32))
|
||||
throw std::runtime_error(
|
||||
"can't allocate memory, " + std::to_string(sz) +
|
||||
" bytes"); // LCOV_EXCL_LINE
|
||||
|
||||
int32_t const p = res.r.vec_.data[0].of.i32;
|
||||
auto const mem = getMem();
|
||||
if (p <= 0 || p + sz > mem.s)
|
||||
throw std::runtime_error(
|
||||
"invalid memory allocation, " + std::to_string(sz) + " bytes");
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
wasm_trap_t*
|
||||
|
||||
Reference in New Issue
Block a user