Compare commits

..

3 Commits

Author SHA1 Message Date
Mayukha Vadari
e40a4df777 Merge branch 'ripple/wasmi-host-functions' into ripple/se/fees 2025-11-25 03:49:06 +05:30
Mayukha Vadari
7a7b96107c Merge branch 'ripple/wasmi' into ripple/wasmi-host-functions 2025-11-25 03:42:05 +05:30
Olek
500bb68831 Fix win build (#6076) 2025-11-24 16:56:23 -05:00
28 changed files with 106 additions and 1463 deletions

View File

@@ -1,8 +1,17 @@
diff --git a/crates/c_api/CMakeLists.txt b/crates/c_api/CMakeLists.txt
index b15c787a..4e6de690 100644
index b15c787a..54eaed2d 100644
--- a/crates/c_api/CMakeLists.txt
+++ b/crates/c_api/CMakeLists.txt
@@ -43,6 +43,10 @@ endif()
@@ -6,6 +6,8 @@ option(BUILD_SHARED_LIBS "Build using shared libraries" OFF)
option(WASMI_ALWAYS_BUILD "If cmake should always invoke cargo to build Wasmi" ON)
set(WASMI_TARGET "" CACHE STRING "Rust target to build for")
+add_compile_definitions(COMPILING_WASM_RUNTIME_API=1)
+
if(NOT WASMI_TARGET)
execute_process(
COMMAND rustc -vV
@@ -43,6 +45,10 @@ endif()
list(TRANSFORM WASMI_SHARED_FILES PREPEND ${WASMI_TARGET_DIR}/)
list(TRANSFORM WASMI_STATIC_FILES PREPEND ${WASMI_TARGET_DIR}/)
@@ -13,7 +22,15 @@ index b15c787a..4e6de690 100644
# Instructions on how to build and install the Wasmi Rust crate.
find_program(WASMI_CARGO_BINARY cargo REQUIRED)
include(ExternalProject)
@@ -112,6 +116,7 @@ install(
@@ -79,7 +85,6 @@ else()
target_link_libraries(wasmi INTERFACE ${WASMI_STATIC_FILES})
if(WASMI_TARGET MATCHES "windows")
- target_compile_options(wasmi INTERFACE -DWASM_API_EXTERN= -DWASI_API_EXTERN=)
target_link_libraries(wasmi INTERFACE ws2_32 advapi32 userenv ntdll shell32 ole32 bcrypt)
elseif(NOT WASMI_TARGET MATCHES "darwin")
target_link_libraries(wasmi INTERFACE pthread dl m)
@@ -112,6 +117,7 @@ install(
DESTINATION ${CMAKE_INSTALL_LIBDIR}
)
@@ -21,7 +38,7 @@ index b15c787a..4e6de690 100644
if(WASMI_TARGET MATCHES "darwin")
set(INSTALLED_LIB "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR}/libwasmi.dylib")
install(
@@ -131,6 +136,7 @@ if(WASMI_TARGET MATCHES "darwin")
@@ -131,6 +137,7 @@ if(WASMI_TARGET MATCHES "darwin")
install(CODE "execute_process(COMMAND ${install_name_tool_cmd})")
endif()
endif()
@@ -29,7 +46,7 @@ index b15c787a..4e6de690 100644
# Documentation Generation via Doxygen:
set(DOXYGEN_CONF_IN ${CMAKE_CURRENT_SOURCE_DIR}/doxygen.conf.in)
@@ -141,19 +147,3 @@ add_custom_target(doc
@@ -141,19 +148,3 @@ add_custom_target(doc
DEPENDS ${WASMI_GENERATED_CONF_H} ${DOXYGEN_CONF_OUT}
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
)
@@ -50,10 +67,29 @@ index b15c787a..4e6de690 100644
- COMMENT "clang-format: Apply formatting rules for Wasmi C-API header files"
-)
diff --git a/crates/c_api/include/wasm.h b/crates/c_api/include/wasm.h
index 5ee617ff..0199192d 100644
index 5ee617ff..37809a6f 100644
--- a/crates/c_api/include/wasm.h
+++ b/crates/c_api/include/wasm.h
@@ -146,6 +146,13 @@ WASM_DECLARE_OWN(store)
@@ -13,11 +13,17 @@
#include <assert.h>
#ifndef WASM_API_EXTERN
-#if defined(_WIN32) && !defined(__MINGW32__) && !defined(LIBWASM_STATIC)
+#if defined(_MSC_BUILD)
+#if defined(COMPILING_WASM_RUNTIME_API)
+#define WASM_API_EXTERN __declspec(dllexport)
+#elif defined(_DLL)
#define WASM_API_EXTERN __declspec(dllimport)
#else
#define WASM_API_EXTERN
#endif
+#else
+#define WASM_API_EXTERN
+#endif
#endif
#ifdef __cplusplus
@@ -146,6 +152,13 @@ WASM_DECLARE_OWN(store)
WASM_API_EXTERN own wasm_store_t* wasm_store_new(wasm_engine_t*);

View File

@@ -55,18 +55,6 @@ public:
deliver_ = amount;
}
void
setGasUsed(std::optional<std::uint32_t> const gasUsed)
{
gasUsed_ = gasUsed;
}
void
setWasmReturnCode(std::int32_t const wasmReturnCode)
{
wasmReturnCode_ = wasmReturnCode;
}
/** Get the number of modified entries
*/
std::size_t
@@ -85,8 +73,6 @@ public:
private:
std::optional<STAmount> deliver_;
std::optional<std::uint32_t> gasUsed_;
std::optional<std::int32_t> wasmReturnCode_;
};
} // namespace ripple

View File

@@ -53,8 +53,6 @@ public:
TER ter,
std::optional<STAmount> const& deliver,
std::optional<uint256 const> const& parentBatchId,
std::optional<std::uint32_t> const& gasUsed,
std::optional<std::int32_t> const& wasmReturnCode,
bool isDryRun,
beast::Journal j);

View File

@@ -212,12 +212,6 @@ page(Keylet const& root, std::uint64_t index = 0) noexcept
Keylet
escrow(AccountID const& src, std::uint32_t seq) noexcept;
inline Keylet
escrow(uint256 const& key) noexcept
{
return {ltESCROW, key};
}
/** A PaymentChannel */
Keylet
payChan(AccountID const& src, AccountID const& dst, std::uint32_t seq) noexcept;

View File

@@ -168,8 +168,6 @@ enum TEFcodes : TERUnderlyingType {
tefNO_TICKET,
tefNFTOKEN_IS_NOT_TRANSFERABLE,
tefINVALID_LEDGER_FIX_TYPE,
tefNO_WASM,
tefWASM_FIELD_NOT_INCLUDED,
};
//------------------------------------------------------------------------------
@@ -351,7 +349,6 @@ enum TECcodes : TERUnderlyingType {
// backward compatibility with historical data on non-prod networks, can be
// reclaimed after those networks reset.
tecNO_DELEGATE_PERMISSION = 198,
tecWASM_REJECTED = 199,
};
//------------------------------------------------------------------------------

View File

@@ -85,12 +85,6 @@ public:
if (obj.isFieldPresent(sfParentBatchID))
parentBatchID_ = obj.getFieldH256(sfParentBatchID);
if (obj.isFieldPresent(sfGasUsed))
gasUsed_ = obj.getFieldU32(sfGasUsed);
if (obj.isFieldPresent(sfWasmReturnCode))
wasmReturnCode_ = obj.getFieldI32(sfWasmReturnCode);
}
std::optional<STAmount> const&
@@ -111,30 +105,6 @@ public:
parentBatchID_ = id;
}
void
setGasUsed(std::optional<std::uint32_t> const gasUsed)
{
gasUsed_ = gasUsed;
}
std::optional<std::uint32_t> const&
getGasUsed() const
{
return gasUsed_;
}
void
setWasmReturnCode(std::optional<std::int32_t> const wasmReturnCode)
{
wasmReturnCode_ = wasmReturnCode;
}
std::optional<std::int32_t> const&
getWasmReturnCode() const
{
return wasmReturnCode_;
}
private:
uint256 transactionID_;
std::uint32_t ledgerSeq_;
@@ -143,8 +113,6 @@ private:
std::optional<STAmount> deliveredAmount_;
std::optional<uint256> parentBatchID_;
std::optional<std::uint32_t> gasUsed_;
std::optional<std::int32_t> wasmReturnCode_;
STArray nodes_;
};

View File

@@ -336,8 +336,6 @@ LEDGER_ENTRY(ltESCROW, 0x0075, Escrow, escrow, ({
{sfCondition, soeOPTIONAL},
{sfCancelAfter, soeOPTIONAL},
{sfFinishAfter, soeOPTIONAL},
{sfFinishFunction, soeOPTIONAL},
{sfData, soeOPTIONAL},
{sfSourceTag, soeOPTIONAL},
{sfDestinationTag, soeOPTIONAL},
{sfOwnerNode, soeREQUIRED},

View File

@@ -99,8 +99,6 @@ TYPED_SFIELD(sfMutableFlags, UINT32, 53)
TYPED_SFIELD(sfExtensionComputeLimit, UINT32, 54)
TYPED_SFIELD(sfExtensionSizeLimit, UINT32, 55)
TYPED_SFIELD(sfGasPrice, UINT32, 56)
TYPED_SFIELD(sfComputationAllowance, UINT32, 57)
TYPED_SFIELD(sfGasUsed, UINT32, 58)
// 64-bit integers (common)
TYPED_SFIELD(sfIndexNext, UINT64, 1)
@@ -194,8 +192,11 @@ TYPED_SFIELD(sfAssetsMaximum, NUMBER, 3)
TYPED_SFIELD(sfAssetsTotal, NUMBER, 4)
TYPED_SFIELD(sfLossUnrealized, NUMBER, 5)
// 32-bit signed (common)
TYPED_SFIELD(sfWasmReturnCode, INT32, 1)
// int32
// NOTE: Do not use `sfDummyInt32`. It's so far the only use of INT32
// in this file and has been defined here for test only.
// TODO: Replace `sfDummyInt32` with actually useful field.
TYPED_SFIELD(sfDummyInt32, INT32, 1) // for tests only
// currency amount (common)
TYPED_SFIELD(sfAmount, AMOUNT, 1)
@@ -225,7 +226,7 @@ TYPED_SFIELD(sfBaseFeeDrops, AMOUNT, 22)
TYPED_SFIELD(sfReserveBaseDrops, AMOUNT, 23)
TYPED_SFIELD(sfReserveIncrementDrops, AMOUNT, 24)
// currency amount (more)
// currency amount (AMM)
TYPED_SFIELD(sfLPTokenOut, AMOUNT, 25)
TYPED_SFIELD(sfLPTokenIn, AMOUNT, 26)
TYPED_SFIELD(sfEPrice, AMOUNT, 27)
@@ -267,7 +268,6 @@ TYPED_SFIELD(sfAssetClass, VL, 28)
TYPED_SFIELD(sfProvider, VL, 29)
TYPED_SFIELD(sfMPTokenMetadata, VL, 30)
TYPED_SFIELD(sfCredentialType, VL, 31)
TYPED_SFIELD(sfFinishFunction, VL, 32)
// account (common)
TYPED_SFIELD(sfAccount, ACCOUNT, 1)

View File

@@ -50,13 +50,11 @@ TRANSACTION(ttESCROW_CREATE, 1, EscrowCreate,
noPriv,
({
{sfDestination, soeREQUIRED},
{sfDestinationTag, soeOPTIONAL},
{sfAmount, soeREQUIRED, soeMPTSupported},
{sfCondition, soeOPTIONAL},
{sfCancelAfter, soeOPTIONAL},
{sfFinishAfter, soeOPTIONAL},
{sfFinishFunction, soeOPTIONAL},
{sfData, soeOPTIONAL},
{sfDestinationTag, soeOPTIONAL},
}))
/** This transaction type completes an existing escrow. */
@@ -70,7 +68,6 @@ TRANSACTION(ttESCROW_FINISH, 2, EscrowFinish,
{sfFulfillment, soeOPTIONAL},
{sfCondition, soeOPTIONAL},
{sfCredentialIDs, soeOPTIONAL},
{sfComputationAllowance, soeOPTIONAL},
}))

View File

@@ -97,8 +97,6 @@ ApplyStateTable::apply(
TER ter,
std::optional<STAmount> const& deliver,
std::optional<uint256 const> const& parentBatchId,
std::optional<std::uint32_t> const& gasUsed,
std::optional<std::int32_t> const& wasmReturnCode,
bool isDryRun,
beast::Journal j)
{
@@ -113,8 +111,6 @@ ApplyStateTable::apply(
meta.setDeliveredAmount(deliver);
meta.setParentBatchID(parentBatchId);
meta.setGasUsed(gasUsed);
meta.setWasmReturnCode(wasmReturnCode);
Mods newMod;
for (auto& item : items_)

View File

@@ -16,16 +16,7 @@ ApplyViewImpl::apply(
bool isDryRun,
beast::Journal j)
{
return items_.apply(
to,
tx,
ter,
deliver_,
parentBatchId,
gasUsed_,
wasmReturnCode_,
isDryRun,
j);
return items_.apply(to, tx, ter, deliver_, parentBatchId, isDryRun, j);
}
std::size_t

View File

@@ -108,7 +108,6 @@ transResults()
MAKE_ERROR(tecLIMIT_EXCEEDED, "Limit exceeded."),
MAKE_ERROR(tecPSEUDO_ACCOUNT, "This operation is not allowed against a pseudo-account."),
MAKE_ERROR(tecPRECISION_LOSS, "The amounts used by the transaction cannot interact."),
MAKE_ERROR(tecWASM_REJECTED, "The custom WASM code that was run rejected your transaction."),
MAKE_ERROR(tefALREADY, "The exact transaction was already in this ledger."),
MAKE_ERROR(tefBAD_ADD_AUTH, "Not authorized to add account."),
@@ -132,8 +131,6 @@ transResults()
MAKE_ERROR(tefNO_TICKET, "Ticket is not in ledger."),
MAKE_ERROR(tefNFTOKEN_IS_NOT_TRANSFERABLE, "The specified NFToken is not transferable."),
MAKE_ERROR(tefINVALID_LEDGER_FIX_TYPE, "The LedgerFixType field has an invalid value."),
MAKE_ERROR(tefNO_WASM, "There is no WASM code to run, but a WASM-specific field was included."),
MAKE_ERROR(tefWASM_FIELD_NOT_INCLUDED, "WASM code requires a field to be included that was not included."),
MAKE_ERROR(telLOCAL_ERROR, "Local failure."),
MAKE_ERROR(telBAD_DOMAIN, "Domain too long."),

View File

@@ -211,12 +211,6 @@ TxMeta::getAsObject() const
if (parentBatchID_.has_value())
metaData.setFieldH256(sfParentBatchID, *parentBatchID_);
if (gasUsed_.has_value())
metaData.setFieldU32(sfGasUsed, *gasUsed_);
if (wasmReturnCode_.has_value())
metaData.setFieldI32(sfWasmReturnCode, *wasmReturnCode_);
return metaData;
}

View File

@@ -7466,7 +7466,7 @@ private:
using namespace test::jtx;
auto const testCase = [&](std::string suffix, FeatureBitset features) {
testcase("Pseudo-account allocation failure " + suffix);
testcase("Fail pseudo-account allocation " + suffix);
std::string logs;
Env env{*this, features, std::make_unique<CaptureLogs>(&logs)};
env.fund(XRP(30'000), gw, alice);

View File

@@ -1,916 +0,0 @@
#include <test/app/wasm_fixtures/fixtures.h>
#include <test/jtx.h>
#include <xrpld/app/tx/applySteps.h>
#include <xrpld/app/wasm/WasmVM.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 <algorithm>
#include <iterator>
namespace ripple {
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}
static auto wasmHex = ledgerSqnWasmHex;
{
// featureSmartEscrow disabled
Env env(*this, features - featureSmartEscrow);
env.fund(XRP(5000), alice, carol);
XRPAmount const txnFees = env.current()->fees().base + 1000;
auto escrowCreate = escrow::create(alice, carol, XRP(1000));
env(escrowCreate,
escrow::finish_function(wasmHex),
escrow::cancel_time(env.now() + 100s),
fee(txnFees),
ter(temDISABLED));
env.close();
env(escrowCreate,
escrow::finish_function(wasmHex),
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 escrowCreate = escrow::create(alice, carol, XRP(500));
// 11-byte string
std::string longWasmHex = "00112233445566778899AA";
env(escrowCreate,
escrow::finish_function(longWasmHex),
escrow::cancel_time(env.now() + 100s),
fee(txnFees),
ter(temMALFORMED));
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 escrowCreate = escrow::create(alice, carol, XRP(500));
std::string 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 escrowCreate = escrow::create(alice, carol, XRP(500));
// string of length maxWasmDataLength * 2 + 2
std::string longData(maxWasmDataLength * 2 + 2, 'B');
env(escrowCreate,
escrow::data(longData),
escrow::finish_function(wasmHex),
escrow::cancel_time(env.now() + 100s),
fee(txnFees),
ter(temMALFORMED));
env.close();
}
Env env(
*this,
envconfig([](std::unique_ptr<Config> cfg) {
cfg->START_UP = Config::FRESH;
return cfg;
}),
features);
XRPAmount const txnFees =
env.current()->fees().base * 10 + wasmHex.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(wasmHex),
escrow::cancel_time(env.now() + 20s),
fee(txnFees));
env.close();
}
{
// FinishFunction + Condition + CancelAfter
env(escrowCreate,
escrow::finish_function(wasmHex),
escrow::cancel_time(env.now() + 30s),
escrow::condition(escrow::cb1),
fee(txnFees));
env.close();
}
{
// FinishFunction + FinishAfter + CancelAfter
env(escrowCreate,
escrow::finish_function(wasmHex),
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(wasmHex),
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(wasmHex),
fee(txnFees),
ter(temBAD_EXPIRATION));
env.close();
}
{
// FinishFunction + FinishAfter
env(escrowCreate,
escrow::finish_function(wasmHex),
escrow::finish_time(env.now() + 2s),
fee(txnFees),
ter(temBAD_EXPIRATION));
env.close();
}
{
// FinishFunction + Condition
env(escrowCreate,
escrow::finish_function(wasmHex),
escrow::condition(escrow::cb1),
fee(txnFees),
ter(temBAD_EXPIRATION));
env.close();
}
{
// FinishFunction + FinishAfter + Condition
env(escrowCreate,
escrow::finish_function(wasmHex),
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(wasmHex),
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}
static auto wasmHex = ledgerSqnWasmHex;
{
// featureSmartEscrow disabled
Env env(*this, features - featureSmartEscrow);
env.fund(XRP(5000), alice, carol);
XRPAmount const txnFees =
env.current()->fees().base * 10 + wasmHex.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));
}
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 + wasmHex.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(wasmHex),
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}
auto const& wasmHex = ledgerSqnWasmHex;
std::uint32_t const allowance = 5;
auto escrowCreate = escrow::create(alice, carol, XRP(1000));
auto [createFee, finishFee] = [&]() {
Env env(*this, features);
auto createFee =
env.current()->fees().base * 10 + wasmHex.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(wasmHex),
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(wasmHex),
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_EXPECT(txMeta->getFieldU32(sfGasUsed) == allowance);
if (BEAST_EXPECT(txMeta->isFieldPresent(sfWasmReturnCode)))
BEAST_EXPECTS(
txMeta->getFieldI32(sfWasmReturnCode) == 6,
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(wasmHex),
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) == 13,
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(wasmHex),
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) == 6,
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
static auto const wasmHex = updateDataWasmHex;
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 + wasmHex.size() / 2 * 5;
env(escrowCreate,
escrow::finish_function(wasmHex),
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 + wasmHex.size() / 2 / 500)))
{
env.require(balance(alice, XRP(4000) - txnFees));
auto const allowance = 14;
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
testAllHostFunctions(FeatureBitset features)
{
testcase("Test all host functions");
using namespace jtx;
using namespace std::chrono;
// TODO: create wasm module for all host functions
static auto wasmHex = allHostFunctionsWasmHex;
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 + wasmHex.size() / 2 * 5;
env(escrowCreate,
escrow::finish_function(wasmHex),
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 + wasmHex.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) == 794,
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
static auto wasmHex = allKeyletsWasmHex;
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 + wasmHex.size() / 2 * 5;
env(escrow::create(alice, carol, XRP(1000)),
escrow::finish_function(wasmHex),
escrow::finish_time(env.now() + 2s),
escrow::cancel_time(env.now() + 100s),
fee(txnFees));
env.close();
env.close();
env.close();
auto const allowance = 2'985;
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
testWithFeats(FeatureBitset features)
{
testCreateFinishFunctionPreflight(features);
testFinishWasmFailures(features);
testFinishFunction(features);
testUpdateDataOnFailure(features);
// TODO: Update module with new host functions
testAllHostFunctions(features);
testKeyletHostFunctions(features);
}
public:
void
run() override
{
using namespace test::jtx;
FeatureBitset const all{testable_amendments()};
testWithFeats(all);
}
};
BEAST_DEFINE_TESTSUITE(EscrowSmart, app, ripple);
} // namespace test
} // namespace ripple

View File

@@ -1507,7 +1507,7 @@ struct Escrow_test : public beast::unit_test::suite
Account const alice{"alice"};
Account const bob{"bob"};
Account const carol{"carol"};
Account const dillon{"dillon"};
Account const dillon{"dillon "};
Account const zelda{"zelda"};
char const credType[] = "abcde";
@@ -1674,8 +1674,6 @@ public:
FeatureBitset const all{testable_amendments()};
testWithFeats(all);
testWithFeats(all - featureTokenEscrow);
testWithFeats(all - featureSmartEscrow);
testWithFeats(all - featureTokenEscrow - featureSmartEscrow);
testTags(all - fixIncludeKeyletFields);
}
};

View File

@@ -1301,8 +1301,7 @@ struct HostFuncImpl_test : public beast::unit_test::suite
BEAST_EXPECT(result.has_value() && result.value() == data.size());
// Should fail for too large data
std::vector<uint8_t> bigData(
1024 * 1024 + 1, 0x42); // > maxWasmDataLength
std::vector<uint8_t> bigData(maxWasmDataLength + 1, 0x42);
auto const tooBig =
hfs.updateData(Slice(bigData.data(), bigData.size()));
if (BEAST_EXPECT(!tooBig.has_value()))

View File

@@ -10276,21 +10276,3 @@ extern std::string const disabledFloatHex =
"6503050b5f5f686561705f6261736503060a5f5f686561705f656e640307"
"0d5f5f6d656d6f72795f6261736503080c5f5f7461626c655f6261736503"
"090a150202000b100043000000c54300200045921a41010b";
extern std::string const updateDataWasmHex =
"0061736d01000000010e0360027f7f017f6000006000017f02130103656e760b7570646174"
"655f64617461000003030201020503010002063f0a7f01419088040b7f004180080b7f0041"
"85080b7f004190080b7f00419088040b7f004180080b7f00419088040b7f00418080080b7f"
"0041000b7f0041010b07aa010c066d656d6f72790200115f5f7761736d5f63616c6c5f6374"
"6f727300010666696e69736800020c5f5f64736f5f68616e646c6503010a5f5f646174615f"
"656e6403020b5f5f737461636b5f6c6f7703030c5f5f737461636b5f6869676803040d5f5f"
"676c6f62616c5f6261736503050b5f5f686561705f6261736503060a5f5f686561705f656e"
"6403070d5f5f6d656d6f72795f6261736503080c5f5f7461626c655f6261736503090a3f02"
"02000b3a01017f230041106b220024002000410c6a4184082d00003a000020004180082800"
"00360208200041086a410410001a200041106a240041807e0b0b0b01004180080b04446174"
"61007f0970726f647563657273010c70726f6365737365642d62790105636c616e675f3139"
"2e312e352d776173692d73646b202868747470733a2f2f6769746875622e636f6d2f6c6c76"
"6d2f6c6c766d2d70726f6a6563742061623462356132646235383239353861663165653330"
"3861373930636664623432626432343732302900490f7461726765745f6665617475726573"
"042b0f6d757461626c652d676c6f62616c732b087369676e2d6578742b0f7265666572656e"
"63652d74797065732b0a6d756c746976616c7565";

View File

@@ -31,5 +31,3 @@ extern std::string const floatTestsWasmHex;
extern std::string const float0Hex;
extern std::string const disabledFloatHex;
extern std::string const updateDataWasmHex;

View File

@@ -1,13 +0,0 @@
#include <stdint.h>
int32_t
update_data(uint8_t const*, int32_t);
int
finish()
{
uint8_t buf[] = "Data";
update_data(buf, sizeof(buf) - 1);
return -256;
}

View File

@@ -85,76 +85,6 @@ auto const condition = JTxFieldWrapper<blobField>(sfCondition);
auto const fulfillment = JTxFieldWrapper<blobField>(sfFulfillment);
struct finish_function
{
private:
std::string value_;
public:
explicit finish_function(std::string func) : value_(func)
{
}
explicit finish_function(Slice const& func) : value_(strHex(func))
{
}
template <size_t N>
explicit finish_function(std::array<std::uint8_t, N> const& f)
: finish_function(makeSlice(f))
{
}
void
operator()(Env&, JTx& jt) const
{
jt.jv[sfFinishFunction.jsonName] = value_;
}
};
struct data
{
private:
std::string value_;
public:
explicit data(std::string func) : value_(func)
{
}
explicit data(Slice const& func) : value_(strHex(func))
{
}
template <size_t N>
explicit data(std::array<std::uint8_t, N> const& f) : data(makeSlice(f))
{
}
void
operator()(Env&, JTx& jt) const
{
jt.jv[sfData.jsonName] = value_;
}
};
struct comp_allowance
{
private:
std::uint32_t value_;
public:
explicit comp_allowance(std::uint32_t const& value) : value_(value)
{
}
void
operator()(Env&, JTx& jt) const
{
jt.jv[sfComputationAllowance.jsonName] = value_;
}
};
} // namespace escrow
} // namespace jtx

View File

@@ -14,14 +14,9 @@ setupConfigForUnitTests(Config& cfg)
using namespace jtx;
// Default fees to old values, so tests don't have to worry about changes in
// Config.h
// NOTE: For new `FEES` fields, you need to wait for the first flag ledger
// to close for the values to be activated.
cfg.FEES.reference_fee = UNIT_TEST_REFERENCE_FEE;
cfg.FEES.account_reserve = XRP(200).value().xrp().drops();
cfg.FEES.owner_reserve = XRP(50).value().xrp().drops();
cfg.FEES.extension_compute_limit = 1'000'000;
cfg.FEES.extension_size_limit = 100'000;
cfg.FEES.gas_price = 1'000'000; // 1 drop = 1,000,000 micro-drops
// The Beta API (currently v2) is always available to tests
cfg.BETA_RPC_API = true;

View File

@@ -724,66 +724,63 @@ class STParsedJSON_test : public beast::unit_test::suite
{
Json::Value j;
int const minInt32 = -2147483648;
j[sfWasmReturnCode] = minInt32;
j[sfDummyInt32] = minInt32;
STParsedJSONObject obj("Test", j);
BEAST_EXPECT(obj.object.has_value());
if (BEAST_EXPECT(obj.object->isFieldPresent(sfWasmReturnCode)))
BEAST_EXPECT(
obj.object->getFieldI32(sfWasmReturnCode) == minInt32);
if (BEAST_EXPECT(obj.object->isFieldPresent(sfDummyInt32)))
BEAST_EXPECT(obj.object->getFieldI32(sfDummyInt32) == minInt32);
}
// max value
{
Json::Value j;
int const maxInt32 = 2147483647;
j[sfWasmReturnCode] = maxInt32;
j[sfDummyInt32] = maxInt32;
STParsedJSONObject obj("Test", j);
BEAST_EXPECT(obj.object.has_value());
if (BEAST_EXPECT(obj.object->isFieldPresent(sfWasmReturnCode)))
BEAST_EXPECT(
obj.object->getFieldI32(sfWasmReturnCode) == maxInt32);
if (BEAST_EXPECT(obj.object->isFieldPresent(sfDummyInt32)))
BEAST_EXPECT(obj.object->getFieldI32(sfDummyInt32) == maxInt32);
}
// max uint value
{
Json::Value j;
unsigned int const maxUInt32 = 2147483647u;
j[sfWasmReturnCode] = maxUInt32;
j[sfDummyInt32] = maxUInt32;
STParsedJSONObject obj("Test", j);
BEAST_EXPECT(obj.object.has_value());
if (BEAST_EXPECT(obj.object->isFieldPresent(sfWasmReturnCode)))
if (BEAST_EXPECT(obj.object->isFieldPresent(sfDummyInt32)))
BEAST_EXPECT(
obj.object->getFieldI32(sfWasmReturnCode) ==
obj.object->getFieldI32(sfDummyInt32) ==
static_cast<int32_t>(maxUInt32));
}
// Test with string value
{
Json::Value j;
j[sfWasmReturnCode] = "2147483647";
j[sfDummyInt32] = "2147483647";
STParsedJSONObject obj("Test", j);
BEAST_EXPECT(obj.object.has_value());
if (BEAST_EXPECT(obj.object->isFieldPresent(sfWasmReturnCode)))
if (BEAST_EXPECT(obj.object->isFieldPresent(sfDummyInt32)))
BEAST_EXPECT(
obj.object->getFieldI32(sfWasmReturnCode) == 2147483647u);
obj.object->getFieldI32(sfDummyInt32) == 2147483647u);
}
// Test with string negative value
{
Json::Value j;
int value = -2147483648;
j[sfWasmReturnCode] = std::to_string(value);
j[sfDummyInt32] = std::to_string(value);
STParsedJSONObject obj("Test", j);
BEAST_EXPECT(obj.object.has_value());
if (BEAST_EXPECT(obj.object->isFieldPresent(sfWasmReturnCode)))
BEAST_EXPECT(
obj.object->getFieldI32(sfWasmReturnCode) == value);
if (BEAST_EXPECT(obj.object->isFieldPresent(sfDummyInt32)))
BEAST_EXPECT(obj.object->getFieldI32(sfDummyInt32) == value);
}
// Test out of range value for int32 (negative)
{
Json::Value j;
j[sfWasmReturnCode] = "-2147483649";
j[sfDummyInt32] = "-2147483649";
STParsedJSONObject obj("Test", j);
BEAST_EXPECT(!obj.object.has_value());
}
@@ -791,7 +788,7 @@ class STParsedJSON_test : public beast::unit_test::suite
// Test out of range value for int32 (positive)
{
Json::Value j;
j[sfWasmReturnCode] = 2147483648u;
j[sfDummyInt32] = 2147483648u;
STParsedJSONObject obj("Test", j);
BEAST_EXPECT(!obj.object.has_value());
}
@@ -799,7 +796,7 @@ class STParsedJSON_test : public beast::unit_test::suite
// Test string value out of range
{
Json::Value j;
j[sfWasmReturnCode] = "2147483648";
j[sfDummyInt32] = "2147483648";
STParsedJSONObject obj("Test", j);
BEAST_EXPECT(!obj.object.has_value());
}
@@ -807,7 +804,7 @@ class STParsedJSON_test : public beast::unit_test::suite
// Test bad_type (arrayValue)
{
Json::Value j;
j[sfWasmReturnCode] = Json::Value(Json::arrayValue);
j[sfDummyInt32] = Json::Value(Json::arrayValue);
STParsedJSONObject obj("Test", j);
BEAST_EXPECT(!obj.object.has_value());
}
@@ -815,7 +812,7 @@ class STParsedJSON_test : public beast::unit_test::suite
// Test bad_type (objectValue)
{
Json::Value j;
j[sfWasmReturnCode] = Json::Value(Json::objectValue);
j[sfDummyInt32] = Json::Value(Json::objectValue);
STParsedJSONObject obj("Test", j);
BEAST_EXPECT(!obj.object.has_value());
}

View File

@@ -40,11 +40,6 @@ ApplyContext::discard()
std::optional<TxMeta>
ApplyContext::apply(TER ter)
{
if (wasmReturnCode_.has_value())
{
view_->setWasmReturnCode(*wasmReturnCode_);
}
view_->setGasUsed(gasUsed_);
return view_->apply(
base_, tx, ter, parentBatchId_, flags_ & tapDRY_RUN, journal);
}

View File

@@ -87,20 +87,6 @@ public:
view_->deliver(amount);
}
/** Sets the gas used in the metadata */
void
setGasUsed(std::uint32_t const gasUsed)
{
gasUsed_ = gasUsed;
}
/** Sets the gas used in the metadata */
void
setWasmReturnCode(std::int32_t const wasmReturnCode)
{
wasmReturnCode_ = wasmReturnCode;
}
/** Discard changes and start fresh. */
void
discard();
@@ -152,8 +138,6 @@ private:
// The ID of the batch transaction we are executing under, if seated.
std::optional<uint256 const> parentBatchId_;
std::optional<std::uint32_t> gasUsed_;
std::optional<std::int32_t> wasmReturnCode_;
};
} // namespace ripple

View File

@@ -1,8 +1,6 @@
#include <xrpld/app/misc/HashRouter.h>
#include <xrpld/app/tx/detail/Escrow.h>
#include <xrpld/app/tx/detail/MPTokenAuthorize.h>
#include <xrpld/app/wasm/HostFuncImpl.h>
#include <xrpld/app/wasm/WasmVM.h>
#include <xrpld/conditions/Condition.h>
#include <xrpld/conditions/Fulfillment.h>
@@ -101,30 +99,6 @@ escrowCreatePreflightHelper<MPTIssue>(PreflightContext const& ctx)
return tesSUCCESS;
}
XRPAmount
EscrowCreate::calculateBaseFee(ReadView const& view, STTx const& tx)
{
XRPAmount txnFees{Transactor::calculateBaseFee(view, tx)};
if (tx.isFieldPresent(sfFinishFunction))
{
// 10 base fees for the transaction (1 is in
// `Transactor::calculateBaseFee`), plus 5 drops per byte
txnFees += 9 * view.fees().base + 5 * tx[sfFinishFunction].size();
}
return txnFees;
}
bool
EscrowCreate::checkExtraFeatures(PreflightContext const& ctx)
{
if ((ctx.tx.isFieldPresent(sfFinishFunction) ||
ctx.tx.isFieldPresent(sfData)) &&
!ctx.rules.enabled(featureSmartEscrow))
return false;
return true;
}
NotTEC
EscrowCreate::preflight(PreflightContext const& ctx)
{
@@ -158,21 +132,12 @@ EscrowCreate::preflight(PreflightContext const& ctx)
ctx.tx[sfCancelAfter] <= ctx.tx[sfFinishAfter])
return temBAD_EXPIRATION;
if (ctx.tx.isFieldPresent(sfFinishFunction) &&
!ctx.tx.isFieldPresent(sfCancelAfter))
return temBAD_EXPIRATION;
// In the absence of a FinishAfter, the escrow can be finished
// immediately, which can be confusing. When creating an escrow,
// we want to ensure that either a FinishAfter time is explicitly
// specified or a completion condition is attached.
if (!ctx.tx[~sfFinishAfter] && !ctx.tx[~sfCondition] &&
!ctx.tx[~sfFinishFunction])
{
JLOG(ctx.j.debug()) << "Must have at least one of FinishAfter, "
"Condition, or FinishFunction.";
if (!ctx.tx[~sfFinishAfter] && !ctx.tx[~sfCondition])
return temMALFORMED;
}
if (auto const cb = ctx.tx[~sfCondition])
{
@@ -190,43 +155,6 @@ EscrowCreate::preflight(PreflightContext const& ctx)
}
}
if (ctx.tx.isFieldPresent(sfData))
{
if (!ctx.tx.isFieldPresent(sfFinishFunction))
{
JLOG(ctx.j.debug())
<< "EscrowCreate with Data requires FinishFunction";
return temMALFORMED;
}
auto const data = ctx.tx.getFieldVL(sfData);
if (data.size() > maxWasmDataLength)
{
JLOG(ctx.j.debug()) << "EscrowCreate.Data bad size " << data.size();
return temMALFORMED;
}
}
if (ctx.tx.isFieldPresent(sfFinishFunction))
{
auto const code = ctx.tx.getFieldVL(sfFinishFunction);
if (code.size() == 0 ||
code.size() > ctx.app.config().FEES.extension_size_limit)
{
JLOG(ctx.j.debug())
<< "EscrowCreate.FinishFunction bad size " << code.size();
return temMALFORMED;
}
HostFunctions mock;
auto const re =
preflightEscrowWasm(code, ESCROW_FUNCTION_NAME, {}, &mock, ctx.j);
if (!isTesSuccess(re))
{
JLOG(ctx.j.debug()) << "EscrowCreate.FinishFunction bad WASM";
return re;
}
}
return tesSUCCESS;
}
@@ -485,17 +413,6 @@ escrowLockApplyHelper<MPTIssue>(
return tesSUCCESS;
}
template <class T>
static uint32_t
calculateAdditionalReserve(T const& finishFunction)
{
if (!finishFunction)
return 1;
// First 500 bytes included in the normal reserve
// Each additional 500 bytes requires an additional reserve
return 1 + (finishFunction->size() / 500);
}
TER
EscrowCreate::doApply()
{
@@ -513,11 +430,9 @@ EscrowCreate::doApply()
// Check reserve and funds availability
STAmount const amount{ctx_.tx[sfAmount]};
auto const reserveToAdd =
calculateAdditionalReserve(ctx_.tx[~sfFinishFunction]);
auto const reserve =
ctx_.view().fees().accountReserve((*sle)[sfOwnerCount] + reserveToAdd);
ctx_.view().fees().accountReserve((*sle)[sfOwnerCount] + 1);
if (mSourceBalance < reserve)
return tecINSUFFICIENT_RESERVE;
@@ -552,8 +467,6 @@ EscrowCreate::doApply()
(*slep)[~sfCancelAfter] = ctx_.tx[~sfCancelAfter];
(*slep)[~sfFinishAfter] = ctx_.tx[~sfFinishAfter];
(*slep)[~sfDestinationTag] = ctx_.tx[~sfDestinationTag];
(*slep)[~sfFinishFunction] = ctx_.tx[~sfFinishFunction];
(*slep)[~sfData] = ctx_.tx[~sfData];
if (ctx_.view().rules().enabled(fixIncludeKeyletFields))
{
@@ -623,7 +536,7 @@ EscrowCreate::doApply()
}
// increment owner count
adjustOwnerCount(ctx_.view(), sle, reserveToAdd, ctx_.journal);
adjustOwnerCount(ctx_.view(), sle, 1, ctx_.journal);
ctx_.view().update(sle);
return tesSUCCESS;
}
@@ -651,16 +564,8 @@ checkCondition(Slice f, Slice c)
bool
EscrowFinish::checkExtraFeatures(PreflightContext const& ctx)
{
if (ctx.tx.isFieldPresent(sfCredentialIDs) &&
!ctx.rules.enabled(featureCredentials))
return false;
if (ctx.tx.isFieldPresent(sfComputationAllowance) &&
!ctx.rules.enabled(featureSmartEscrow))
{
return false;
}
return true;
return !ctx.tx.isFieldPresent(sfCredentialIDs) ||
ctx.rules.enabled(featureCredentials);
}
NotTEC
@@ -672,10 +577,7 @@ EscrowFinish::preflight(PreflightContext const& ctx)
// If you specify a condition, then you must also specify
// a fulfillment.
if (static_cast<bool>(cb) != static_cast<bool>(fb))
{
JLOG(ctx.j.debug()) << "Condition != Fulfillment";
return temMALFORMED;
}
return tesSUCCESS;
}
@@ -705,20 +607,6 @@ EscrowFinish::preflightSigValidated(PreflightContext const& ctx)
}
}
if (auto const allowance = ctx.tx[~sfComputationAllowance]; allowance)
{
if (*allowance == 0)
{
return temBAD_LIMIT;
}
if (*allowance > ctx.app.config().FEES.extension_compute_limit)
{
JLOG(ctx.j.debug())
<< "ComputationAllowance too large: " << *allowance;
return temBAD_LIMIT;
}
}
if (auto const err = credentials::checkFields(ctx.tx, ctx.j);
!isTesSuccess(err))
return err;
@@ -735,14 +623,7 @@ EscrowFinish::calculateBaseFee(ReadView const& view, STTx const& tx)
{
extraFee += view.fees().base * (32 + (fb->size() / 16));
}
if (auto const allowance = tx[~sfComputationAllowance]; allowance)
{
// The extra fee is the allowance in drops, rounded up to the nearest
// whole drop.
// Integer math rounds down by default, so we add 1 to round up.
extraFee +=
((*allowance) * view.fees().gasPrice) / MICRO_DROPS_PER_DROP + 1;
}
return Transactor::calculateBaseFee(view, tx) + extraFee;
}
@@ -822,52 +703,25 @@ EscrowFinish::preclaim(PreclaimContext const& ctx)
return err;
}
if (ctx.view.rules().enabled(featureTokenEscrow) ||
ctx.view.rules().enabled(featureSmartEscrow))
if (ctx.view.rules().enabled(featureTokenEscrow))
{
// this check is done in doApply before this amendment is enabled
auto const k = keylet::escrow(ctx.tx[sfOwner], ctx.tx[sfOfferSequence]);
auto const slep = ctx.view.read(k);
if (!slep)
return tecNO_TARGET;
if (ctx.view.rules().enabled(featureSmartEscrow))
{
if (slep->isFieldPresent(sfFinishFunction))
{
if (!ctx.tx.isFieldPresent(sfComputationAllowance))
{
JLOG(ctx.j.debug())
<< "FinishFunction requires ComputationAllowance";
return tefWASM_FIELD_NOT_INCLUDED;
}
}
else
{
if (ctx.tx.isFieldPresent(sfComputationAllowance))
{
JLOG(ctx.j.debug()) << "FinishFunction not present, "
"ComputationAllowance present";
return tefNO_WASM;
}
}
}
if (ctx.view.rules().enabled(featureTokenEscrow))
{
AccountID const dest = (*slep)[sfDestination];
STAmount const amount = (*slep)[sfAmount];
AccountID const dest = (*slep)[sfDestination];
STAmount const amount = (*slep)[sfAmount];
if (!isXRP(amount))
{
if (auto const ret = std::visit(
[&]<typename T>(T const&) {
return escrowFinishPreclaimHelper<T>(
ctx, dest, amount);
},
amount.asset().value());
!isTesSuccess(ret))
return ret;
}
if (!isXRP(amount))
{
if (auto const ret = std::visit(
[&]<typename T>(T const&) {
return escrowFinishPreclaimHelper<T>(ctx, dest, amount);
},
amount.asset().value());
!isTesSuccess(ret))
return ret;
}
}
return tesSUCCESS;
@@ -1102,8 +956,7 @@ EscrowFinish::doApply()
auto const slep = ctx_.view().peek(k);
if (!slep)
{
if (ctx_.view().rules().enabled(featureTokenEscrow) ||
ctx_.view().rules().enabled(featureSmartEscrow))
if (ctx_.view().rules().enabled(featureTokenEscrow))
return tecINTERNAL; // LCOV_EXCL_LINE
return tecNO_TARGET;
@@ -1121,20 +974,6 @@ EscrowFinish::doApply()
if ((*slep)[~sfCancelAfter] && after(now, (*slep)[sfCancelAfter]))
return tecNO_PERMISSION;
AccountID const destID = (*slep)[sfDestination];
auto const sled = ctx_.view().peek(keylet::account(destID));
if (ctx_.view().rules().enabled(featureSmartEscrow))
{
// NOTE: Escrow payments cannot be used to fund accounts.
if (!sled)
return tecNO_DST;
if (auto err = verifyDepositPreauth(
ctx_.tx, ctx_.view(), account_, destID, sled, ctx_.journal);
!isTesSuccess(err))
return err;
}
// Check cryptocondition fulfillment
{
auto const id = ctx_.tx.getTransactionID();
@@ -1184,69 +1023,16 @@ EscrowFinish::doApply()
return tecCRYPTOCONDITION_ERROR;
}
if (!ctx_.view().rules().enabled(featureSmartEscrow))
{
// NOTE: Escrow payments cannot be used to fund accounts.
if (!sled)
return tecNO_DST;
// NOTE: Escrow payments cannot be used to fund accounts.
AccountID const destID = (*slep)[sfDestination];
auto const sled = ctx_.view().peek(keylet::account(destID));
if (!sled)
return tecNO_DST;
if (auto err = verifyDepositPreauth(
ctx_.tx, ctx_.view(), account_, destID, sled, ctx_.journal);
!isTesSuccess(err))
return err;
}
// Execute custom release function
if ((*slep)[~sfFinishFunction])
{
JLOG(j_.trace())
<< "The escrow has a finish function, running WASM code...";
// WASM execution
auto const wasmStr = slep->getFieldVL(sfFinishFunction);
std::vector<uint8_t> wasm(wasmStr.begin(), wasmStr.end());
WasmHostFunctionsImpl ledgerDataProvider(ctx_, k);
if (!ctx_.tx.isFieldPresent(sfComputationAllowance))
{
// already checked above, this check is just in case
return tecINTERNAL;
}
std::uint32_t allowance = ctx_.tx[sfComputationAllowance];
auto re = runEscrowWasm(
wasm, ESCROW_FUNCTION_NAME, {}, &ledgerDataProvider, allowance);
JLOG(j_.trace()) << "Escrow WASM ran";
if (auto const& data = ledgerDataProvider.getData(); data.has_value())
{
slep->setFieldVL(sfData, makeSlice(*data));
ctx_.view().update(slep);
}
if (re.has_value())
{
auto reValue = re.value().result;
auto reCost = re.value().cost;
JLOG(j_.debug()) << "WASM Success: " + std::to_string(reValue)
<< ", cost: " << reCost;
ctx_.setWasmReturnCode(reValue);
if (reCost < 0 || reCost > std::numeric_limits<uint32_t>::max())
return tecINTERNAL; // LCOV_EXCL_LINE
ctx_.setGasUsed(static_cast<uint32_t>(reCost));
if (reValue <= 0)
{
return tecWASM_REJECTED;
}
}
else
{
JLOG(j_.debug()) << "WASM Failure: " + transHuman(re.error());
return re.error();
}
}
if (auto err = verifyDepositPreauth(
ctx_.tx, ctx_.view(), account_, destID, sled, ctx_.journal);
!isTesSuccess(err))
return err;
AccountID const account = (*slep)[sfAccount];
@@ -1324,12 +1110,9 @@ EscrowFinish::doApply()
ctx_.view().update(sled);
auto const reserveToSubtract =
calculateAdditionalReserve((*slep)[~sfFinishFunction]);
// Adjust source owner count
auto const sle = ctx_.view().peek(keylet::account(account));
adjustOwnerCount(ctx_.view(), sle, -1 * reserveToSubtract, ctx_.journal);
adjustOwnerCount(ctx_.view(), sle, -1, ctx_.journal);
ctx_.view().update(sle);
// Remove escrow from ledger
@@ -1529,9 +1312,7 @@ EscrowCancel::doApply()
}
}
auto const reserveToSubtract =
calculateAdditionalReserve((*slep)[~sfFinishFunction]);
adjustOwnerCount(ctx_.view(), sle, -1 * reserveToSubtract, ctx_.journal);
adjustOwnerCount(ctx_.view(), sle, -1, ctx_.journal);
ctx_.view().update(sle);
// Remove escrow from ledger

View File

@@ -14,15 +14,9 @@ public:
{
}
static bool
checkExtraFeatures(PreflightContext const& ctx);
static TxConsequences
makeTxConsequences(PreflightContext const& ctx);
static XRPAmount
calculateBaseFee(ReadView const& view, STTx const& tx);
static NotTEC
preflight(PreflightContext const& ctx);

View File

@@ -1008,22 +1008,6 @@ removeExpiredCredentials(
}
}
static void
modifyWasmDataFields(
ApplyView& view,
std::vector<std::pair<uint256, Blob>> const& wasmObjects,
beast::Journal viewJ)
{
for (auto const& [index, data] : wasmObjects)
{
if (auto const sle = view.peek(keylet::escrow(index)))
{
sle->setFieldVL(sfData, data);
view.update(sle);
}
}
}
static void
removeDeletedTrustLines(
ApplyView& view,
@@ -1182,7 +1166,6 @@ Transactor::operator()()
else if (
(result == tecOVERSIZE) || (result == tecKILLED) ||
(result == tecINCOMPLETE) || (result == tecEXPIRED) ||
(result == tecWASM_REJECTED) ||
(isTecClaimHardFail(result, view().flags())))
{
JLOG(j_.trace()) << "reapplying because of " << transToken(result);
@@ -1195,16 +1178,13 @@ Transactor::operator()()
std::vector<uint256> removedTrustLines;
std::vector<uint256> expiredNFTokenOffers;
std::vector<uint256> expiredCredentials;
std::vector<std::pair<uint256, Blob>> modifiedWasmObjects;
bool const doOffers =
((result == tecOVERSIZE) || (result == tecKILLED));
bool const doLines = (result == tecINCOMPLETE);
bool const doNFTokenOffers = (result == tecEXPIRED);
bool const doCredentials = (result == tecEXPIRED);
bool const doWasmData = (result == tecWASM_REJECTED);
if (doOffers || doLines || doNFTokenOffers || doCredentials ||
doWasmData)
if (doOffers || doLines || doNFTokenOffers || doCredentials)
{
ctx_.visit([doOffers,
&removedOffers,
@@ -1213,9 +1193,7 @@ Transactor::operator()()
doNFTokenOffers,
&expiredNFTokenOffers,
doCredentials,
&expiredCredentials,
doWasmData,
&modifiedWasmObjects](
&expiredCredentials](
uint256 const& index,
bool isDelete,
std::shared_ptr<SLE const> const& before,
@@ -1250,13 +1228,6 @@ Transactor::operator()()
(before->getType() == ltCREDENTIAL))
expiredCredentials.push_back(index);
}
if (doWasmData && before && after &&
(before->getType() == ltESCROW))
{
modifiedWasmObjects.push_back(
std::make_pair(index, after->getFieldVL(sfData)));
}
});
}
@@ -1286,10 +1257,6 @@ Transactor::operator()()
removeExpiredCredentials(
view(), expiredCredentials, ctx_.app.journal("View"));
if (result == tecWASM_REJECTED)
modifyWasmDataFields(
view(), modifiedWasmObjects, ctx_.app.journal("View"));
applied = isTecClaim(result);
}