mirror of
https://github.com/XRPLF/rippled.git
synced 2026-03-15 09:12:25 +00:00
1193 lines
45 KiB
C++
1193 lines
45 KiB
C++
#include <test/app/wasm_fixtures/fixtures.h>
|
|
#include <test/jtx.h>
|
|
|
|
#include <xrpl/ledger/Dir.h>
|
|
#include <xrpl/protocol/Feature.h>
|
|
#include <xrpl/protocol/Indexes.h>
|
|
#include <xrpl/protocol/TxFlags.h>
|
|
#include <xrpl/protocol/jss.h>
|
|
#include <xrpl/tx/applySteps.h>
|
|
#include <xrpl/tx/wasm/WasmVM.h>
|
|
|
|
#include <algorithm>
|
|
#include <iterator>
|
|
#include <source_location>
|
|
|
|
namespace xrpl {
|
|
namespace test {
|
|
|
|
struct EscrowSmart_test : public beast::unit_test::suite
|
|
{
|
|
void
|
|
testCreateFinishFunctionPreflight(FeatureBitset features)
|
|
{
|
|
testcase("Test preflight checks involving FinishFunction");
|
|
|
|
using namespace jtx;
|
|
using namespace std::chrono;
|
|
|
|
Account const alice{"alice"};
|
|
Account const carol{"carol"};
|
|
|
|
// Tests whether the ledger index is >= 5
|
|
// getLedgerSqn() >= 5}
|
|
|
|
{
|
|
// featureSmartEscrow disabled
|
|
Env env(*this, features - featureSmartEscrow);
|
|
env.fund(XRP(5000), alice, carol);
|
|
XRPAmount const txnFees = env.current()->fees().base + 1000;
|
|
auto const escrowCreate = escrow::create(alice, carol, XRP(1000));
|
|
env(escrowCreate,
|
|
escrow::finish_function(ledgerSqnWasmHex),
|
|
escrow::cancel_time(env.now() + 100s),
|
|
fee(txnFees),
|
|
ter(temDISABLED));
|
|
env.close();
|
|
|
|
env(escrowCreate,
|
|
escrow::finish_function(ledgerSqnWasmHex),
|
|
escrow::cancel_time(env.now() + 100s),
|
|
escrow::data("00112233"),
|
|
fee(txnFees),
|
|
ter(temDISABLED));
|
|
env.close();
|
|
}
|
|
|
|
{
|
|
// FinishFunction > max length
|
|
Env env(
|
|
*this,
|
|
envconfig([](std::unique_ptr<Config> cfg) {
|
|
cfg->FEES.extension_size_limit = 10; // 10 bytes
|
|
return cfg;
|
|
}),
|
|
features);
|
|
XRPAmount const txnFees = env.current()->fees().base + 1000;
|
|
// create escrow
|
|
env.fund(XRP(5000), alice, carol);
|
|
|
|
auto const escrowCreate = escrow::create(alice, carol, XRP(500));
|
|
|
|
// 11-byte string
|
|
std::string const longWasmHex = "00112233445566778899AA";
|
|
env(escrowCreate,
|
|
escrow::finish_function(longWasmHex),
|
|
escrow::cancel_time(env.now() + 100s),
|
|
fee(txnFees),
|
|
ter(temMALFORMED));
|
|
env.close();
|
|
}
|
|
|
|
{
|
|
// compute limit set to 0
|
|
Env env(
|
|
*this,
|
|
envconfig([](std::unique_ptr<Config> cfg) {
|
|
// WASM runtime disabled
|
|
cfg->FEES.extension_compute_limit = 0;
|
|
return cfg;
|
|
}),
|
|
features);
|
|
XRPAmount const txnFees = env.current()->fees().base + 1000;
|
|
// create escrow
|
|
env.fund(XRP(5000), alice, carol);
|
|
|
|
auto const escrowCreate = escrow::create(alice, carol, XRP(500));
|
|
|
|
env(escrowCreate,
|
|
escrow::finish_function(ledgerSqnWasmHex),
|
|
escrow::cancel_time(env.now() + 100s),
|
|
escrow::comp_allowance(100),
|
|
fee(txnFees),
|
|
ter(temMALFORMED));
|
|
env.close();
|
|
}
|
|
|
|
{
|
|
// size limit set to 0
|
|
Env env(
|
|
*this,
|
|
envconfig([](std::unique_ptr<Config> cfg) {
|
|
cfg->FEES.extension_size_limit = 0; // WASM upload disabled
|
|
return cfg;
|
|
}),
|
|
features);
|
|
XRPAmount const txnFees = env.current()->fees().base + 1000;
|
|
// create escrow
|
|
env.fund(XRP(5000), alice, carol);
|
|
|
|
auto const escrowCreate = escrow::create(alice, carol, XRP(500));
|
|
|
|
// 2-byte string
|
|
env(escrowCreate,
|
|
escrow::finish_function("AA"),
|
|
escrow::cancel_time(env.now() + 100s),
|
|
fee(txnFees),
|
|
ter(temTEMP_DISABLED));
|
|
env.close();
|
|
|
|
env(escrowCreate,
|
|
escrow::finish_function(ledgerSqnWasmHex),
|
|
escrow::cancel_time(env.now() + 100s),
|
|
fee(txnFees),
|
|
ter(temTEMP_DISABLED));
|
|
env.close();
|
|
}
|
|
|
|
{
|
|
// Data without FinishFunction
|
|
Env env(*this, features);
|
|
XRPAmount const txnFees = env.current()->fees().base + 100000;
|
|
// create escrow
|
|
env.fund(XRP(5000), alice, carol);
|
|
|
|
auto const escrowCreate = escrow::create(alice, carol, XRP(500));
|
|
|
|
std::string const longData(4, 'A');
|
|
env(escrowCreate,
|
|
escrow::data(longData),
|
|
escrow::finish_time(env.now() + 100s),
|
|
fee(txnFees),
|
|
ter(temMALFORMED));
|
|
env.close();
|
|
}
|
|
|
|
{
|
|
// Data > max length
|
|
Env env(*this, features);
|
|
XRPAmount const txnFees = env.current()->fees().base + 100000;
|
|
// create escrow
|
|
env.fund(XRP(5000), alice, carol);
|
|
|
|
auto const escrowCreate = escrow::create(alice, carol, XRP(500));
|
|
|
|
// string of length maxWasmDataLength * 2 + 2
|
|
std::string const longData((maxWasmDataLength + 1) * 2, 'B');
|
|
env(escrowCreate,
|
|
escrow::data(longData),
|
|
escrow::finish_function(ledgerSqnWasmHex),
|
|
escrow::cancel_time(env.now() + 100s),
|
|
fee(txnFees),
|
|
ter(temMALFORMED));
|
|
env.close();
|
|
}
|
|
|
|
Env env(
|
|
*this,
|
|
envconfig([](std::unique_ptr<Config> cfg) {
|
|
cfg->START_UP = StartUpType::FRESH;
|
|
return cfg;
|
|
}),
|
|
features);
|
|
XRPAmount const txnFees = env.current()->fees().base * 10 + ledgerSqnWasmHex.size() / 2 * 5;
|
|
// create escrow
|
|
env.fund(XRP(5000), alice, carol);
|
|
|
|
auto escrowCreate = escrow::create(alice, carol, XRP(500));
|
|
|
|
// Success situations
|
|
{
|
|
// FinishFunction + CancelAfter
|
|
env(escrowCreate,
|
|
escrow::finish_function(ledgerSqnWasmHex),
|
|
escrow::cancel_time(env.now() + 20s),
|
|
fee(txnFees));
|
|
env.close();
|
|
}
|
|
{
|
|
// FinishFunction + Condition + CancelAfter
|
|
env(escrowCreate,
|
|
escrow::finish_function(ledgerSqnWasmHex),
|
|
escrow::cancel_time(env.now() + 30s),
|
|
escrow::condition(escrow::cb1),
|
|
fee(txnFees));
|
|
env.close();
|
|
}
|
|
{
|
|
// FinishFunction + FinishAfter + CancelAfter
|
|
env(escrowCreate,
|
|
escrow::finish_function(ledgerSqnWasmHex),
|
|
escrow::cancel_time(env.now() + 40s),
|
|
escrow::finish_time(env.now() + 2s),
|
|
fee(txnFees));
|
|
env.close();
|
|
}
|
|
{
|
|
// FinishFunction + FinishAfter + Condition + CancelAfter
|
|
env(escrowCreate,
|
|
escrow::finish_function(ledgerSqnWasmHex),
|
|
escrow::cancel_time(env.now() + 50s),
|
|
escrow::condition(escrow::cb1),
|
|
escrow::finish_time(env.now() + 2s),
|
|
fee(txnFees));
|
|
env.close();
|
|
}
|
|
|
|
// Failure situations (i.e. all other combinations)
|
|
{
|
|
// only FinishFunction
|
|
env(escrowCreate,
|
|
escrow::finish_function(ledgerSqnWasmHex),
|
|
fee(txnFees),
|
|
ter(temBAD_EXPIRATION));
|
|
env.close();
|
|
}
|
|
{
|
|
// FinishFunction + FinishAfter
|
|
env(escrowCreate,
|
|
escrow::finish_function(ledgerSqnWasmHex),
|
|
escrow::finish_time(env.now() + 2s),
|
|
fee(txnFees),
|
|
ter(temBAD_EXPIRATION));
|
|
env.close();
|
|
}
|
|
{
|
|
// FinishFunction + Condition
|
|
env(escrowCreate,
|
|
escrow::finish_function(ledgerSqnWasmHex),
|
|
escrow::condition(escrow::cb1),
|
|
fee(txnFees),
|
|
ter(temBAD_EXPIRATION));
|
|
env.close();
|
|
}
|
|
{
|
|
// FinishFunction + FinishAfter + Condition
|
|
env(escrowCreate,
|
|
escrow::finish_function(ledgerSqnWasmHex),
|
|
escrow::condition(escrow::cb1),
|
|
escrow::finish_time(env.now() + 2s),
|
|
fee(txnFees),
|
|
ter(temBAD_EXPIRATION));
|
|
env.close();
|
|
}
|
|
{
|
|
// FinishFunction 0 length
|
|
env(escrowCreate,
|
|
escrow::finish_function(""),
|
|
escrow::cancel_time(env.now() + 60s),
|
|
fee(txnFees),
|
|
ter(temMALFORMED));
|
|
env.close();
|
|
}
|
|
{
|
|
// Not enough fees
|
|
env(escrowCreate,
|
|
escrow::finish_function(ledgerSqnWasmHex),
|
|
escrow::cancel_time(env.now() + 70s),
|
|
fee(txnFees - 1),
|
|
ter(telINSUF_FEE_P));
|
|
env.close();
|
|
}
|
|
|
|
{
|
|
// FinishFunction nonexistent host function
|
|
// pub fn finish() -> bool {
|
|
// unsafe { host_lib::bad() >= 5 }
|
|
// }
|
|
auto const badWasmHex =
|
|
"0061736d010000000105016000017f02100108686f73745f6c696203626164"
|
|
"00000302010005030100100611027f00418080c0000b7f00418080c0000b07"
|
|
"2e04066d656d6f727902000666696e69736800010a5f5f646174615f656e64"
|
|
"03000b5f5f686561705f6261736503010a09010700100041044a0b004d0970"
|
|
"726f64756365727302086c616e6775616765010452757374000c70726f6365"
|
|
"737365642d6279010572757374631d312e38352e3120283465623136313235"
|
|
"3020323032352d30332d31352900490f7461726765745f6665617475726573"
|
|
"042b0f6d757461626c652d676c6f62616c732b087369676e2d6578742b0f72"
|
|
"65666572656e63652d74797065732b0a6d756c746976616c7565";
|
|
env(escrowCreate,
|
|
escrow::finish_function(badWasmHex),
|
|
escrow::cancel_time(env.now() + 100s),
|
|
fee(txnFees),
|
|
ter(temBAD_WASM));
|
|
env.close();
|
|
}
|
|
}
|
|
|
|
void
|
|
testFinishWasmFailures(FeatureBitset features)
|
|
{
|
|
testcase("EscrowFinish Smart Escrow failures");
|
|
|
|
using namespace jtx;
|
|
using namespace std::chrono;
|
|
|
|
Account const alice{"alice"};
|
|
Account const carol{"carol"};
|
|
|
|
// Tests whether the ledger index is >= 5
|
|
// getLedgerSqn() >= 5}
|
|
|
|
{
|
|
// featureSmartEscrow disabled
|
|
Env env(*this, features - featureSmartEscrow);
|
|
env.fund(XRP(5000), alice, carol);
|
|
XRPAmount const txnFees =
|
|
env.current()->fees().base * 10 + ledgerSqnWasmHex.size() / 2 * 5;
|
|
env(escrow::finish(carol, alice, 1),
|
|
fee(txnFees),
|
|
escrow::comp_allowance(4),
|
|
ter(temDISABLED));
|
|
env.close();
|
|
}
|
|
|
|
{
|
|
// ComputationAllowance > max compute limit
|
|
Env env(
|
|
*this,
|
|
envconfig([](std::unique_ptr<Config> cfg) {
|
|
cfg->FEES.extension_compute_limit = 1'000; // in gas
|
|
return cfg;
|
|
}),
|
|
features);
|
|
env.fund(XRP(5000), alice, carol);
|
|
// Run past the flag ledger so that a Fee change vote occurs and
|
|
// updates FeeSettings. (It also activates all supported
|
|
// amendments.)
|
|
for (auto i = env.current()->seq(); i <= 257; ++i)
|
|
env.close();
|
|
|
|
auto const allowance = 1'001;
|
|
env(escrow::finish(carol, alice, 1),
|
|
fee(env.current()->fees().base + allowance),
|
|
escrow::comp_allowance(allowance),
|
|
ter(temBAD_LIMIT));
|
|
}
|
|
|
|
{
|
|
// WASM compute disabled
|
|
using namespace test::jtx;
|
|
using namespace std::chrono;
|
|
Env env{*this, envconfig([](std::unique_ptr<Config> cfg) {
|
|
cfg->FEES.extension_compute_limit = 0;
|
|
return cfg;
|
|
})};
|
|
|
|
Account const alice{"alice"};
|
|
env.fund(XRP(1000), alice);
|
|
env.close();
|
|
|
|
auto const seq = env.seq(alice);
|
|
auto const keylet = keylet::escrow(alice.id(), seq);
|
|
env(noop(alice)); // to align sequence numbers
|
|
|
|
// This adds the Escrow ledger object by hand, bypassing normal
|
|
// transaction processing This is necessary because the config
|
|
// cannot be updated in the middle of a test, and we cannot easily
|
|
// create a Smart Escrow while the compute limit is set to 0
|
|
env.app().openLedger().modify([&](OpenView& view, beast::Journal j) {
|
|
auto sle = std::make_shared<SLE>(keylet);
|
|
|
|
sle->setAccountID(sfAccount, alice.id());
|
|
sle->setFieldAmount(sfAmount, XRP(100));
|
|
sle->setFieldU32(sfCancelAfter, 110);
|
|
sle->setAccountID(sfDestination, alice.id());
|
|
sle->setFieldVL(sfFinishFunction, strUnHex(ledgerSqnWasmHex).value());
|
|
sle->setFieldU32(sfFlags, 0);
|
|
sle->setFieldU64(sfOwnerNode, 0);
|
|
uint256 tmp;
|
|
BEAST_EXPECT(tmp.parseHex(
|
|
"F63D1A452A96C19EFD77901FB37D236C59EAA746771A6"
|
|
"85D1BBA57A2238B9401"));
|
|
sle->setFieldH256(sfPreviousTxnID, tmp);
|
|
sle->setFieldU32(sfPreviousTxnLgrSeq, 4);
|
|
sle->setFieldU32(sfSequence, seq);
|
|
|
|
view.rawInsert(sle);
|
|
return true;
|
|
});
|
|
BEAST_EXPECT(env.le(keylet));
|
|
|
|
env(escrow::finish(alice, alice, seq),
|
|
escrow::comp_allowance(1000),
|
|
fee(env.current()->fees().base + 1000),
|
|
ter(temTEMP_DISABLED));
|
|
}
|
|
|
|
Env env(*this, features);
|
|
|
|
// Run past the flag ledger so that a Fee change vote occurs and
|
|
// updates FeeSettings. (It also activates all supported
|
|
// amendments.)
|
|
for (auto i = env.current()->seq(); i <= 257; ++i)
|
|
env.close();
|
|
|
|
XRPAmount const txnFees = env.current()->fees().base * 10 + ledgerSqnWasmHex.size() / 2 * 5;
|
|
env.fund(XRP(5000), alice, carol);
|
|
|
|
// create escrow
|
|
auto const seq = env.seq(alice);
|
|
env(escrow::create(alice, carol, XRP(500)),
|
|
escrow::finish_function(ledgerSqnWasmHex),
|
|
escrow::cancel_time(env.now() + 100s),
|
|
fee(txnFees));
|
|
env.close();
|
|
|
|
{
|
|
// no ComputationAllowance field
|
|
env(escrow::finish(carol, alice, seq), ter(tefWASM_FIELD_NOT_INCLUDED));
|
|
}
|
|
|
|
{
|
|
// ComputationAllowance value of 0
|
|
env(escrow::finish(carol, alice, seq), escrow::comp_allowance(0), ter(temBAD_LIMIT));
|
|
}
|
|
|
|
{
|
|
// not enough fees
|
|
// This function takes 4 gas
|
|
// In testing, 1 gas costs 1 drop
|
|
auto const finishFee = env.current()->fees().base + 3;
|
|
env(escrow::finish(carol, alice, seq),
|
|
fee(finishFee),
|
|
escrow::comp_allowance(4),
|
|
ter(telINSUF_FEE_P));
|
|
}
|
|
|
|
{
|
|
// not enough gas
|
|
// This function takes 4 gas
|
|
// In testing, 1 gas costs 1 drop
|
|
auto const finishFee = env.current()->fees().base + 4;
|
|
env(escrow::finish(carol, alice, seq),
|
|
fee(finishFee),
|
|
escrow::comp_allowance(2),
|
|
ter(tecFAILED_PROCESSING));
|
|
}
|
|
|
|
{
|
|
// ComputationAllowance field included w/no FinishFunction on
|
|
// escrow
|
|
auto const seq2 = env.seq(alice);
|
|
env(escrow::create(alice, carol, XRP(500)),
|
|
escrow::finish_time(env.now() + 10s),
|
|
escrow::cancel_time(env.now() + 100s));
|
|
env.close();
|
|
|
|
auto const allowance = 100;
|
|
env(escrow::finish(carol, alice, seq2),
|
|
fee(env.current()->fees().base +
|
|
(allowance * env.current()->fees().gasPrice) / MICRO_DROPS_PER_DROP + 1),
|
|
escrow::comp_allowance(allowance),
|
|
ter(tefNO_WASM));
|
|
}
|
|
}
|
|
|
|
void
|
|
testFinishFunction(FeatureBitset features)
|
|
{
|
|
testcase("Example escrow function");
|
|
|
|
using namespace jtx;
|
|
using namespace std::chrono;
|
|
|
|
Account const alice{"alice"};
|
|
Account const carol{"carol"};
|
|
|
|
// Tests whether the ledger index is >= 5
|
|
// getLedgerSqn() >= 5}
|
|
std::uint32_t const allowance = 467;
|
|
auto escrowCreate = escrow::create(alice, carol, XRP(1000));
|
|
auto [createFee, finishFee] = [&]() {
|
|
Env env(*this, features);
|
|
auto createFee = env.current()->fees().base * 10 + ledgerSqnWasmHex.size() / 2 * 5;
|
|
auto finishFee = env.current()->fees().base +
|
|
(allowance * env.current()->fees().gasPrice) / MICRO_DROPS_PER_DROP + 1;
|
|
return std::make_pair(createFee, finishFee);
|
|
}();
|
|
|
|
{
|
|
// basic FinishFunction situation
|
|
Env env(*this, features);
|
|
// create escrow
|
|
env.fund(XRP(5000), alice, carol);
|
|
auto const seq = env.seq(alice);
|
|
BEAST_EXPECT(env.ownerCount(alice) == 0);
|
|
env(escrowCreate,
|
|
escrow::finish_function(ledgerSqnWasmHex),
|
|
escrow::cancel_time(env.now() + 100s),
|
|
fee(createFee));
|
|
env.close();
|
|
|
|
if (BEAST_EXPECT(env.ownerCount(alice) == 2))
|
|
{
|
|
env.require(balance(alice, XRP(4000) - createFee));
|
|
env.require(balance(carol, XRP(5000)));
|
|
|
|
env(escrow::finish(carol, alice, seq),
|
|
escrow::comp_allowance(allowance),
|
|
fee(finishFee),
|
|
ter(tecWASM_REJECTED));
|
|
env(escrow::finish(alice, alice, seq),
|
|
escrow::comp_allowance(allowance),
|
|
fee(finishFee),
|
|
ter(tecWASM_REJECTED));
|
|
env(escrow::finish(alice, alice, seq),
|
|
escrow::comp_allowance(allowance),
|
|
fee(finishFee),
|
|
ter(tecWASM_REJECTED));
|
|
env(escrow::finish(carol, alice, seq),
|
|
escrow::comp_allowance(allowance),
|
|
fee(finishFee),
|
|
ter(tecWASM_REJECTED));
|
|
env(escrow::finish(carol, alice, seq),
|
|
escrow::comp_allowance(allowance),
|
|
fee(finishFee),
|
|
ter(tecWASM_REJECTED));
|
|
env.close();
|
|
|
|
{
|
|
auto const txMeta = env.meta();
|
|
if (BEAST_EXPECT(txMeta->isFieldPresent(sfGasUsed)))
|
|
BEAST_EXPECTS(
|
|
env.meta()->getFieldU32(sfGasUsed) == allowance,
|
|
std::to_string(env.meta()->getFieldU32(sfGasUsed)));
|
|
}
|
|
|
|
env(escrow::finish(alice, alice, seq),
|
|
fee(finishFee),
|
|
escrow::comp_allowance(allowance),
|
|
ter(tesSUCCESS));
|
|
|
|
auto const txMeta = env.meta();
|
|
if (BEAST_EXPECT(txMeta->isFieldPresent(sfGasUsed)))
|
|
BEAST_EXPECTS(
|
|
txMeta->getFieldU32(sfGasUsed) == allowance,
|
|
std::to_string(txMeta->getFieldU32(sfGasUsed)));
|
|
if (BEAST_EXPECT(txMeta->isFieldPresent(sfWasmReturnCode)))
|
|
BEAST_EXPECTS(
|
|
txMeta->getFieldI32(sfWasmReturnCode) == 5,
|
|
std::to_string(txMeta->getFieldI32(sfWasmReturnCode)));
|
|
|
|
BEAST_EXPECT(env.ownerCount(alice) == 0);
|
|
}
|
|
}
|
|
|
|
{
|
|
// FinishFunction + Condition
|
|
Env env(*this, features);
|
|
env.fund(XRP(5000), alice, carol);
|
|
BEAST_EXPECT(env.ownerCount(alice) == 0);
|
|
auto const seq = env.seq(alice);
|
|
// create escrow
|
|
env(escrowCreate,
|
|
escrow::finish_function(ledgerSqnWasmHex),
|
|
escrow::condition(escrow::cb1),
|
|
escrow::cancel_time(env.now() + 100s),
|
|
fee(createFee));
|
|
env.close();
|
|
auto const conditionFinishFee =
|
|
finishFee + env.current()->fees().base * (32 + (escrow::fb1.size() / 16));
|
|
|
|
if (BEAST_EXPECT(env.ownerCount(alice) == 2))
|
|
{
|
|
env.require(balance(alice, XRP(4000) - createFee));
|
|
env.require(balance(carol, XRP(5000)));
|
|
|
|
// no fulfillment provided, function fails
|
|
env(escrow::finish(carol, alice, seq),
|
|
escrow::comp_allowance(allowance),
|
|
fee(finishFee),
|
|
ter(tecCRYPTOCONDITION_ERROR));
|
|
// fulfillment provided, function fails
|
|
env(escrow::finish(carol, alice, seq),
|
|
escrow::condition(escrow::cb1),
|
|
escrow::fulfillment(escrow::fb1),
|
|
escrow::comp_allowance(allowance),
|
|
fee(conditionFinishFee),
|
|
ter(tecWASM_REJECTED));
|
|
if (BEAST_EXPECT(env.meta()->isFieldPresent(sfGasUsed)))
|
|
BEAST_EXPECTS(
|
|
env.meta()->getFieldU32(sfGasUsed) == allowance,
|
|
std::to_string(env.meta()->getFieldU32(sfGasUsed)));
|
|
env.close();
|
|
// no fulfillment provided, function succeeds
|
|
env(escrow::finish(alice, alice, seq),
|
|
escrow::comp_allowance(allowance),
|
|
fee(conditionFinishFee),
|
|
ter(tecCRYPTOCONDITION_ERROR));
|
|
// wrong fulfillment provided, function succeeds
|
|
env(escrow::finish(alice, alice, seq),
|
|
escrow::condition(escrow::cb1),
|
|
escrow::fulfillment(escrow::fb2),
|
|
escrow::comp_allowance(allowance),
|
|
fee(conditionFinishFee),
|
|
ter(tecCRYPTOCONDITION_ERROR));
|
|
// fulfillment provided, function succeeds, tx succeeds
|
|
env(escrow::finish(alice, alice, seq),
|
|
escrow::condition(escrow::cb1),
|
|
escrow::fulfillment(escrow::fb1),
|
|
escrow::comp_allowance(allowance),
|
|
fee(conditionFinishFee),
|
|
ter(tesSUCCESS));
|
|
|
|
auto const txMeta = env.meta();
|
|
if (BEAST_EXPECT(txMeta->isFieldPresent(sfGasUsed)))
|
|
BEAST_EXPECTS(
|
|
txMeta->getFieldU32(sfGasUsed) == allowance,
|
|
std::to_string(txMeta->getFieldU32(sfGasUsed)));
|
|
if (BEAST_EXPECT(txMeta->isFieldPresent(sfWasmReturnCode)))
|
|
BEAST_EXPECTS(
|
|
txMeta->getFieldI32(sfWasmReturnCode) == 5,
|
|
std::to_string(txMeta->getFieldI32(sfWasmReturnCode)));
|
|
|
|
env.close();
|
|
BEAST_EXPECT(env.ownerCount(alice) == 0);
|
|
}
|
|
}
|
|
|
|
{
|
|
// FinishFunction + FinishAfter
|
|
Env env(*this, features);
|
|
// create escrow
|
|
env.fund(XRP(5000), alice, carol);
|
|
auto const seq = env.seq(alice);
|
|
BEAST_EXPECT(env.ownerCount(alice) == 0);
|
|
auto const ts = env.now() + 97s;
|
|
env(escrowCreate,
|
|
escrow::finish_function(ledgerSqnWasmHex),
|
|
escrow::finish_time(ts),
|
|
escrow::cancel_time(env.now() + 1000s),
|
|
fee(createFee));
|
|
env.close();
|
|
|
|
if (BEAST_EXPECT(env.ownerCount(alice) == 2))
|
|
{
|
|
env.require(balance(alice, XRP(4000) - createFee));
|
|
env.require(balance(carol, XRP(5000)));
|
|
|
|
// finish time hasn't passed, function fails
|
|
env(escrow::finish(carol, alice, seq),
|
|
escrow::comp_allowance(allowance),
|
|
fee(finishFee + 1),
|
|
ter(tecNO_PERMISSION));
|
|
env.close();
|
|
// finish time hasn't passed, function succeeds
|
|
for (; env.now() < ts; env.close())
|
|
env(escrow::finish(carol, alice, seq),
|
|
escrow::comp_allowance(allowance),
|
|
fee(finishFee + 2),
|
|
ter(tecNO_PERMISSION));
|
|
|
|
env(escrow::finish(carol, alice, seq),
|
|
escrow::comp_allowance(allowance),
|
|
fee(finishFee + 1),
|
|
ter(tesSUCCESS));
|
|
|
|
auto const txMeta = env.meta();
|
|
if (BEAST_EXPECT(txMeta->isFieldPresent(sfGasUsed)))
|
|
BEAST_EXPECT(txMeta->getFieldU32(sfGasUsed) == allowance);
|
|
if (BEAST_EXPECT(txMeta->isFieldPresent(sfWasmReturnCode)))
|
|
BEAST_EXPECTS(
|
|
txMeta->getFieldI32(sfWasmReturnCode) == 5,
|
|
std::to_string(txMeta->getFieldI32(sfWasmReturnCode)));
|
|
|
|
BEAST_EXPECT(env.ownerCount(alice) == 0);
|
|
}
|
|
}
|
|
|
|
{
|
|
// FinishFunction + FinishAfter #2
|
|
Env env(*this, features);
|
|
// create escrow
|
|
env.fund(XRP(5000), alice, carol);
|
|
auto const seq = env.seq(alice);
|
|
BEAST_EXPECT(env.ownerCount(alice) == 0);
|
|
env(escrowCreate,
|
|
escrow::finish_function(ledgerSqnWasmHex),
|
|
escrow::finish_time(env.now() + 2s),
|
|
escrow::cancel_time(env.now() + 100s),
|
|
fee(createFee));
|
|
// Don't close the ledger here
|
|
|
|
if (BEAST_EXPECT(env.ownerCount(alice) == 2))
|
|
{
|
|
env.require(balance(alice, XRP(4000) - createFee));
|
|
env.require(balance(carol, XRP(5000)));
|
|
|
|
// finish time hasn't passed, function fails
|
|
env(escrow::finish(carol, alice, seq),
|
|
escrow::comp_allowance(allowance),
|
|
fee(finishFee),
|
|
ter(tecNO_PERMISSION));
|
|
env.close();
|
|
|
|
// finish time has passed, function fails
|
|
env(escrow::finish(carol, alice, seq),
|
|
escrow::comp_allowance(allowance),
|
|
fee(finishFee),
|
|
ter(tecWASM_REJECTED));
|
|
if (BEAST_EXPECT(env.meta()->isFieldPresent(sfGasUsed)))
|
|
BEAST_EXPECTS(
|
|
env.meta()->getFieldU32(sfGasUsed) == allowance,
|
|
std::to_string(env.meta()->getFieldU32(sfGasUsed)));
|
|
env.close();
|
|
// finish time has passed, function succeeds, tx succeeds
|
|
env(escrow::finish(carol, alice, seq),
|
|
escrow::comp_allowance(allowance),
|
|
fee(finishFee),
|
|
ter(tesSUCCESS));
|
|
|
|
auto const txMeta = env.meta();
|
|
if (BEAST_EXPECT(txMeta->isFieldPresent(sfGasUsed)))
|
|
BEAST_EXPECT(txMeta->getFieldU32(sfGasUsed) == allowance);
|
|
if (BEAST_EXPECT(txMeta->isFieldPresent(sfWasmReturnCode)))
|
|
BEAST_EXPECTS(
|
|
txMeta->getFieldI32(sfWasmReturnCode) == 5,
|
|
std::to_string(txMeta->getFieldI32(sfWasmReturnCode)));
|
|
|
|
env.close();
|
|
BEAST_EXPECT(env.ownerCount(alice) == 0);
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
testUpdateDataOnFailure(FeatureBitset features)
|
|
{
|
|
testcase("Update escrow data on failure");
|
|
|
|
using namespace jtx;
|
|
using namespace std::chrono;
|
|
|
|
// wasm that always fails
|
|
Account const alice{"alice"};
|
|
Account const carol{"carol"};
|
|
|
|
Env env(*this, features);
|
|
// create escrow
|
|
env.fund(XRP(5000), alice);
|
|
auto const seq = env.seq(alice);
|
|
BEAST_EXPECT(env.ownerCount(alice) == 0);
|
|
auto escrowCreate = escrow::create(alice, alice, XRP(1000));
|
|
XRPAmount txnFees = env.current()->fees().base * 10 + updateDataWasmHex.size() / 2 * 5;
|
|
env(escrowCreate,
|
|
escrow::finish_function(updateDataWasmHex),
|
|
escrow::finish_time(env.now() + 2s),
|
|
escrow::cancel_time(env.now() + 100s),
|
|
fee(txnFees));
|
|
env.close();
|
|
env.close();
|
|
env.close();
|
|
|
|
if (BEAST_EXPECT(env.ownerCount(alice) == (1 + updateDataWasmHex.size() / 2 / 500)))
|
|
{
|
|
env.require(balance(alice, XRP(4000) - txnFees));
|
|
|
|
auto const allowance = 1420;
|
|
XRPAmount const finishFee = env.current()->fees().base +
|
|
(allowance * env.current()->fees().gasPrice) / MICRO_DROPS_PER_DROP + 1;
|
|
|
|
// FinishAfter time hasn't passed
|
|
env(escrow::finish(alice, alice, seq),
|
|
escrow::comp_allowance(allowance),
|
|
fee(finishFee),
|
|
ter(tecWASM_REJECTED));
|
|
|
|
auto const txMeta = env.meta();
|
|
if (BEAST_EXPECT(txMeta && txMeta->isFieldPresent(sfGasUsed)))
|
|
BEAST_EXPECTS(
|
|
txMeta->getFieldU32(sfGasUsed) == allowance,
|
|
std::to_string(txMeta->getFieldU32(sfGasUsed)));
|
|
if (BEAST_EXPECT(txMeta->isFieldPresent(sfWasmReturnCode)))
|
|
BEAST_EXPECTS(
|
|
txMeta->getFieldI32(sfWasmReturnCode) == -256,
|
|
std::to_string(txMeta->getFieldI32(sfWasmReturnCode)));
|
|
|
|
auto const sle = env.le(keylet::escrow(alice, seq));
|
|
if (BEAST_EXPECT(sle && sle->isFieldPresent(sfData)))
|
|
BEAST_EXPECTS(checkVL(sle, sfData, "Data"), strHex(sle->getFieldVL(sfData)));
|
|
}
|
|
}
|
|
|
|
void
|
|
testFees(FeatureBitset features)
|
|
{
|
|
testcase("Fees");
|
|
|
|
using namespace jtx;
|
|
using namespace std::chrono;
|
|
|
|
Account const alice{"alice"};
|
|
Account const carol{"carol"};
|
|
|
|
// Tests whether the ledger index is >= 5
|
|
// getLedgerSqn() >= 5}
|
|
uint64_t const allowance = 467;
|
|
auto escrowCreate = escrow::create(alice, carol, XRP(1000));
|
|
auto createFee = [&]() {
|
|
Env env(*this, features);
|
|
auto createFee = env.current()->fees().base * 10 + ledgerSqnWasmHex.size() / 2 * 5;
|
|
return createFee;
|
|
}();
|
|
|
|
{
|
|
// ensure fees don't overflow
|
|
Env env(
|
|
*this,
|
|
envconfig([](std::unique_ptr<Config> cfg) {
|
|
cfg->FEES.gas_price = 1'000'000; // in gas
|
|
return cfg;
|
|
}),
|
|
features);
|
|
// Run past the flag ledger so that a Fee change vote occurs and
|
|
// updates FeeSettings. (It also activates all supported
|
|
// amendments.)
|
|
for (auto i = env.current()->seq(); i <= 257; ++i)
|
|
env.close();
|
|
|
|
// create escrow
|
|
env.fund(XRP(5000), alice, carol);
|
|
auto const seq = env.seq(alice);
|
|
BEAST_EXPECT(env.ownerCount(alice) == 0);
|
|
env(escrowCreate,
|
|
escrow::finish_function(ledgerSqnWasmHex),
|
|
escrow::cancel_time(env.now() + 100s),
|
|
fee(createFee));
|
|
env.close();
|
|
|
|
if (BEAST_EXPECT(env.ownerCount(alice) == 2))
|
|
{
|
|
env.require(balance(alice, XRP(4000) - createFee));
|
|
env.require(balance(carol, XRP(5000)));
|
|
env.close();
|
|
|
|
auto const bigAllowance = 996'433;
|
|
uint64_t partialFeeCalc =
|
|
(static_cast<uint64_t>(bigAllowance) * 1'000'000) / MICRO_DROPS_PER_DROP +
|
|
1; // to avoid an overflow
|
|
auto finishFee = env.current()->fees().base + partialFeeCalc;
|
|
BEAST_EXPECT(finishFee.drops() > bigAllowance);
|
|
|
|
// Intentional low value to test overflow handling
|
|
auto finishFeeOverflow = drops(30);
|
|
|
|
env(escrow::finish(alice, alice, seq),
|
|
fee(finishFeeOverflow), // enough if there's an overflow
|
|
escrow::comp_allowance(bigAllowance),
|
|
ter(telINSUF_FEE_P));
|
|
|
|
env(escrow::finish(alice, alice, seq),
|
|
fee(finishFee - 1),
|
|
escrow::comp_allowance(bigAllowance),
|
|
ter(telINSUF_FEE_P));
|
|
|
|
env(escrow::finish(alice, alice, seq),
|
|
fee(finishFee),
|
|
escrow::comp_allowance(bigAllowance),
|
|
ter(tesSUCCESS));
|
|
|
|
auto const txMeta = env.meta();
|
|
if (BEAST_EXPECT(txMeta->isFieldPresent(sfGasUsed)))
|
|
BEAST_EXPECTS(
|
|
txMeta->getFieldU32(sfGasUsed) == allowance,
|
|
std::to_string(txMeta->getFieldU32(sfGasUsed)));
|
|
if (BEAST_EXPECT(txMeta->isFieldPresent(sfWasmReturnCode)))
|
|
BEAST_EXPECTS(
|
|
txMeta->getFieldI32(sfWasmReturnCode) == 5,
|
|
std::to_string(txMeta->getFieldI32(sfWasmReturnCode)));
|
|
|
|
BEAST_EXPECT(env.ownerCount(alice) == 0);
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
testAllHostFunctions(FeatureBitset features)
|
|
{
|
|
testcase("Test all host functions");
|
|
|
|
using namespace jtx;
|
|
using namespace std::chrono;
|
|
|
|
Account const alice{"alice"};
|
|
Account const carol{"carol"};
|
|
|
|
{
|
|
Env env(*this, features);
|
|
// create escrow
|
|
env.fund(XRP(5000), alice, carol);
|
|
auto const seq = env.seq(alice);
|
|
BEAST_EXPECT(env.ownerCount(alice) == 0);
|
|
auto escrowCreate = escrow::create(alice, carol, XRP(1000));
|
|
XRPAmount txnFees =
|
|
env.current()->fees().base * 10 + allHostFunctionsWasmHex.size() / 2 * 5;
|
|
env(escrowCreate,
|
|
escrow::finish_function(allHostFunctionsWasmHex),
|
|
escrow::finish_time(env.now() + 11s),
|
|
escrow::cancel_time(env.now() + 100s),
|
|
escrow::data("1000000000"), // 1000 XRP in drops
|
|
fee(txnFees));
|
|
env.close();
|
|
|
|
if (BEAST_EXPECT(
|
|
env.ownerCount(alice) == (1 + allHostFunctionsWasmHex.size() / 2 / 500)))
|
|
{
|
|
env.require(balance(alice, XRP(4000) - txnFees));
|
|
env.require(balance(carol, XRP(5000)));
|
|
|
|
auto const allowance = 1'000'000;
|
|
XRPAmount const finishFee = env.current()->fees().base +
|
|
(allowance * env.current()->fees().gasPrice) / MICRO_DROPS_PER_DROP + 1;
|
|
|
|
// FinishAfter time hasn't passed
|
|
env(escrow::finish(carol, alice, seq),
|
|
escrow::comp_allowance(allowance),
|
|
fee(finishFee),
|
|
ter(tecNO_PERMISSION));
|
|
env.close();
|
|
env.close();
|
|
env.close();
|
|
|
|
// reduce the destination balance
|
|
env(pay(carol, alice, XRP(4500)));
|
|
env.close();
|
|
env.close();
|
|
|
|
env(escrow::finish(alice, alice, seq),
|
|
escrow::comp_allowance(allowance),
|
|
fee(finishFee),
|
|
ter(tesSUCCESS));
|
|
|
|
auto const txMeta = env.meta();
|
|
if (BEAST_EXPECT(txMeta && txMeta->isFieldPresent(sfGasUsed)))
|
|
BEAST_EXPECTS(
|
|
txMeta->getFieldU32(sfGasUsed) == 64'292,
|
|
std::to_string(txMeta->getFieldU32(sfGasUsed)));
|
|
if (BEAST_EXPECT(txMeta->isFieldPresent(sfWasmReturnCode)))
|
|
BEAST_EXPECT(txMeta->getFieldI32(sfWasmReturnCode) == 1);
|
|
|
|
env.close();
|
|
BEAST_EXPECT(env.ownerCount(alice) == 0);
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
testKeyletHostFunctions(FeatureBitset features)
|
|
{
|
|
testcase("Test all keylet host functions");
|
|
|
|
using namespace jtx;
|
|
using namespace std::chrono;
|
|
|
|
// TODO: create wasm module for all host functions
|
|
Account const alice{"alice"};
|
|
Account const carol{"carol"};
|
|
|
|
{
|
|
Env env{*this};
|
|
env.fund(XRP(10000), alice, carol);
|
|
|
|
BEAST_EXPECT(env.seq(alice) == 4);
|
|
BEAST_EXPECT(env.ownerCount(alice) == 0);
|
|
|
|
// base objects that need to be created first
|
|
auto const tokenId = token::getNextID(env, alice, 0, tfTransferable);
|
|
env(token::mint(alice, 0u), txflags(tfTransferable));
|
|
env(trust(alice, carol["USD"](1'000'000)));
|
|
env.close();
|
|
BEAST_EXPECT(env.seq(alice) == 6);
|
|
BEAST_EXPECT(env.ownerCount(alice) == 2);
|
|
|
|
// set up a bunch of objects to check their keylets
|
|
AMM amm(env, carol, XRP(10), carol["USD"](1000));
|
|
env(check::create(alice, carol, XRP(100)));
|
|
env(credentials::create(alice, alice, "termsandconditions"));
|
|
env(delegate::set(alice, carol, {"TrustSet"}));
|
|
env(deposit::auth(alice, carol));
|
|
env(did::set(alice), did::data("alice_did"));
|
|
env(escrow::create(alice, carol, XRP(100)), escrow::finish_time(env.now() + 100s));
|
|
MPTTester mptTester{env, alice, {.fund = false}};
|
|
mptTester.create();
|
|
mptTester.authorize({.account = carol});
|
|
env(token::createOffer(carol, tokenId, XRP(100)), token::owner(alice));
|
|
env(offer(alice, carol["GBP"](0.1), XRP(100)));
|
|
env(paychan::create(alice, carol, XRP(1000), 100s, alice.pk()));
|
|
pdomain::Credentials credentials{{alice, "first credential"}};
|
|
env(pdomain::setTx(alice, credentials));
|
|
env(signers(alice, 1, {{carol, 1}}));
|
|
env(ticket::create(alice, 1));
|
|
Vault vault{env};
|
|
auto [tx, _keylet] = vault.create({.owner = alice, .asset = xrpIssue()});
|
|
env(tx);
|
|
env.close();
|
|
|
|
BEAST_EXPECTS(env.ownerCount(alice) == 17, std::to_string(env.ownerCount(alice)));
|
|
if (BEAST_EXPECTS(env.seq(alice) == 20, std::to_string(env.seq(alice))))
|
|
{
|
|
auto const seq = env.seq(alice);
|
|
XRPAmount txnFees =
|
|
env.current()->fees().base * 10 + allKeyletsWasmHex.size() / 2 * 5;
|
|
env(escrow::create(alice, carol, XRP(1000)),
|
|
escrow::finish_function(allKeyletsWasmHex),
|
|
escrow::finish_time(env.now() + 2s),
|
|
escrow::cancel_time(env.now() + 100s),
|
|
fee(txnFees));
|
|
env.close();
|
|
env.close();
|
|
env.close();
|
|
|
|
auto const allowance = 184'444;
|
|
auto const finishFee = env.current()->fees().base +
|
|
(allowance * env.current()->fees().gasPrice) / MICRO_DROPS_PER_DROP + 1;
|
|
env(escrow::finish(carol, alice, seq),
|
|
escrow::comp_allowance(allowance),
|
|
fee(finishFee));
|
|
env.close();
|
|
|
|
auto const txMeta = env.meta();
|
|
if (BEAST_EXPECT(txMeta && txMeta->isFieldPresent(sfGasUsed)))
|
|
{
|
|
auto const gasUsed = txMeta->getFieldU32(sfGasUsed);
|
|
BEAST_EXPECTS(gasUsed == allowance, std::to_string(gasUsed));
|
|
}
|
|
BEAST_EXPECTS(env.ownerCount(alice) == 17, std::to_string(env.ownerCount(alice)));
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
testLargeWasmModules(FeatureBitset features)
|
|
{
|
|
testcase("Test large wasm modules");
|
|
|
|
using namespace jtx;
|
|
using namespace std::chrono;
|
|
using namespace wasm_constants;
|
|
|
|
enum class ExpectedStatus { Success, Malformed, Crash };
|
|
|
|
auto runTest = [&](std::vector<uint8_t> const& wasm,
|
|
std::optional<uint32_t> sizeLimit,
|
|
ExpectedStatus expectedStatus,
|
|
std::source_location const& loc = std::source_location::current()) {
|
|
auto makeEnv = [&]() -> Env {
|
|
if (sizeLimit)
|
|
return Env(
|
|
*this,
|
|
envconfig([&sizeLimit](std::unique_ptr<Config> cfg) {
|
|
cfg->FEES.extension_size_limit = *sizeLimit;
|
|
return cfg;
|
|
}),
|
|
features);
|
|
else
|
|
return Env(*this, features);
|
|
};
|
|
Env env = makeEnv();
|
|
|
|
auto const alice = Account("alice");
|
|
env.fund(XRP(1'000'000), alice);
|
|
env.close();
|
|
|
|
auto const wasmHex = strHex(wasm);
|
|
try
|
|
{
|
|
env(escrow::create(alice, alice, XRP(1000)),
|
|
escrow::finish_function(wasmHex),
|
|
escrow::cancel_time(env.now() + 100s),
|
|
fee(env.current()->fees().base * 10 + wasmHex.size() / 2 * 5),
|
|
ter(expectedStatus == ExpectedStatus::Success ? TER{tesSUCCESS}
|
|
: TER{temMALFORMED}));
|
|
if (expectedStatus == ExpectedStatus::Crash)
|
|
fail("Expected crash", loc.file_name(), loc.line());
|
|
else
|
|
pass();
|
|
}
|
|
catch (std::exception const& e)
|
|
{
|
|
if (expectedStatus == ExpectedStatus::Crash)
|
|
pass();
|
|
else
|
|
fail(e.what(), loc.file_name(), loc.line());
|
|
}
|
|
};
|
|
|
|
// Table-driven test cases
|
|
struct TestCase
|
|
{
|
|
enum class BlobType { Code, Data };
|
|
BlobType type;
|
|
uint32_t size;
|
|
std::optional<uint32_t> sizeLimit;
|
|
ExpectedStatus expected;
|
|
};
|
|
|
|
std::vector<TestCase> const testCases = {
|
|
// Code blob tests
|
|
{TestCase::BlobType::Code,
|
|
99'959,
|
|
std::nullopt,
|
|
ExpectedStatus::Success}, // just under 100kb
|
|
{TestCase::BlobType::Code,
|
|
99'961,
|
|
std::nullopt,
|
|
ExpectedStatus::Malformed}, // just over 100kb
|
|
{TestCase::BlobType::Code, 200'000, 10'000'000, ExpectedStatus::Success}, // ~200kb
|
|
{TestCase::BlobType::Code,
|
|
490'000,
|
|
10'000'000,
|
|
ExpectedStatus::Success}, // just under 1MB JSON
|
|
{TestCase::BlobType::Code,
|
|
999'999,
|
|
10'000'000,
|
|
ExpectedStatus::Crash}, // just over 1MB JSON
|
|
// Data blob tests
|
|
{TestCase::BlobType::Data,
|
|
99'946,
|
|
std::nullopt,
|
|
ExpectedStatus::Success}, // just under 100kb
|
|
{TestCase::BlobType::Data,
|
|
99'948,
|
|
std::nullopt,
|
|
ExpectedStatus::Malformed}, // just over 100kb
|
|
{TestCase::BlobType::Data, 200'000, 10'000'000, ExpectedStatus::Success}, // ~200kb
|
|
{TestCase::BlobType::Data,
|
|
490'000,
|
|
10'000'000,
|
|
ExpectedStatus::Success}, // just under 1MB JSON
|
|
{TestCase::BlobType::Data,
|
|
999'950,
|
|
10'000'000,
|
|
ExpectedStatus::Crash}, // just over 1MB JSON
|
|
};
|
|
|
|
for (auto const& tc : testCases)
|
|
{
|
|
auto const wasm = tc.type == TestCase::BlobType::Code ? generateCodeBlob(tc.size)
|
|
: generateDataBlob(tc.size);
|
|
runTest(wasm, tc.sizeLimit, tc.expected);
|
|
}
|
|
}
|
|
|
|
void
|
|
testWithFeats(FeatureBitset features)
|
|
{
|
|
testCreateFinishFunctionPreflight(features);
|
|
testFinishWasmFailures(features);
|
|
testFinishFunction(features);
|
|
testUpdateDataOnFailure(features);
|
|
testFees(features);
|
|
|
|
// TODO: Update module with new host functions
|
|
testAllHostFunctions(features);
|
|
testKeyletHostFunctions(features);
|
|
|
|
testLargeWasmModules(features);
|
|
}
|
|
|
|
public:
|
|
void
|
|
run() override
|
|
{
|
|
using namespace test::jtx;
|
|
FeatureBitset const all{testable_amendments()};
|
|
testWithFeats(all);
|
|
}
|
|
};
|
|
|
|
BEAST_DEFINE_TESTSUITE(EscrowSmart, app, xrpl);
|
|
|
|
} // namespace test
|
|
} // namespace xrpl
|